vditor
Version:
♏ 易于使用的 Markdown 编辑器,为适配不同的应用场景而生
565 lines (512 loc) • 20.2 kB
text/typescript
import "./assets/less/index.less";
import VditorMethod from "./method";
import {Constants, VDITOR_VERSION} from "./ts/constants";
import {DevTools} from "./ts/devtools/index";
import {Hint} from "./ts/hint/index";
import {IR} from "./ts/ir/index";
import {input as irInput} from "./ts/ir/input";
import {processAfterRender} from "./ts/ir/process";
import {getHTML} from "./ts/markdown/getHTML";
import {getMarkdown} from "./ts/markdown/getMarkdown";
import {setLute} from "./ts/markdown/setLute";
import {Outline} from "./ts/outline/index";
import {Preview} from "./ts/preview/index";
import {Resize} from "./ts/resize/index";
import {Editor} from "./ts/sv/index";
import {inputEvent} from "./ts/sv/inputEvent";
import {processAfterRender as processSVAfterRender, processPaste} from "./ts/sv/process";
import {Tip} from "./ts/tip/index";
import {Toolbar} from "./ts/toolbar/index";
import {disableToolbar, hidePanel} from "./ts/toolbar/setToolbar";
import {enableToolbar} from "./ts/toolbar/setToolbar";
import {initUI, UIUnbindListener} from "./ts/ui/initUI";
import {setCodeTheme} from "./ts/ui/setCodeTheme";
import {setContentTheme} from "./ts/ui/setContentTheme";
import {setPreviewMode} from "./ts/ui/setPreviewMode";
import {setTheme} from "./ts/ui/setTheme";
import {Undo} from "./ts/undo/index";
import {Upload} from "./ts/upload/index";
import {addScript, addScriptSync} from "./ts/util/addScript";
import {getSelectText} from "./ts/util/getSelectText";
import {Options} from "./ts/util/Options";
import {processCodeRender} from "./ts/util/processCode";
import {getCursorPosition, getEditorRange, insertHTML} from "./ts/util/selection";
import {afterRenderEvent} from "./ts/wysiwyg/afterRenderEvent";
import {WYSIWYG} from "./ts/wysiwyg/index";
import {input} from "./ts/wysiwyg/input";
import {renderDomByMd} from "./ts/wysiwyg/renderDomByMd";
import {execAfterRender, insertEmptyBlock} from "./ts/util/fixBrowserBehavior";
import {accessLocalStorage} from "./ts/util/compatibility";
class Vditor extends VditorMethod {
public readonly version: string;
public vditor: IVditor;
private isDestroyed = false;
/**
* @param id 要挂载 Vditor 的元素或者元素 ID。
* @param options Vditor 参数
*/
constructor(id: string | HTMLElement, options?: IOptions) {
super();
this.version = VDITOR_VERSION;
if (typeof id === "string") {
if (!options) {
options = {
cache: {
id: `vditor${id}`,
},
};
} else if (!options.cache) {
options.cache = { id: `vditor${id}` };
} else if (!options.cache.id) {
options.cache.id = `vditor${id}`;
}
if (!document.getElementById(id)) {
this.showErrorTip(`Failed to get element by id: ${id}`);
return;
}
id = document.getElementById(id);
}
const getOptions = new Options(options);
const mergedOptions = getOptions.merge();
// 支持自定义国际化
if (!mergedOptions.i18n) {
if (!["de_DE", "en_US", "fr_FR", "pt_BR", "ja_JP", "ko_KR", "ru_RU", "sv_SE", "zh_CN", "zh_TW"].includes(mergedOptions.lang)) {
throw new Error(
"options.lang error, see https://ld246.com/article/1549638745630#options",
);
} else {
const i18nScriptPrefix = "vditorI18nScript";
const i18nScriptID = i18nScriptPrefix + mergedOptions.lang;
document.querySelectorAll(`head script[id^="${i18nScriptPrefix}"]`).forEach((el) => {
if (el.id !== i18nScriptID) {
document.head.removeChild(el);
}
});
addScript(`${mergedOptions.cdn}/dist/js/i18n/${mergedOptions.lang}.js`, i18nScriptID).then(() => {
this.init(id as HTMLElement, mergedOptions);
}).catch(error => {
this.showErrorTip(`GET ${mergedOptions.cdn}/dist/js/i18n/${mergedOptions.lang}.js net::ERR_ABORTED 404 (Not Found)`);
});
}
} else {
window.VditorI18n = mergedOptions.i18n;
this.init(id, mergedOptions);
}
}
private showErrorTip(error: string) {
const tip = new Tip();
document.body.appendChild(tip.element);
tip.show(error, 0);
}
public updateToolbarConfig(options: IToolbarConfig) {
this.vditor.toolbar.updateConfig(this.vditor, options);
}
/** 设置主题 */
public setTheme(
theme: "dark" | "classic",
contentTheme?: string,
codeTheme?: string,
contentThemePath?: string,
) {
this.vditor.options.theme = theme;
setTheme(this.vditor);
if (contentTheme) {
this.vditor.options.preview.theme.current = contentTheme;
setContentTheme(contentTheme, contentThemePath || this.vditor.options.preview.theme.path);
}
if (codeTheme) {
this.vditor.options.preview.hljs.style = codeTheme;
setCodeTheme(codeTheme, this.vditor.options.cdn);
}
}
/** 获取 Markdown 内容 */
public getValue() {
return getMarkdown(this.vditor);
}
/** 获取编辑器当前编辑模式 */
public getCurrentMode() {
return this.vditor.currentMode;
}
/** 聚焦到编辑器 */
public focus() {
if (this.vditor.currentMode === "sv") {
this.vditor.sv.element.focus();
} else if (this.vditor.currentMode === "wysiwyg") {
this.vditor.wysiwyg.element.focus();
} else if (this.vditor.currentMode === "ir") {
this.vditor.ir.element.focus();
}
}
/** 让编辑器失焦 */
public blur() {
if (this.vditor.currentMode === "sv") {
this.vditor.sv.element.blur();
} else if (this.vditor.currentMode === "wysiwyg") {
this.vditor.wysiwyg.element.blur();
} else if (this.vditor.currentMode === "ir") {
this.vditor.ir.element.blur();
}
}
/** 禁用编辑器 */
public disabled() {
hidePanel(this.vditor, ["subToolbar", "hint", "popover"]);
disableToolbar(
this.vditor.toolbar.elements,
Constants.EDIT_TOOLBARS.concat(["undo", "redo", "fullscreen", "edit-mode"]),
);
this.vditor[this.vditor.currentMode].element.setAttribute(
"contenteditable",
"false",
);
}
/** 解除编辑器禁用 */
public enable() {
enableToolbar(
this.vditor.toolbar.elements,
Constants.EDIT_TOOLBARS.concat(["undo", "redo", "fullscreen", "edit-mode"]),
);
this.vditor.undo.resetIcon(this.vditor);
this.vditor[this.vditor.currentMode].element.setAttribute("contenteditable", "true");
}
/** 返回选中的字符串 */
public getSelection() {
if (this.vditor.currentMode === "wysiwyg") {
return getSelectText(this.vditor.wysiwyg.element);
} else if (this.vditor.currentMode === "sv") {
return getSelectText(this.vditor.sv.element);
} else if (this.vditor.currentMode === "ir") {
return getSelectText(this.vditor.ir.element);
}
}
/** 设置预览区域内容 */
public renderPreview(value?: string) {
this.vditor.preview.render(this.vditor, value);
}
/** 获取焦点位置 */
public getCursorPosition() {
return getCursorPosition(this.vditor[this.vditor.currentMode].element);
}
/** 上传是否还在进行中 */
public isUploading() {
return this.vditor.upload.isUploading;
}
/** 清除缓存 */
public clearCache() {
if (this.vditor.options.cache.enable && accessLocalStorage()) {
localStorage.removeItem(this.vditor.options.cache.id);
}
}
/** 禁用缓存 */
public disabledCache() {
this.vditor.options.cache.enable = false;
}
/** 启用缓存 */
public enableCache() {
if (!this.vditor.options.cache.id) {
throw new Error(
"need options.cache.id, see https://ld246.com/article/1549638745630#options",
);
}
this.vditor.options.cache.enable = true;
}
/** HTML 转 md */
public html2md(value: string) {
return this.vditor.lute.HTML2Md(value);
}
/** markdown 转 JSON 输出 */
public exportJSON(value: string) {
return this.vditor.lute.RenderJSON(value);
}
/** 获取 HTML */
public getHTML() {
return getHTML(this.vditor);
}
/** 消息提示。time 为 0 将一直显示 */
public tip(text: string, time?: number) {
this.vditor.tip.show(text, time);
}
/** 设置预览模式 */
public setPreviewMode(mode: "both" | "editor") {
setPreviewMode(mode, this.vditor);
}
/** 删除选中内容 */
public deleteValue() {
if (window.getSelection().isCollapsed) {
return;
}
document.execCommand("delete", false);
}
/** 更新选中内容 */
public updateValue(value: string) {
document.execCommand("insertHTML", false, value);
}
/** 在焦点处插入内容,并默认进行 Markdown 渲染 */
public insertValue(value: string, render = true) {
const range = getEditorRange(this.vditor);
range.collapse(true);
// https://github.com/Vanessa219/vditor/issues/716
// https://github.com/Vanessa219/vditor/issues/917
const tmpElement = document.createElement("template");
tmpElement.innerHTML = value;
range.insertNode(tmpElement.content.cloneNode(true));
range.collapse(false);
if (this.vditor.currentMode === "sv") {
this.vditor.sv.preventInput = true;
if (render) {
inputEvent(this.vditor);
}
} else if (this.vditor.currentMode === "wysiwyg") {
// 由于 https://github.com/Vanessa219/vditor/issues/1566 不能使用 this.vditor.wysiwyg.preventInput = true;
if (render) {
input(this.vditor, getSelection().getRangeAt(0));
}
} else if (this.vditor.currentMode === "ir") {
this.vditor.ir.preventInput = true;
if (render) {
irInput(this.vditor, getSelection().getRangeAt(0), true);
}
}
}
/** 在焦点处插入 Markdown */
public insertMD(md: string) {
// https://github.com/Vanessa219/vditor/issues/1640
if (this.vditor.currentMode === "ir") {
insertHTML(this.vditor.lute.Md2VditorIRDOM(md), this.vditor);
} else if (this.vditor.currentMode === "wysiwyg") {
insertHTML(this.vditor.lute.Md2VditorDOM(md), this.vditor);
} else {
processPaste(this.vditor, md);
}
this.vditor.outline.render(this.vditor);
execAfterRender(this.vditor);
}
/** 设置编辑器内容 */
public setValue(markdown: string, clearStack = false) {
if (this.vditor.currentMode === "sv") {
this.vditor.sv.element.innerHTML = `<div data-block='0'>${this.vditor.lute.SpinVditorSVDOM(markdown)}</div>`;
processSVAfterRender(this.vditor, {
enableAddUndoStack: true,
enableHint: false,
enableInput: false,
});
} else if (this.vditor.currentMode === "wysiwyg") {
renderDomByMd(this.vditor, markdown, {
enableAddUndoStack: true,
enableHint: false,
enableInput: false,
});
} else {
this.vditor.ir.element.innerHTML = this.vditor.lute.Md2VditorIRDOM(markdown);
this.vditor.ir.element
.querySelectorAll(".vditor-ir__preview[data-render='2']")
.forEach((item: HTMLElement) => {
processCodeRender(item, this.vditor);
});
processAfterRender(this.vditor, {
enableAddUndoStack: true,
enableHint: false,
enableInput: false,
});
}
this.vditor.outline.render(this.vditor);
if (!markdown) {
hidePanel(this.vditor, ["emoji", "headings", "submenu", "hint"]);
if (this.vditor.wysiwyg.popover) {
this.vditor.wysiwyg.popover.style.display = "none";
}
this.clearCache();
}
if (clearStack) {
this.clearStack();
}
}
/** 空块 */
public insertEmptyBlock(position: InsertPosition) {
insertEmptyBlock(this.vditor, position);
}
/** 清空 undo & redo 栈 */
public clearStack() {
this.vditor.undo.clearStack(this.vditor);
this.vditor.undo.addToUndoStack(this.vditor);
}
/** 销毁编辑器 */
public destroy() {
this.vditor.element.innerHTML = this.vditor.originalInnerHTML;
this.vditor.element.classList.remove("vditor");
this.vditor.element.removeAttribute("style");
const iconScript = document.getElementById("vditorIconScript");
if (iconScript) {
iconScript.remove();
}
this.clearCache();
UIUnbindListener();
this.vditor.wysiwyg.unbindListener();
this.vditor.options.after = undefined;
this.isDestroyed = true;
}
/** 获取评论 ID */
public getCommentIds() {
if (this.vditor.currentMode !== "wysiwyg") {
return [];
}
return this.vditor.wysiwyg.getComments(this.vditor, true);
}
/** 高亮评论 */
public hlCommentIds(ids: string[]) {
if (this.vditor.currentMode !== "wysiwyg") {
return;
}
const hlItem = (item: Element) => {
item.classList.remove("vditor-comment--hover");
ids.forEach((id) => {
if (item.getAttribute("data-cmtids").indexOf(id) > -1) {
item.classList.add("vditor-comment--hover");
}
});
};
this.vditor.wysiwyg.element
.querySelectorAll(".vditor-comment")
.forEach((item) => {
hlItem(item);
});
if (this.vditor.preview.element.style.display !== "none") {
this.vditor.preview.element
.querySelectorAll(".vditor-comment")
.forEach((item) => {
hlItem(item);
});
}
}
/** 取消评论高亮 */
public unHlCommentIds(ids: string[]) {
if (this.vditor.currentMode !== "wysiwyg") {
return;
}
const unHlItem = (item: Element) => {
ids.forEach((id) => {
if (item.getAttribute("data-cmtids").indexOf(id) > -1) {
item.classList.remove("vditor-comment--hover");
}
});
};
this.vditor.wysiwyg.element
.querySelectorAll(".vditor-comment")
.forEach((item) => {
unHlItem(item);
});
if (this.vditor.preview.element.style.display !== "none") {
this.vditor.preview.element
.querySelectorAll(".vditor-comment")
.forEach((item) => {
unHlItem(item);
});
}
}
/** 删除评论 */
public removeCommentIds(removeIds: string[]) {
if (this.vditor.currentMode !== "wysiwyg") {
return;
}
const removeItem = (item: Element, removeId: string) => {
const ids = item.getAttribute("data-cmtids").split(" ");
ids.find((id, index) => {
if (id === removeId) {
ids.splice(index, 1);
return true;
}
});
if (ids.length === 0) {
item.outerHTML = item.innerHTML;
getEditorRange(this.vditor).collapse(true);
} else {
item.setAttribute("data-cmtids", ids.join(" "));
}
};
removeIds.forEach((removeId) => {
this.vditor.wysiwyg.element
.querySelectorAll(".vditor-comment")
.forEach((item) => {
removeItem(item, removeId);
});
if (this.vditor.preview.element.style.display !== "none") {
this.vditor.preview.element
.querySelectorAll(".vditor-comment")
.forEach((item) => {
removeItem(item, removeId);
});
}
});
afterRenderEvent(this.vditor, {
enableAddUndoStack: true,
enableHint: false,
enableInput: false,
});
}
private init(id: HTMLElement, mergedOptions: IOptions) {
if (this.isDestroyed) {
return;
}
this.vditor = {
currentMode: mergedOptions.mode,
element: id,
hint: new Hint(mergedOptions.hint.extend),
lute: undefined,
options: mergedOptions,
originalInnerHTML: id.innerHTML,
outline: new Outline(window.VditorI18n.outline),
tip: new Tip(),
};
this.vditor.sv = new Editor(this.vditor);
this.vditor.undo = new Undo();
this.vditor.wysiwyg = new WYSIWYG(this.vditor);
this.vditor.ir = new IR(this.vditor);
this.vditor.toolbar = new Toolbar(this.vditor);
if (mergedOptions.resize.enable) {
this.vditor.resize = new Resize(this.vditor);
}
if (this.vditor.toolbar.elements.devtools) {
this.vditor.devtools = new DevTools();
}
if (mergedOptions.upload.url || mergedOptions.upload.handler) {
this.vditor.upload = new Upload();
}
addScript(
mergedOptions._lutePath ||
`${mergedOptions.cdn}/dist/js/lute/lute.min.js`,
"vditorLuteScript",
).then(() => {
this.vditor.lute = setLute({
autoSpace: this.vditor.options.preview.markdown.autoSpace,
gfmAutoLink: this.vditor.options.preview.markdown.gfmAutoLink,
codeBlockPreview: this.vditor.options.preview.markdown
.codeBlockPreview,
emojiSite: this.vditor.options.hint.emojiPath,
emojis: this.vditor.options.hint.emoji,
fixTermTypo: this.vditor.options.preview.markdown.fixTermTypo,
footnotes: this.vditor.options.preview.markdown.footnotes,
headingAnchor: false,
inlineMathDigit: this.vditor.options.preview.math.inlineDigit,
linkBase: this.vditor.options.preview.markdown.linkBase,
linkPrefix: this.vditor.options.preview.markdown.linkPrefix,
listStyle: this.vditor.options.preview.markdown.listStyle,
mark: this.vditor.options.preview.markdown.mark,
mathBlockPreview: this.vditor.options.preview.markdown
.mathBlockPreview,
paragraphBeginningSpace: this.vditor.options.preview.markdown
.paragraphBeginningSpace,
sanitize: this.vditor.options.preview.markdown.sanitize,
toc: this.vditor.options.preview.markdown.toc,
});
this.vditor.preview = new Preview(this.vditor);
initUI(this.vditor);
if (mergedOptions.after) {
mergedOptions.after();
}
if (mergedOptions.icon) {
// 防止初始化 2 个编辑器时加载 2 次
addScriptSync(`${mergedOptions.cdn}/dist/js/icons/${mergedOptions.icon}.js`, "vditorIconScript");
}
});
}
}
export default Vditor;