Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge v3.3.9 #6

Merged
merged 17 commits into from Jul 20, 2020
Merged
21 changes: 16 additions & 5 deletions CHANGELOG.md
Expand Up @@ -66,11 +66,22 @@
* [open issues](https://github.com/Vanessa219/vditor/issues)
* [346](https://github.com/Vanessa219/vditor/issues/346) 内容主题推荐(长期有效) `改进功能`

### v3.3.9 / 2020-07-xx


### v3.3.8 / 2020-07-17

### v3.3.10 / 2020-07-xx

* [594](https://github.com/Vanessa219/vditor/issues/594) Blockquote, ordered list and code block `改进功能`
* [593](https://github.com/Vanessa219/vditor/issues/593) IR 模式焦点离开时的渲染问题 `改进功能`
* [604](https://github.com/Vanessa219/vditor/issues/604) 站外图片抓取请求的 Content-Type 是 text/plain `改进功能`
* [597](https://github.com/Vanessa219/vditor/issues/597) one more backspace before delete lists `修复缺陷`
* [599](https://github.com/Vanessa219/vditor/issues/599) Ordered list(minor bug) `修复缺陷`

### v3.3.9 / 2020-07-18

* [591](https://github.com/Vanessa219/vditor/issues/591) 粘贴有可能不渲染本行内容 `改进功能`
* [586](https://github.com/Vanessa219/vditor/issues/586) code block inside ordered list `修复缺陷`
* [585](https://github.com/Vanessa219/vditor/issues/585) [suggestion] when enter after code block inside blockqoute `改进功能`
* [584](https://github.com/Vanessa219/vditor/issues/584) Can not delete Code block `修复缺陷`
* [588](https://github.com/Vanessa219/vditor/issues/588) 第五版 SV 模式 bug 记录 `修复缺陷`
* [259](https://github.com/Vanessa219/vditor/issues/259) 分屏预览模式列表项自动完成 `引入特性`
* [580](https://github.com/Vanessa219/vditor/issues/580) 重构 SV 模式 DOM `开发重构`
* [567](https://github.com/Vanessa219/vditor/issues/567) SV 模式块引用嵌套列表、代码块问题 `修复缺陷`
* [563](https://github.com/Vanessa219/vditor/issues/563) SV 模式列表项下的代码块问题 `修复缺陷`
Expand Down
30 changes: 26 additions & 4 deletions README.md
Expand Up @@ -117,6 +117,7 @@ Vditor 在这些方面做了努力,希望能为现代化的通用 Markdown 编
* [📕 链滴笔记](https://github.com/88250/liandi) 一款桌面端笔记应用,支持 Windows、Mac 和 Linux
* [🌟 Starfire](https://github.com/88250/starfire) 一个分布式的内容分享讨论社区,星星之火可以燎原
* [📝 Arya](https://github.com/nicejade/markdown-online-editor) 基于 Vue、Vditor,所构建的在线 Markdown 编辑器
* [更多案例](https://github.com/Vanessa219/vditor/network/dependents?package_id=UGFja2FnZS0zMTY2Mzg4MzE%3D)

## 🛠️ 使用文档

Expand Down Expand Up @@ -155,10 +156,31 @@ const vditor = new Vditor(id, {options...})

### 主题

* 支持黑白两套主题:classic/dark
* 参考现有样式后使用自己开发的 scss/css 进行样式的完全自定制
#### 编辑器主题

编辑器所展现的外观。内置classic,dark 2 套主题。

* 编辑器初始化时可通过 `options.theme` 设置内置主题
* 初始化完成后可通过 `setTheme` 更新编辑器主题
* 可通过修改 [index.scss](https://github.com/Vanessa219/vditor/blob/master/src/assets/scss/index.scss) 中的变量对主题颜色进行定制
* 在内容显示元素上添加 `class="vditor-reset"` (经典主题) 或 `class="vditor-reset vditor-reset--dark"`(黑色主题) 属性可对内容进行更为友好的展示
* 可参考现有结构和类名在原有基础上进行修改

#### 内容主题

Markdown 输出的 HTML 所展现的外观。内置 light,dark,wechat 3 套主题。支持内容主题扩展接口。

* 需在显示元素上添加 `class="vditor-reset"`
* 编辑器初始化时可通过 `options.preview.theme` 设置内置或自己开发的主题列表
* 内容渲染初始化时可通过 `IPreviewOptions.theme` 设置内置或自己开发的主题
* 初始化完成后可通过 `setTheme` 或 `setContentTheme` 更新内容主题

#### 代码主题

代码块所展现的外观。内置 github 等 36 套主题。

* 编辑器初始化时可通过 `options.preview.hljs` 对代码块样式、行号、是否启用进行设置
* 内容渲染初始化时可通过 `IPreviewOptions.hljs` 对代码块样式、行号、是否启用进行设置
* 初始化完成后可通过 `setTheme` 或 `setCodeTheme` 更新代码主题

### API

Expand Down Expand Up @@ -442,7 +464,7 @@ if (xhr.status === 200) {
| html2md(value: string) | HTML 转 md |
| tip(text: string, time: number) | 消息提示。time 为 0 将一直显示 |
| setPreviewMode(mode: "both" \| "editor") | 设置预览模式 |
| setTheme(theme: "dark" | "classic", contentTheme?: string, codeTheme?: string, contentThemePath?: string) | 设置主题、内容主题及代码块风格 |
| setTheme(theme: "dark" \| "classic", contentTheme?: string, codeTheme?: string, contentThemePath?: string) | 设置主题、内容主题及代码块风格 |
| getCurrentMode(): string | 获取编辑器当前编辑模式 |
| destroy() |销毁编辑器|

Expand Down
15 changes: 10 additions & 5 deletions demo/index.js
Expand Up @@ -50,6 +50,8 @@ if (window.innerWidth < 768) {
}

window.vditor = new Vditor('vditor', {
// _lutePath: `http://192.168.0.107:9090/lute.min.js?${new Date().getTime()}`,
_lutePath: 'src/js/lute/lute.min.js',
toolbar,
mode: 'sv',
height: window.innerHeight + 100,
Expand Down Expand Up @@ -77,11 +79,14 @@ window.vditor = new Vditor('vditor', {
'j': 'https://unpkg.com/vditor@1.3.1/dist/images/emoji/j.png',
},
at: (key) => {
return [
{
value: '@Vanessa',
html: '<img src="https://avatars0.githubusercontent.com/u/970828?s=60&v=4"/> Vanessa',
}]
if ('vanessa'.indexOf(key.toLocaleLowerCase()) > -1) {
return [
{
value: '@Vanessa',
html: '<img src="https://avatars0.githubusercontent.com/u/970828?s=60&v=4"/> Vanessa',
}]
}
return []
},
},
tab: '\t',
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "vditor",
"version": "3.3.8",
"version": "3.3.9",
"description": "♏ An In-browser Markdown editor, support WYSIWYG, Instant Rendering (Typora-like) and Split View modes.",
"author": "Vanessa <v@b3log.org> (http://vanessa.b3log.org)",
"homepage": "https://vditor.b3log.org",
Expand Down
54 changes: 26 additions & 28 deletions src/index.ts
Expand Up @@ -99,36 +99,34 @@ class Vditor extends VditorMethod {
this.vditor.upload = new Upload();
}

// const lutePath = `http://192.168.0.107:9090/lute.min.js?${new Date().getTime()}`;
// const lutePath = "src/js/lute/lute.min.js";
const lutePath = `${mergedOptions.cdn}/dist/js/lute/lute.min.js`;
addScript(lutePath, "vditorLuteScript").then(() => {
this.vditor.lute = setLute({
autoSpace: this.vditor.options.preview.markdown.autoSpace,
chinesePunct: this.vditor.options.preview.markdown.chinesePunct,
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,
listStyle: this.vditor.options.preview.markdown.listStyle,
paragraphBeginningSpace: this.vditor.options.preview.markdown.paragraphBeginningSpace,
sanitize: this.vditor.options.preview.markdown.sanitize,
setext: this.vditor.options.preview.markdown.setext,
toc: this.vditor.options.preview.markdown.toc,
});
addScript(options._lutePath || `${mergedOptions.cdn}/dist/js/lute/lute.min.js`, "vditorLuteScript")
.then(() => {
this.vditor.lute = setLute({
autoSpace: this.vditor.options.preview.markdown.autoSpace,
chinesePunct: this.vditor.options.preview.markdown.chinesePunct,
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,
listStyle: this.vditor.options.preview.markdown.listStyle,
paragraphBeginningSpace: this.vditor.options.preview.markdown.paragraphBeginningSpace,
sanitize: this.vditor.options.preview.markdown.sanitize,
setext: this.vditor.options.preview.markdown.setext,
toc: this.vditor.options.preview.markdown.toc,
});

this.vditor.preview = new Preview(this.vditor);
this.vditor.preview = new Preview(this.vditor);

initUI(this.vditor);
initUI(this.vditor);

if (mergedOptions.after) {
mergedOptions.after();
}
});
if (mergedOptions.after) {
mergedOptions.after();
}
});
}

/** 设置主题 */
Expand Down Expand Up @@ -240,7 +238,7 @@ class Vditor extends VditorMethod {

/** HTML 转 md */
public html2md(value: string) {
return this.vditor.lute.HTML2Md(value);
return this.vditor.lute.HTML2Md(value);
}

/** 获取 HTML */
Expand Down
6 changes: 3 additions & 3 deletions src/js/lute/lute.min.js

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/ts/sv/index.ts
@@ -1,5 +1,5 @@
import {isCtrl, isFirefox} from "../util/compatibility";
import {blurEvent, dropEvent, focusEvent, hotkeyEvent, selectEvent} from "../util/editorCommonEvent";
import {blurEvent, dropEvent, focusEvent, hotkeyEvent, scrollCenter, selectEvent} from "../util/editorCommonEvent";
import {paste} from "../util/fixBrowserBehavior";
import {getSelectText} from "../util/getSelectText";
import {inputEvent} from "./inputEvent";
Expand Down Expand Up @@ -94,6 +94,9 @@ class Editor {
vditor.sv.element.innerHTML = "";
return;
}
if (event.key === "Enter") {
scrollCenter(vditor);
}
});
}
}
Expand Down
40 changes: 35 additions & 5 deletions src/ts/sv/inputEvent.ts
@@ -1,7 +1,7 @@
import {scrollCenter} from "../util/editorCommonEvent";
import {hasClosestByAttribute} from "../util/hasClosest";
import {getSelectPosition, setRangeByWbr} from "../util/selection";
import {processAfterRender, processSpinVditorSVDOM} from "./process";
import {getSideByType, processAfterRender, processSpinVditorSVDOM} from "./process";

export const inputEvent = (vditor: IVditor, event?: InputEvent) => {
const range = getSelection().getRangeAt(0).cloneRange();
Expand Down Expand Up @@ -32,15 +32,45 @@ export const inputEvent = (vditor: IVditor, event?: InputEvent) => {
processAfterRender(vditor);
return;
}
// https://github.com/Vanessa219/vditor/issues/584
if (event.inputType === "deleteContentBackward") {
const codeBlockMarkerElement =
hasClosestByAttribute(startContainer, "data-type", "code-block-open-marker") ||
hasClosestByAttribute(startContainer, "data-type", "code-block-close-marker");
if (codeBlockMarkerElement) {
if (codeBlockMarkerElement.getAttribute("data-type") === "code-block-close-marker") {
const openMarkerElement = getSideByType(startContainer, "code-block-open-marker");
if (openMarkerElement) {
openMarkerElement.textContent = codeBlockMarkerElement.textContent;
processAfterRender(vditor);
return;
}
}
if (codeBlockMarkerElement.getAttribute("data-type") === "code-block-open-marker") {
const openMarkerElement = getSideByType(startContainer, "code-block-close-marker", false);
if (openMarkerElement) {
openMarkerElement.textContent = codeBlockMarkerElement.textContent;
processAfterRender(vditor);
return;
}
}
}
blockElement.querySelectorAll('[data-type="code-block-open-marker"]').forEach((item: HTMLElement) => {
if (item.textContent.length === 1) {
item.remove();
}
});
blockElement.querySelectorAll('[data-type="code-block-close-marker"]').forEach((item: HTMLElement) => {
if (item.textContent.length === 1) {
item.remove();
}
});
}
// 删除或空格不解析,否则会 format 回去
if ((event.data === " " || event.inputType === "deleteContentBackward") &&
(hasClosestByAttribute(startContainer, "data-type", "padding") // 场景:b 前进行删除 [> 1. a\n> b]
|| hasClosestByAttribute(startContainer, "data-type", "li-marker") // 场景:删除最后一个字符 [* 1\n* ]
|| hasClosestByAttribute(startContainer, "data-type", "task-marker") // 场景:删除最后一个字符 [* [ ] ]
// 场景:删除前面的飘号 [```\n``` ]
|| hasClosestByAttribute(startContainer, "data-type", "code-block-open-marker")
// 场景:删除后面的飘号 [```\n``` ]
|| hasClosestByAttribute(startContainer, "data-type", "code-block-close-marker")
|| hasClosestByAttribute(startContainer, "data-type", "blockquote-marker") // 场景:删除最后一个字符 [> ]
)) {
processAfterRender(vditor);
Expand Down
80 changes: 65 additions & 15 deletions src/ts/sv/process.ts
@@ -1,21 +1,52 @@
import {getMarkdown} from "../markdown/getMarkdown";
import {accessLocalStorage} from "../util/compatibility";
import {hasClosestBlock} from "../util/hasClosest";
import {scrollCenter} from "../util/editorCommonEvent";
import {hasClosestBlock, hasClosestByAttribute} from "../util/hasClosest";
import {hasClosestByTag} from "../util/hasClosestByHeadings";
import {log} from "../util/log";
import {getEditorRange, setRangeByWbr} from "../util/selection";
import {inputEvent} from "./inputEvent";

const getPreviousNL = (spanElement: Element) => {
let previousElement = spanElement;
while (previousElement && previousElement.getAttribute("data-type") !== "newline") {
previousElement = previousElement.previousElementSibling;
export const processPaste = (vditor: IVditor, text: string) => {
const range = getEditorRange(vditor.sv.element);
range.extractContents();
range.insertNode(document.createTextNode(Lute.Caret));
range.insertNode(document.createTextNode(text));
let blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
if (!blockElement) {
blockElement = vditor.sv.element;
}
const html = "<div data-block='0'>" +
vditor.lute.Md2VditorSVDOM(blockElement.textContent).replace(/<span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span><span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span></g, '<span data-type="newline"><br /><span style="display: none">\n</span></span><span data-type="newline"><br /><span style="display: none">\n</span></span></div><div data-block="0"><') +
"</div>";
if (blockElement.isEqualNode(vditor.sv.element)) {
blockElement.innerHTML = html;
} else {
blockElement.outerHTML = html;
}
if (previousElement && previousElement.getAttribute("data-type") === "newline") {
return previousElement;
setRangeByWbr(vditor.sv.element, range);

scrollCenter(vditor);
};

export const getSideByType = (spanNode: Node, type: string, isPrevious = true) => {
let sideElement = spanNode as Element;
if (sideElement.nodeType === 3) {
sideElement = sideElement.parentElement;
}
while (sideElement) {
if (sideElement.getAttribute("data-type") === type) {
return sideElement;
}
if (isPrevious) {
sideElement = sideElement.previousElementSibling;
} else {
sideElement = sideElement.nextElementSibling;
}
}
return false;
};

export const processSpinVditorSVDOM = (html: string, vditor: IVditor) => {
log("SpinVditorSVDOM", html, "argument", vditor.options.debugger);
html = "<div data-block='0'>" +
Expand All @@ -25,14 +56,33 @@ export const processSpinVditorSVDOM = (html: string, vditor: IVditor) => {
return html;
};

export const processPreviousMarkers = (textElement: HTMLElement) => {
let previousElement = textElement.previousElementSibling;
export const processPreviousMarkers = (spanElement: HTMLElement) => {
const spanType = spanElement.getAttribute("data-type");
let previousElement = spanElement.previousElementSibling;
let markerText = "";
while (previousElement && (previousElement.getAttribute("data-type") === "li-marker" ||
previousElement.getAttribute("data-type") === "blockquote-marker" ||
previousElement.getAttribute("data-type") === "task-marker" ||
previousElement.getAttribute("data-type") === "padding")) {
markerText = previousElement.textContent + markerText;
let hasNL = false;
while (previousElement && !hasNL) {
const previousType = previousElement.getAttribute("data-type");
if (previousType === "li-marker" || previousType === "blockquote-marker" || previousType === "task-marker" ||
previousType === "padding") {
if (previousType === "li-marker" &&
(spanType === "code-block-open-marker" || spanType === "code-block-info")) {
// https://github.com/Vanessa219/vditor/issues/586
markerText = previousElement.textContent.replace(/\S/g, " ") + markerText;
} else if (spanType === "code-block-close-marker" &&
previousElement.nextElementSibling.isSameNode(spanElement)) {
// https://github.com/Vanessa219/vditor/issues/594
const openMarker = getSideByType(spanElement, "code-block-open-marker");
if (openMarker && openMarker.previousElementSibling) {
previousElement = openMarker.previousElementSibling;
markerText = previousElement.textContent + markerText;
}
} else {
markerText = previousElement.textContent + markerText;
}
} else if (previousType === "newline") {
hasNL = true;
}
previousElement = previousElement.previousElementSibling;
}
return markerText;
Expand Down Expand Up @@ -137,7 +187,7 @@ export const processToolbar = (vditor: IVditor, actionBtn: Element, prefix: stri
} else if (commandName === "quote") {
marker = "> ";
}
const newLine = getPreviousNL(spanElement);
const newLine = getSideByType(spanElement, "newline");
if (newLine) {
newLine.insertAdjacentText("afterend", marker);
} else {
Expand Down