提交 d9a2875b 编写于 作者: 璃白.'s avatar 璃白. 🌻

feat:添加格式刷

上级 96e37f77
无法预览此类型文件
......@@ -51,7 +51,7 @@ void main()
{
if(i%j==0)
{
a+=i/j;{1}
a+=i/j;
}
}
if(i==a){
......@@ -81,7 +81,7 @@ void main()
// aaa.remove()
// })
// `,
value: "",
value: "abcabcabc123**中文的**字体",
themeOptions: {
dark: false,
borderColorActive: "#409eff",
......@@ -90,11 +90,12 @@ void main()
codeTheme: "atom-one-dark"
},
toolsOptions: {
format: true,
bold: true,
italic: true,
quote: false,
quote: true,
code: true,
link: false,
link: true,
ul: true,
ol: true,
task: true,
......@@ -103,7 +104,7 @@ void main()
fullScreen: true
},
zIndex: 7000,
// maxLength: 1000,
maxLength: 20000,
showWordLimit: true,
rows: "10",
// height: "600",
......
此差异已折叠。
......@@ -69,7 +69,6 @@
:can-attach-file="canAttachFile"
v-if="!showPreview && canAttachFile"
/> -->
<tableSelect />
</div>
</template>
<script>
......@@ -77,15 +76,13 @@ import markdownHeader from "./components/header/md-header";
import markdownFooter from "./components/footer/md-footer";
import markdownEditor from "./components/content/md-textarea";
import markdownPreview from "./components/content/md-preview";
import tableSelect from "./components/header/components/table-select";
import { formatText, checktUrl } from "@/assets/js/utils";
export default {
components: {
markdownHeader,
markdownFooter,
markdownEditor,
markdownPreview,
tableSelect
markdownPreview
},
props: {
placeholder: {
......@@ -329,7 +326,7 @@ export default {
</script>
<style lang="less" scoped>
.md_container {
width: 100%;
// width: 100%;
background: var(--md-editor-frame-bg-color);
// margin: 200px auto;
border: 1px solid var(--md-editor-border-color);
......
......@@ -3,11 +3,12 @@
export function getSelectionInfo(selectorId) {
const selector = document.getElementById(selectorId);
if (!selector) return;
// const selection = window.getSelection();
const { selectionStart = 0, selectionEnd = 0 } = selector;
if (selectionStart === selectionEnd) return "";
const selection = window.getSelection().toString();
return {
selectorId,
selection,
selectionStart,
selectionEnd
};
......@@ -188,3 +189,110 @@ export function getFilteredTags(oldStr, newStr) {
const filteredTags = Array.from(virtualDom.getElementsByTagName("*"));
return filteredTags;
}
// 格式刷的规则
export function copyFormatRules(selection) {
if (!selection) return "";
const first1 = selection.slice(0, 1);
const first2 = selection.slice(0, 2);
const first3 = selection.slice(0, 3);
const first4 = selection.slice(0, 4);
const first5 = selection.slice(0, 5);
const first6 = selection.slice(0, 6);
const first7 = selection.slice(0, 7);
const end1 = selection.slice(-1);
const end2 = selection.slice(-2);
const end3 = selection.slice(-3);
let formatType = "";
console.log(first6);
switch (true) {
// 斜体
case first1 === "_" && end1 === "_":
formatType = {
startStr: "_",
endStr: "_"
};
break;
// 加粗
case first2 === "**" && end2 === "**":
formatType = {
startStr: "**",
endStr: "**"
};
break;
// 代码
case first3 === "```" && end3 === "```":
formatType = {
startStr: "\n```\n",
endStr: "\n\n```"
};
break;
// 引用
case first2 === "> ":
formatType = {
startStr: "\n> ",
endStr: ""
};
break;
// 待办
case first6 === "- [ ] ":
formatType = {
startStr: "\n- [ ] ",
endStr: ""
};
break;
// 无序列表
case first2 === "- ":
formatType = {
startStr: "\n- ",
endStr: ""
};
break;
// H6
case first7 === "###### ":
formatType = {
startStr: "\n###### ",
endStr: ""
};
break;
// H5
case first6 === "##### ":
formatType = {
startStr: "\n##### ",
endStr: ""
};
break;
// H4
case first5 === "#### ":
formatType = {
startStr: "\n#### ",
endStr: ""
};
break;
// H3
case first4 === "### ":
formatType = {
startStr: "\n### ",
endStr: ""
};
break;
// H2
case first3 === "## ":
formatType = {
startStr: "\n## ",
endStr: ""
};
break;
// H1
case first2 === "# ":
formatType = {
startStr: "\n# ",
endStr: ""
};
break;
default:
break;
}
return formatType;
}
......@@ -6,6 +6,8 @@
padding: 0;
margin: 0;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-family: -apple-system, SF UI Text, Arial, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, WenQuanYi Micro Hei, sans-serif, SimHei, SimSun;
}
html,
......
@font-face {
font-family: "iconfont"; /* Project id */
src: url('font/iconfont.ttf?t=1624355824015') format('truetype');
src: url('./font/iconfont.ttf') format('truetype');
}
.iconfont {
......@@ -59,3 +59,11 @@
content: "\eb98";
}
.icon-geshishua:before {
content: "\e6bb";
}
.icon-yinyong:before {
content: "\e697";
}
......@@ -297,9 +297,9 @@ export default {
color: var(--md-editor-text-color-active);
height: var(--md-editor-height);
resize: none;
font-family: "Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",
"Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace;
font-family: "Menlo", -apple-system, SF UI Text, Arial, PingFang SC,
Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif, SimHei,
SimSun;
&::placeholder {
color: var(--md-editor-text-color);
}
......
......@@ -52,11 +52,27 @@ export default {
</script>
<style lang="less" scoped>
ul {
max-height: 160px;
max-height: 162px;
overflow-y: auto;
margin-top: 2px;
margin-bottom: 6px;
border-top: 1px solid #e1e1e1;
padding-top: 4px;
box-sizing: border-box;
scrollbar-color: transparent transparent;
// scrollbar-track-color: transparent;
// -ms-scrollbar-track-color: transparent;
&::-webkit-scrollbar {
display: none;
width: 2px;
height: 2px;
}
&::-webkit-scrollbar-thumb {
border-radius: 1em;
background-color: rgba(50, 50, 50, 0.3);
}
&::-webkit-scrollbar-track {
border-radius: 1em;
background-color: rgba(50, 50, 50, 0);
}
li {
cursor: pointer;
......
<template>
<div class="block_container" ref="block_container">
<div
class="block_container"
@mousemove.stop="getCursorPoint"
@mouseout="resetCursorPoint"
@click.stop="handleSelectTable"
ref="block_container"
>
<span v-show="lastItem.row && lastItem.col" class="select_num"
>{{ lastItem.row }}&nbsp;x&nbsp;{{ lastItem.col }}</span
>
<div
@mousemove.stop="getCursorPoint"
ref="block"
:class="['block', { active: item.active }]"
v-for="(item, index) in list"
:key="index"
......@@ -13,23 +22,98 @@ import { throttle as throttleFn } from "@/assets/js/utils";
export default {
data() {
return {
list: new Array(40).fill({
index: 0,
active: false
})
list: new Array(60).fill(0).map((item, index) => {
return {
index: index + 1,
active: false,
row: Math.floor(index / 10) + 1,
col: (index % 10) + 1
};
}),
mousePoint: {
x: 0,
y: 0
},
dialogPositon: {
x: 0,
y: 0
},
lastItem: {
row: 0,
col: 0
}
};
},
computed: {},
created() {
setTimeout(() => {
// this.dialogPositon = this.getDialogPosition();
this.createBlockPoint();
}, 0);
},
methods: {
getCursorPoint(e) {
console.log(e.clientX, e.clientY);
const target = e.target;
handleSelectTable() {
const { row, col } = this.lastItem;
const str = `\n\n|${this.createStr(" 表头 |", col)}\n|${this.createStr(
" ------ |",
col
)}\n${this.createStr("|" + this.createStr(" 单元格 |", col) + "\n", row)}`;
console.log(str);
const { offsetLeft, offsetTop } = target;
if (e.clientX > offsetLeft) {
target.className = "block active";
}
// console.log(offsetLeft, offsetTop);
this.$emit("select", str);
},
createStr(str, num) {
let res = "";
new Array(num).fill(0).forEach(item => {
res += str;
});
return res;
},
// getDialogPosition() {
// const dialog = this.$refs.block_container.parentElement;
// const transform = document.defaultView.getComputedStyle(dialog).transform;
// const list = transform.split(",");
// console.log(document.body);
// return { y: parseInt(list.pop()), x: parseInt(list.pop()) };
// },
createBlockPoint() {
const list = this.$refs.block;
this.list = this.list.map((item, index) => {
// item.x = list[index].offsetLeft;
item.x = list[index].getBoundingClientRect().left;
// item.y = list[index].offsetTop;
item.y = list[index].getBoundingClientRect().top;
return item;
});
// console.log(this.list);
},
getCursorPoint(e) {
this.mousePoint = {
x: e.clientX,
y: e.clientY
};
this.setBlockActive();
},
resetCursorPoint() {
this.lastItem = {
row: 0,
col: 0
};
this.list.forEach(item => {
item.active = false;
});
},
setBlockActive() {
const mousePoint = this.mousePoint;
const list = this.list;
list.forEach(item => {
item.active = mousePoint.x > item.x && mousePoint.y > item.y;
});
const lastItem = list.filter(item => item.active).pop();
if (!lastItem) return;
this.lastItem = { row: lastItem.row, col: lastItem.col };
}
}
};
......@@ -42,8 +126,14 @@ export default {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
cursor: pointer;
margin-bottom: 4px;
.select_num {
position: absolute;
top: 8px;
right: 10px;
}
.block {
cursor: pointer;
width: @block-size;
height: @block-size;
box-sizing: border-box;
......
......@@ -26,6 +26,7 @@
v-else
v-tip.bottom="options"
@click="handleTool(info.name, info.startStr, info.endStr)"
@dblclick="handleDbClick(info.name)"
class="tool_button"
>
<span :class="['icon iconfont', `icon-${info.icon}`]"></span>
......@@ -33,10 +34,10 @@
</template>
<script>
import { formatText, checkBoswer } from "@/assets/js/utils";
import languageList from "./language-list";
import codeSelect from "./code-select";
import tableSelect from "./table-select";
export default {
components: { languageList, tableSelect },
components: { codeSelect, tableSelect },
props: {
info: {
type: Object,
......@@ -68,9 +69,7 @@ export default {
}
},
data() {
return {
// ulNum: 1
};
return {};
},
computed: {
darkMode() {
......@@ -90,12 +89,14 @@ export default {
codeOptions() {
return {
content: this.info.tip,
customComponent: languageList,
customComponent: codeSelect,
customClass: "codeSelectDialog",
width: 110,
customListeners: {
select: val => {
console.log(val);
this.closeTips();
const lang = val.toLowerCase().replace(/-/, "");
this.handleTool("code", "\n```" + lang, "\n\n\n```");
this.handleTool("code", "\n```" + lang + "\n", "\n\n\n```");
}
},
zIndex: parseInt(this.zIndex) + 1,
......@@ -105,12 +106,13 @@ export default {
tableOptions() {
return {
content: this.info.tip,
customClass: "tableSelectDialog",
customComponent: tableSelect,
customListeners: {
select: val => {
console.log(val);
const lang = val.toLowerCase().replace(/-/, "");
this.handleTool("code", "\n```" + lang, "\n\n\n```");
this.handleTool("table", val, "");
this.closeTips();
}
},
zIndex: parseInt(this.zIndex) + 1,
......@@ -119,6 +121,17 @@ export default {
}
},
methods: {
closeTips() {
Array.from(document.getElementsByClassName("v-tip-container")).map(
item => {
item.remove();
}
);
},
handleDbClick(type) {
if (type !== "format") return;
this.$emit("setFormatType", { lock: true });
},
handleTool(type, startStr, endStr) {
switch (type) {
case "bold":
......@@ -129,11 +142,14 @@ export default {
case "ul":
case "task":
case "table":
this.updateText(startStr, endStr);
this.$emit("updateText", { startStr, endStr });
break;
case "format":
this.$emit("setFormatType", { lock: false });
break;
case "ol":
let ulNum = this.ulNum;
this.updateText(`\n${ulNum++}. `, "");
this.$emit("updateText", { startStr: `\n${ulNum++}. `, endStr: "" });
this.$emit("update:ulNum", ulNum);
break;
case "file":
......@@ -148,17 +164,6 @@ export default {
default:
break;
}
},
updateText(startStr, endStr) {
const originalText = this.text;
const selectionInfo = this.selectionInfo;
const newText = formatText(originalText, selectionInfo, startStr, endStr);
const len =
selectionInfo.selectionEnd -
selectionInfo.selectionStart +
startStr.length;
this.$emit("updateText", { val: newText, len });
}
}
};
......@@ -168,14 +173,20 @@ export default {
padding: 4px 8px;
box-sizing: border-box;
cursor: pointer;
&.active {
.icon {
color: var(--md-editor-border-color-active);
}
}
.icon {
font-size: 18px;
color: var(--md-editor-text-color);
@media (any-hover: hover) {
&:hover {
color: var(--md-editor-text-color-active);
color: var(--md-editor-border-color-active);
}
}
&.icon-quxiaoquanping_o {
font-size: 24px;
margin: 0 -4px;
......
......@@ -24,9 +24,12 @@
@setFullScreen="$emit('update:fullScreen', $event)"
@updateText="handleUpdateText"
@upload="$emit('upload')"
@setFormatType="setFormatType"
:class="{ active: item.name === 'format' && formatType }"
v-for="(item, index) in toolsShow"
:key="index"
:text="text"
:formatType.sync="formatType"
:zIndex="zIndex"
:themeOptions="themeOptions"
:selectionInfo="selectionInfo"
......@@ -39,7 +42,8 @@ import {
isNotFalse,
formatText,
getPosition,
removeBlankLine
removeBlankLine,
copyFormatRules
} from "@/assets/js/utils";
import toolButton from "./components/tool-button";
export default {
......@@ -113,8 +117,15 @@ export default {
}
}
},
text: {
handler: function(newVal, oldVal) {}
selectionInfo: {
handler: function(newVal, oldVal) {
if (
newVal.selectionStart === oldVal.selectionStart &&
newVal.selectionEnd === oldVal.selectionEnd
)
return;
this.copyFormat();
}
}
},
data() {
......@@ -125,6 +136,8 @@ export default {
icon: "quxiaoquanping_o",
tip: "退出全屏"
},
formatType: "", // 格式刷类型
lock: false,
ulNum: 1,
fullScreenBtn: {
name: "fullScreen",
......@@ -132,6 +145,11 @@ export default {
tip: "全屏模式"
},
toolButtonList: [
{
name: "format",
icon: "geshishua",
tip: "格式刷"
},
{
name: "bold",
icon: "bold",
......@@ -148,7 +166,7 @@ export default {
},
{
name: "quote",
icon: "baojiaquotation",
icon: "yinyong",
tip: "插入引用",
startStr: "\n> ",
endStr: ""
......@@ -157,15 +175,15 @@ export default {
name: "code",
icon: "code",
tip: "插入代码块",
startStr: "\n```",
startStr: "\n```\n",
endStr: "\n\n\n```"
},
{
name: "link",
icon: "lianjie",
tip: "添加链接",
startStr: "[",
endStr: "](url)"
startStr: "[链接](",
endStr: ")"
},
{
name: "ul",
......@@ -200,7 +218,7 @@ export default {
icon: "biaoge",
tip: "添加表格",
startStr:
"\n| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |\n\n",
"\n\n| 表头 | 表头 |\n| ------ | ------ |\n| 单元格 | 单元格 |\n| 单元格 | 单元格 |\n\n",
endStr: ""
},
{
......@@ -211,11 +229,36 @@ export default {
]
};
},
methods: {
resetUlNum() {
this.ulNum = 1;
},
setFormatType({ lock }) {
if (this.formatType) {
this.formatType = "";
return;
}
const selectionInfo = this.selectionInfo;
const selection = this.text.slice(
selectionInfo.selectionStart,
selectionInfo.selectionEnd
);
const formatType = copyFormatRules(selection);
this.formatType = formatType;
this.lock = lock;
},
copyFormat() {
const selectionInfo = this.selectionInfo;
const formatType = this.formatType;
if (
selectionInfo.selectionStart === selectionInfo.selectionEnd ||
!formatType
)
return;
this.handleUpdateText(formatType);
if (this.lock) return;
this.formatType = "";
},
tab() {
const textEl = document.getElementById(this.id);
const scrollTop = textEl.scrollTop;
......@@ -238,9 +281,16 @@ export default {
setShowPreview(val) {
this.$emit("update:showPreview", val);
},
handleUpdateText({ val, len }) {
// const cursorPoint = getPosition(this.id);
this.updateText(val, len);
handleUpdateText({ startStr, endStr }) {
const originalText = this.text;
const selectionInfo = this.selectionInfo;
const newText = formatText(originalText, selectionInfo, startStr, endStr);
const len =
selectionInfo.selectionEnd -
selectionInfo.selectionStart +
startStr.length;
this.updateText(newText, len);
},
updateText(val, len = 0) {
const textEl = document.getElementById(this.id);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册