Skip to content

Commit 25d91ed

Browse files
barryhubarryhu
authored andcommitted
Merge branch 'dev'
2 parents 7bc3350 + 37f6ec1 commit 25d91ed

File tree

11 files changed

+126
-8
lines changed

11 files changed

+126
-8
lines changed

examples/head_num.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,12 @@
108108
<script>
109109
var cherry = new Cherry({
110110
id: 'markdown',
111-
previewer: {
112-
// head-num 表示启用标题自动序号
113-
className: 'cherry-markdown head-num'
111+
engine: {
112+
syntax: {
113+
header: {
114+
anchorStyle: 'autonumber',
115+
}
116+
}
114117
},
115118
value: document.getElementById("demo-val").value,
116119
});

src/Cherry.config.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ const defaultConfig = {
8989
customRenderer: {
9090
// 自定义语法渲染器
9191
},
92+
/**
93+
* indentedCodeBlock是缩进代码块是否启用的开关
94+
*
95+
* 在6.X之前的版本中默认不支持该语法。
96+
* 因为cherry的开发团队认为该语法太丑了(容易误触)
97+
* 开发团队希望用```代码块语法来彻底取代该语法
98+
* 但在后续的沟通中,开发团队发现在某些场景下该语法有更好的显示效果
99+
* 因此开发团队在6.X版本中才引入了该语法
100+
* 已经引用6.x以下版本的业务如果想做到用户无感知升级,可以去掉该语法:
101+
* indentedCodeBlock:false
102+
*/
103+
indentedCodeBlock: true,
92104
},
93105
emoji: {
94106
useUnicode: true, // 是否使用unicode进行渲染
@@ -112,6 +124,15 @@ const defaultConfig = {
112124
/** 默认只渲染一个目录 */
113125
allowMultiToc: false,
114126
},
127+
header: {
128+
/**
129+
* 标题的样式:
130+
* - default 默认样式,标题前面有锚点
131+
* - autonumber 标题前面有自增序号锚点
132+
* - none 标题没有锚点
133+
*/
134+
anchorStyle: 'default',
135+
},
115136
},
116137
},
117138
editor: {
@@ -123,6 +144,8 @@ const defaultConfig = {
123144
// editOnly: 纯编辑模式(没有预览,可通过toolbar切换成双栏或预览模式)
124145
// previewOnly: 预览模式(没有编辑框,toolbar只显示“返回编辑”按钮,可通过toolbar切换成编辑模式)
125146
defaultModel: 'edit&preview',
147+
// 粘贴时是否自动将html转成markdown
148+
convertWhenPaste: true,
126149
},
127150
toolbars: {
128151
theme: 'dark', // light or dark

src/Cherry.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,11 @@ export default class Cherry extends CherryStatic {
421421
createPreviewer() {
422422
/** @type {HTMLDivElement} */
423423
let previewer;
424+
const anchorStyle =
425+
(this.options.engine.syntax.header && this.options.engine.syntax.header.anchorStyle) || 'default';
426+
const autonumberClass = anchorStyle === 'autonumber' ? ' head-num' : '';
424427
const { className, dom } = this.options.previewer;
425-
const previewerClassName = `cherry-previewer ${className}`;
428+
const previewerClassName = ['cherry-previewer', className || '', autonumberClass].join(' ');
426429
if (dom) {
427430
previewer = dom;
428431
previewer.className += ` ${previewerClassName}`;

src/Editor.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ export default class Editor {
5353
editorDom: document.createElement('div'),
5454
wrapperDom: null,
5555
autoScrollByCursor: true,
56+
convertWhenPaste: true,
5657
codemirror: {
5758
lineNumbers: false, // 显示行数
5859
cursorHeight: 0.85, // 光标高度,0.85好看一些
5960
indentUnit: 4, // 缩进单位为4
61+
tabSize: 4, // 一个tab转换成的空格数量
6062
// styleActiveLine: false, // 当前行背景高亮
6163
// matchBrackets: true, // 括号匹配
6264
mode: 'markdown',
@@ -135,7 +137,7 @@ export default class Editor {
135137
// 复制html转换markdown
136138
const htmlText = clipboardData.getData('text/plain');
137139
let html = clipboardData.getData('Text/Html');
138-
if (!html) {
140+
if (!html || !this.options.convertWhenPaste) {
139141
return true;
140142
}
141143
const test = html.replace(/<(html|head|body|!)/g, '');

src/core/hooks/CodeBlock.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export default class CodeBlock extends ParagraphBase {
3535
this.customParser = {};
3636
this.wrap = config.wrap; // 超出是否换行
3737
this.lineNumber = config.lineNumber; // 是否显示行号
38+
this.indentedCodeBlock = typeof config.indentedCodeBlock === 'undefined' ? true : config.indentedCodeBlock; // 是否支持缩进代码块
3839
if (config && config.customRenderer) {
3940
this.customLang = Object.keys(config.customRenderer).map((lang) => lang.toLowerCase());
4041
this.customParser = { ...config.customRenderer };
@@ -196,9 +197,66 @@ export default class CodeBlock extends ParagraphBase {
196197
return cacheCode;
197198
}
198199

200+
/**
201+
* 获取缩进代码块语法的正则
202+
*/
203+
$getIndentedCodeReg() {
204+
const ret = {
205+
begin: '(?:^|\\n\\s*\\n)(?: {4}|\\t)',
206+
end: '(?=$|\\n( {0,3}[^ \\t\\n]|\\n[^ \\t\\n]))',
207+
content: '([\\s\\S]+?)',
208+
};
209+
return new RegExp(ret.begin + ret.content + ret.end, 'g');
210+
}
211+
212+
/**
213+
* 生成缩进代码块(没有行号、没有代码高亮)
214+
*/
215+
$getIndentCodeBlock(str) {
216+
if (!this.indentedCodeBlock) {
217+
return str;
218+
}
219+
return this.$recoverCodeInIndent(str).replace(this.$getIndentedCodeReg(), (match, code) => {
220+
const lineCount = (match.match(/\n/g) || []).length - 1;
221+
const sign = this.$engine.md5(match);
222+
const html = `<pre data-sign="${sign}" data-lines="${lineCount}">${escapeHTMLSpecialChar(
223+
code.replace(/\n( {4}|\t)/g, '\n'),
224+
)}</pre>`;
225+
// return this.getCacheWithSpace(this.pushCache(html), match, true);
226+
return this.pushCache(html, sign, lineCount);
227+
});
228+
}
229+
230+
/**
231+
* 预处理缩进代码块,将缩进代码块里的高亮代码块和行内代码进行占位处理
232+
*/
233+
$replaceCodeInIndent(str) {
234+
if (!this.indentedCodeBlock) {
235+
return str;
236+
}
237+
return str.replace(this.$getIndentedCodeReg(), (match) => {
238+
return match.replace(/`/g, '~~~IndentCode');
239+
});
240+
}
241+
242+
/**
243+
* 恢复预处理的内容
244+
*/
245+
$recoverCodeInIndent(str) {
246+
if (!this.indentedCodeBlock) {
247+
return str;
248+
}
249+
return str.replace(this.$getIndentedCodeReg(), (match) => {
250+
return match.replace(/~~~IndentCode/g, '`');
251+
});
252+
}
253+
199254
beforeMakeHtml(str, sentenceMakeFunc, markdownParams) {
200255
let $str = str;
201256

257+
// 预处理缩进代码块
258+
$str = this.$replaceCodeInIndent($str);
259+
202260
$str = $str.replace(this.RULE.reg, (match, leadingContent, lang, code) => {
203261
let $code = code;
204262
const { sign, lines } = this.computeLines(match, leadingContent, code);
@@ -262,6 +320,10 @@ export default class CodeBlock extends ParagraphBase {
262320
});
263321
$str = $str.replace(/~~not~inlineCode/g, '\\`');
264322
}
323+
324+
// 处理缩进代码块
325+
$str = this.$getIndentCodeBlock($str);
326+
265327
return $str;
266328
}
267329

src/core/hooks/Header.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default class Header extends ParagraphBase {
3232
this.RULE = this.rule();
3333
this.headerIDCache = [];
3434
this.headerIDCounter = {};
35+
this.config = config || {};
3536
// TODO: AllowCustomID
3637
}
3738

@@ -113,13 +114,21 @@ export default class Header extends ParagraphBase {
113114
const sign = this.$engine.md5(`${level}-${processedText.sign}-${anchorID}-${dataLines}`);
114115
const result = [
115116
`<h${level} id="${anchorID}" data-sign="${sign}" data-lines="${dataLines}">`,
116-
`<a class="anchor" href="#${anchorID}"></a>`,
117+
this.$getAnchor(anchorID),
117118
`${html}`,
118119
`</h${level}>`,
119120
].join('');
120121
return { html: result, sign: `${sign}` };
121122
}
122123

124+
$getAnchor(anchorID) {
125+
const anchorStyle = this.config.anchorStyle || 'default';
126+
if (anchorStyle === 'none') {
127+
return '';
128+
}
129+
return `<a class="anchor" href="#${anchorID}"></a>`;
130+
}
131+
123132
beforeMakeHtml(str) {
124133
let $str = str;
125134
// atx 优先

src/sass/cherry.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@
469469
font-size: 0.9em;
470470
}
471471

472-
.cm-s-default .cm-whitespace {
472+
.cm-s-default .cm-whitespace, .cm-tab {
473473
font-family: $monospaceFont;
474474
font-size: 0.9em;
475475
}
@@ -629,9 +629,9 @@
629629
}
630630

631631
.cherry-switch-paste {
632-
width: 224px;
633632
margin-left: -112px;
634633
left: 50%;
634+
box-sizing: content-box;
635635
.switch-btn--bg {
636636
transition: all 0.3s;
637637
position: absolute;

src/sass/markdown.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,15 @@
190190
margin: 0 0 $elementGap;
191191
}
192192

193+
pre {
194+
padding: 16px;
195+
overflow: auto;
196+
font-size: 85%;
197+
line-height: 1.45;
198+
background-color: #f6f8fa;
199+
border-radius: 6px;
200+
}
201+
193202
// 适配历史文章样式
194203
@import 'prettyprint/prettyprint.scss';
195204

src/toolbars/PreviewerBubble.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import imgSizeHander from '@/utils/imgSizeHander';
18+
import Event from '@/Event';
1819
/**
1920
* 预览区域的响应式工具栏
2021
*/
@@ -59,6 +60,9 @@ export default class PreviewerBubble {
5960
this.previewerDom.addEventListener('scroll', (event) => {
6061
this.bubbleHandler.emit('scroll', event);
6162
});
63+
Event.on(this.previewer.instanceId, Event.Events.previewerClose, () => {
64+
this.$removeAllPreviewerBubbles();
65+
});
6266
this.previewer.options.afterUpdateCallBack.push(() => {
6367
this.bubbleHandler.emit('previewUpdate', () => {
6468
this.$removeAllPreviewerBubbles();

types/cherry.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ export interface CherryEditorOptions {
101101
height?: string;
102102
/** 编辑器初始化后的模式 */
103103
defaultModel?: EditorMode;
104+
/** 粘贴时是否自动将html转成markdown */
105+
convertWhenPaste?: boolean;
104106
}
105107

106108
export type CherryLifecycle = (text: string, html: string) => void;

0 commit comments

Comments
 (0)