2021-06-30 21:21:54 +08:00

411 lines
15 KiB
JavaScript
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Codemirror from '../../assets/js/codemirror';
import '../../assets/js/codemirror/mode/xml';
import '../../assets/js/codemirror/mode/css';
import '../../assets/js/codemirror/mode/javascript';
import '../../assets/js/codemirror/mode/markdown';
import codemirrorConfig from '../../assets/js/codemirror/config';
import '../../assets/js/codemirror/styles/codemirror.css';
import common from '../../mixins/common';
import marked from '../../config/marked';
import tocObj from "../../assets/js/marked/createToc";
export default {
name: 'markdown-pro',
mixins: [common],
data() {
return {
pro: true,
editor: null, // 编辑器实例
lastPos: '' // 光标最后所在位置,
};
},
mounted() {
this.init();
this.createEditor();
},
methods: {
init() {// 初始化
this.currentValue = this.value;
this.themeName = this.theme;
this.preview = this.isPreview;
this.currentValue = this.value;
if (this.isPreview) {
return;
}
setTimeout(() => {
if (this.autoSave) {
this.timerId = setInterval(() => {
this.handleSave();
}, this.interval);
}
}, 20);
},
createEditor() {// 初始化左侧编辑器
this.editor = new Codemirror(this.$refs.codemirror, {
value: this.currentValue,
onload: (data) => {
const {doc: {height = 0}} = data;
this.editorScrollHeight = height;
},
...codemirrorConfig
});
this.addEditorLintener();
this.$emit('on-ready', {
vm: this,
insertContent: this.insertContent
})
},
addEditorLintener() {//绑定监听事件
const editor = this.editor;
editor.on('change', data => {
this.lastPos = editor.getCursor();
this.currentValue = editor.getValue();
const {
doc: {height}
} = data;
this.editorScrollHeight = height;
});
editor.on('scroll', this.markdownScroll);
editor.on('paste', this.handlePaste);
editor.on('keydown', (data, e) => {
if (e.keyCode === 83) {
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
this.handleSave();
}
} else if (e.keyCode === 13) {
this.listerenKeyupEnter(e);
} else if (e.keyCode === 8) {
this.listerenDelete(data);
}
});
editor.on('focus', () => {
this.lastPos = editor.getCursor();
});
},
insertContent(str) {// 插入文本
this.editor.replaceSelection(str);
this.lastInsert = str.replace(/\n/g, '');
},
setCursor(line = 0, ch = 0) {// 设置焦点
const {editor} = this;
editor.setCursor(line, ch);
editor.focus();
},
insertStrong() {// 粗体
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('**' + selection + '**');
} else {
this.insertContent('****');
this.setCursor(line, ch + 2);
}
},
insertItalic() {// 斜体
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('*' + selection + '*');
} else {
this.insertContent('**');
this.setCursor(line, ch + 1);
}
},
insertUnderline() {// 下划线
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('<u>' + selection + '</u>');
} else {
this.insertContent('<u></u>');
this.setCursor(line, ch + 3);
}
},
insertOverline() {// 删除线
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('~~' + selection + '~~');
} else {
this.insertContent('~~~~');
this.setCursor(line, ch + 2);
}
},
insertTitle(level) {// 插入标题
const titles = {
1: '# ',
2: '## ',
3: '### ',
4: '#### ',
5: '##### ',
6: '###### '
};
const {editor, lastPos = {}} = this;
const {line} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('\n' + titles[level] + selection + '\n');
} else {
const title = titles[level];
if (editor.isClean()) {
this.insertContent(title);
this.setCursor(0, title.length);
} else {
this.insertContent('\n' + title);
this.setCursor(line + 1, title.length);
}
}
},
insertLine() {// 插入分割线
const {editor} = this;
if (editor.isClean()) {
this.insertContent('----\n');
} else {
this.insertContent('\n\n----\n');
}
},
insertQuote() {// 引用
const {editor, lastPos = {}} = this;
const {line = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('\n> ' + selection + '\n\n');
} else {
if (editor.isClean()) {
this.insertContent('> ');
this.setCursor(0, 3);
} else {
this.insertContent('\n> ');
this.setCursor(line + 1, 3);
}
}
},
insertUl() {// 无序列表
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('\n- ' + selection + '\n\n');
} else {
if (editor.isClean() || ch === 0) {
this.insertContent('- ');
this.setCursor(line, 3);
} else {
this.insertContent('\n- ');
this.setCursor(line + 1, 3);
}
}
},
insertOl() {// 有序列表
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('\n1. ' + selection + '\n\n');
} else {
if (editor.isClean() || ch === 0) {
this.insertContent('1. ');
this.setCursor(line, 4);
} else {
this.insertContent('\n1. ');
this.setCursor(line + 1, 4);
}
}
},
insertCode() {// 插入code
const {editor, lastPos = {}} = this;
const {line} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('\n```\n' + selection + '\n```\n');
} else {
if (editor.isClean()) {
this.insertContent('```\n\n```');
this.setCursor(1, 0);
} else {
this.insertContent('\n```\n\n```');
this.setCursor(line + 2, 0);
}
}
},
insertFinished() {// 已完成列表
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('\n- [x] ' + selection + '\n\n');
} else {
if (editor.isClean() || ch === 0) {
this.insertContent('- [x] ');
this.setCursor(line, 6);
} else {
this.insertContent('\n- [x] ');
this.setCursor(line + 1, 6);
}
}
},
insertNotFinished() {// 未完成列表
const {editor, lastPos = {}} = this;
const {line = 0, ch = 0} = lastPos;
const selection = editor.getSelection();
if (selection) {
this.insertContent('\n- [ ] ' + selection + '\n\n');
} else {
if (editor.isClean() || ch === 0) {
this.insertContent('- [ ] ');
this.setCursor(line, 6);
} else {
this.insertContent('\n- [ ] ');
this.setCursor(line + 1, 6);
}
}
},
listerenKeyupEnter(e) {// 回车事件
const {lastInsert} = this;
if (lastInsert) {
const list = ['-', '- [ ]', '- [x]'];
if (list.includes(lastInsert.trim())) {
e.preventDefault();
this.insertContent('\n' + lastInsert);
} else if (/^\d+\.$/.test(lastInsert.trim())) {
e.preventDefault();
this.insertContent(
'\n' + (parseInt(lastInsert, 0) + 1) + '. '
);
}
}
},
listerenDelete() {// 删除 backup
setTimeout(() => {
const {editor} = this;
if (!editor.isClean()) {
const value = editor.getValue();
if (value.split('\n').pop() === '') {
this.lastInsert = '';
}
}
}, 20);
},
onDelete() {// 删除时,以回车为界分割,如果数组最后一个元素为''时,将行一次插入的共嗯那个置为空,避免回车时再次插入
const lines = this.currentValue.split('\n');
if (lines[lines.length - 1] === '') {
this.lastInsert = '';
}
},
markdownScroll(data = {}) {//编辑器区域滚动
if (this.scrolling && this.scrollSide === 'left') {
const {
doc: {height, scrollTop}
} = data;
const preview = this.$refs.preview;
const contentHeight = preview.offsetHeight;
const previewScrollHeight = preview.scrollHeight;
preview.scrollTop = parseInt(
(scrollTop * (previewScrollHeight - contentHeight)) /
(height - contentHeight),
0
);
}
},
previewScroll() {//预览内容区域滚动
if (this.scrolling && this.scrollSide === 'right') {
const preview = this.$refs.preview;
const contentHeight = preview.offsetHeight;
const previewScrollHeight = preview.scrollHeight;
const previewScrollTop = preview.scrollTop;
const scrollTop = parseInt((previewScrollTop * (this.editorScrollHeight - contentHeight)) / (previewScrollHeight - contentHeight), 0);
this.editor.scrollTo(0, scrollTop);
//
const container = $A(this.$refs.preview);
let inner, topId;
container.find("h1,h2,h3,h4,h5").each((index, item) => {
inner = $A(item);
if (inner.offset().top - container.offset().top >= 0 && (topId = inner.attr("toc-id"))) {
this.tocAction = topId;
return false;
}
});
}
},
redo() {
const {editor} = this;
editor.redo();
setTimeout(() => {
editor.refresh();
}, 20);
},
tocLevel(l, lists) {
let minLevel = 9999;
if (typeof lists === "undefined") {
lists = this.tocLists;
}
lists.forEach(({level}) => {
minLevel = Math.min(minLevel, level)
});
if (minLevel === 9999) {
return l;
}
return l - (minLevel - 1);
},
tocClick(item) {
this.tocAction = item.anchor;
const container = $A(this.$refs.preview);
const inner = container.find("h" + item.level + '[toc-id="' + this.tocAction + '"]');
if (inner) {
container.animate({
scrollTop: inner.offset().top - container.offset().top + container.scrollTop()
});
}
}
},
watch: {
currentValue() {
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
const {currentValue} = this;
let html = marked(currentValue, {
sanitize: false,
...this.markedOptions
}).replace(/href="/gi, 'target="_blank" href="');
if (this.copyCode && html !== '') {
html = html.replace(/<pre>/g, '<div class="code-block"><span class="copy-code">' + this.copyBtnText + '</span><pre>').replace(/<\/pre>/g, '</pre></div>')
}
if (/\[\[TOC\]\]/.test(html)) {
let string = '';
tocObj.tocItems.forEach((item) => {
string+= `<li class="toc-anchor-item" onclick="_goTocAction(this, '${item.level}', '${item.anchor}')"><span class="toc-link-${this.tocLevel(item.level, tocObj.tocItems)}" title="${item.text}">${item.text}</span></li>`;
});
html = html.replace(/\[\[TOC\]\]/g, `<ul class="toc-anchor">${string}</ul>`)
}
this.html = html;
//toc
this.tocLists = tocObj.tocItems;
if (!this.tocTrigger) {
this.tocShow = this.tocLists.length > 1
}
tocObj.reset()
this.addImageClickListener();
this.addCopyListener();
this.$emit('input', currentValue);
}, 30);
},
value() {
const {value, currentValue} = this;
if (currentValue !== value) {// 由于用户输入而造成的value变化不对editor设置值
this.currentValue = value;
this.editor.setOption('value', value);
}
}
}
};