Skip to content

Commit c90238f

Browse files
authored
feat(editor-md): feat checkbox plugin (DevCloudFE#1725)
1 parent 815b175 commit c90238f

File tree

10 files changed

+194
-8
lines changed

10 files changed

+194
-8
lines changed

packages/devui-vue/devui/editor-md/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { App } from 'vue';
22
import EditorMd from './src/editor-md';
33
import MdRender from './src/components/md-render';
44
export * from './src/editor-md-types';
5+
export * from './src/plugins/checkbox';
56

67
export { EditorMd, MdRender };
78

@@ -12,5 +13,5 @@ export default {
1213
install(app: App): void {
1314
app.component(EditorMd.name, EditorMd);
1415
app.component(MdRender.name, MdRender);
15-
}
16+
},
1617
};

packages/devui-vue/devui/editor-md/src/components/md-render.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useEditorMdRender, useMdRenderWatcher } from '../composables/use-editor
66
export default defineComponent({
77
name: 'DMdRender',
88
props: mdRenderProps,
9-
emits: ['mdRenderChange', 'mdCheckedEvent'],
9+
emits: ['mdRenderChange', 'checkedChange'],
1010
setup(props: MdRenderProps, ctx: SetupContext) {
1111
const { previewRef, renderService, onPreviewClick, setContainerContent } = useEditorMdRender(props, ctx);
1212
useMdRenderWatcher(props, renderService, setContainerContent);

packages/devui-vue/devui/editor-md/src/composables/use-editor-md-render.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function useEditorMdRender(props: MdRenderProps, ctx: SetupContext) {
5454
const result = previewRef.value.querySelectorAll('input');
5555
const index = [...result].filter((el: any) => el.type === 'checkbox').findIndex((item: any) => item === e.target);
5656
const checkContent = setChecked(e.target.checked, index);
57-
ctx.emit('mdCheckedEvent', checkContent);
57+
ctx.emit('checkedChange', checkContent);
5858
}
5959
};
6060

packages/devui-vue/devui/editor-md/src/composables/use-editor-md.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export function useEditorMd(props: EditorMdProps, ctx: SetupContext) {
102102
};
103103

104104
const onChecked = (e: string) => {
105-
ctx.emit('mdCheckedEvent', e);
105+
ctx.emit('checkedChange', e);
106106
};
107107

108108
const scrollToFocusItem = () => {

packages/devui-vue/devui/editor-md/src/editor-md.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ $font-family: helvetica, arial, 'PingFang', 'Microsoft YaHei', 'Hiragino Sans GB
109109
.CodeMirror-empty pre.CodeMirror-placeholder.CodeMirror-line-like {
110110
color: $devui-line;
111111
}
112+
113+
input[type='checkbox'] + label {
114+
margin-left: 6px;
115+
}
112116
}
113117

114118
.dp-editor-md-preview-container.dp-md-view {

packages/devui-vue/devui/editor-md/src/editor-md.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import './editor-md.scss';
1111
export default defineComponent({
1212
name: 'DEditorMd',
1313
props: editorMdProps,
14-
emits: ['update:modelValue', 'mdCheckedEvent', 'selectHint', 'afterEditorInit', 'contentChange', 'previewContentChange', 'imageUpload'],
14+
emits: ['update:modelValue', 'checkedChange', 'selectHint', 'afterEditorInit', 'contentChange', 'previewContentChange', 'imageUpload'],
1515
setup(props: EditorMdProps, ctx: SetupContext) {
1616
const {
1717
mode,
@@ -64,7 +64,8 @@ export default defineComponent({
6464
class={[
6565
'dp-md-container',
6666
{ 'dp-md-readonly': mode.value === 'readonly', 'dp-md-editonly': mode.value === 'editonly', 'dp-md-dark': isDarkMode.value },
67-
]} onPaste={onPaste}>
67+
]}
68+
onPaste={onPaste}>
6869
<div class="dp-md-toolbar-container">
6970
<Toolbar />
7071
</div>
@@ -94,7 +95,7 @@ export default defineComponent({
9495
disable-render
9596
md-plugins={mdPlugins.value}
9697
onMdRenderChange={previewContentChange}
97-
onMdCheckedEvent={onChecked}
98+
onCheckedChange={onChecked}
9899
onScroll={onPreviewScroll}
99100
onMouseover={onPreviewMouseover}
100101
onMouseout={onPreviewMouseout}>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import * as _ from 'lodash';
2+
3+
const checkboxReplace = function (md: any, options: any) {
4+
let lastId: number;
5+
const arrReplaceAt = md.utils.arrayReplaceAt;
6+
lastId = 0;
7+
const defaults = {
8+
divWrap: false,
9+
divClass: 'checkbox',
10+
idPrefix: 'checkbox',
11+
};
12+
options = _.extend(defaults, options);
13+
const pattern = /\[(X|\s|\_|\-)\]\s(.*)/i;
14+
const createTokens = function (checked: any, label: any, Token: any) {
15+
let token;
16+
const nodes = [];
17+
/**
18+
* <div class="checkbox">
19+
*/
20+
if (options.divWrap) {
21+
token = new Token('checkbox_open', 'div', 1);
22+
token.attrs = [['class', 'checkbox']];
23+
nodes.push(token);
24+
}
25+
26+
/**
27+
* <input type="checkbox" id="checkbox{n}" checked="true">
28+
*/
29+
const id = options.idPrefix + lastId;
30+
lastId += 1;
31+
token = new Token('checkbox_input', 'input', 0);
32+
token.attrs = [
33+
['type', 'checkbox'],
34+
['id', id],
35+
];
36+
37+
if (options.disabled) {
38+
token.attrs.push(['disabled', true]);
39+
}
40+
if (checked === true) {
41+
token.attrs.push(['checked', 'true']);
42+
}
43+
nodes.push(token);
44+
45+
/**
46+
* <label for="checkbox{n}">
47+
*/
48+
token = new Token('label_open', 'label', 1);
49+
token.attrs = [['for', id]];
50+
nodes.push(token);
51+
52+
/**
53+
* content of label tag
54+
*/
55+
token = new Token('text', '', 0);
56+
token.content = label;
57+
nodes.push(token);
58+
59+
/**
60+
* closing tags
61+
*/
62+
nodes.push(new Token('label_close', 'label', -1));
63+
if (options.divWrap) {
64+
nodes.push(new Token('checkbox_close', 'div', -1));
65+
}
66+
return nodes;
67+
};
68+
69+
const splitTextToken = function (original: any, Token: any) {
70+
let checked;
71+
const text = original.content;
72+
const matches = text.match(pattern);
73+
74+
if (matches === null) {
75+
return original;
76+
}
77+
checked = false;
78+
const value = matches[1];
79+
const label = matches[2];
80+
if (value === 'X' || value === 'x') {
81+
checked = true;
82+
}
83+
return createTokens(checked, label, Token);
84+
};
85+
86+
return function (state: any) {
87+
let i, j, token, tokens;
88+
const blockTokens = state.tokens;
89+
j = 0;
90+
const l = blockTokens.length;
91+
while (j < l) {
92+
if (blockTokens[j].type !== 'inline') {
93+
j++;
94+
continue;
95+
}
96+
tokens = blockTokens[j].children;
97+
i = tokens.length - 1;
98+
while (i >= 0) {
99+
token = tokens[i];
100+
if (token.type === 'text' && pattern.test(token.content)) {
101+
blockTokens[j].children = tokens = arrReplaceAt(tokens, i, splitTextToken(token, state.Token));
102+
}
103+
i--;
104+
}
105+
j++;
106+
}
107+
};
108+
};
109+
110+
export function checkbox(md: any, options: any) {
111+
md.core.ruler.push('checkbox', checkboxReplace(md, options));
112+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<template>
2+
<d-editor-md
3+
v-model="content"
4+
:md-rules="mdRules"
5+
base-url="https://test-base-url"
6+
@content-change="valueChange"
7+
@checked-change="onCheckedEvent"
8+
/>
9+
<!-- <d-editor-md
10+
v-model="content"
11+
:md-rules="mdRules"
12+
:md-plugins="plugins"
13+
base-url="https://test-base-url"
14+
@content-change="valueChange"
15+
@checked-change="onCheckedEvent"
16+
/> -->
17+
</template>
18+
19+
<script setup lang="ts">
20+
import { reactive, ref } from 'vue';
21+
// import { checkbox } from 'vue-devui/editor-md'; // demo无法进行import,使用时请放开注释
22+
23+
const content = ref('- [x] checked \n - [ ] unchecked');
24+
const mdRules = reactive({
25+
linkify: {
26+
fuzzyLink: false,
27+
},
28+
});
29+
30+
const valueChange = (val) => {
31+
console.log(val);
32+
};
33+
34+
const plugins = reactive([
35+
{
36+
// plugin: checkbox,
37+
opts: {
38+
idPrefix: 'devui',
39+
disable: false,
40+
},
41+
},
42+
]);
43+
44+
const onCheckedEvent = (val) => {
45+
console.log('demo', val);
46+
content.value = val;
47+
};
48+
</script>

packages/devui-vue/docs/components/editor-md/index.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,13 @@ export default defineComponent({
225225

226226
:::
227227

228+
### checkbox 渲染
229+
230+
:::demo 通过配置md-plugins checkbox插件,进行checkbox渲染于checked变更响应。
231+
232+
editor-md/checkbox
233+
:::
234+
228235
### EditorMd 参数
229236

230237
| 参数名 | 类型 | 默认值 | 说明 |
@@ -234,6 +241,7 @@ export default defineComponent({
234241
| base-url | `string` | -- | 设置渲染到 html 时,为相对 url 添加的 baseUrl |
235242
| custom-parse | `(html: string) => string` | -- | 自定义对渲染后的 html 处理,需要接收渲染后的 html,返回自定义处理后的 html |
236243
| md-rules | `object` | {} | 设置 markdown 对字符串的处理方式, 可参考[markdown-it](https://www.npmjs.com/package/markdown-it?activeTab=readme) |
244+
| md-plugins | [MdPlugin[]](#mdplugin) | -- | 设置 markdown-it 插件 |
237245
| mode | `'editonly' \| 'readonly' \| 'normal'` | 'normal' | 只写/只读/双栏显示模式选择,默认 'normal' 双栏模式显示 |
238246
| custom-renderer-rules | [ICustomRenderRule[]](#icustomrenderrule) | [] | 自定义 markdown 对节点的渲染方式,每条规则需要指定对应节点 key,并自定义渲染函数 |
239247
| custom-xss-rules | [ICustomXssRule[]](#icustomxssrule) | [] | 自定义 xss 对某种 tag 的过滤方式,每条规则需要指定 tag, 并给出需要加入白名单的属性数组 |
@@ -249,6 +257,7 @@ export default defineComponent({
249257
| content-change | `Function(content: string)` | 编辑器内容改变事件,返回当前内容 | |
250258
| preview-content-change | `Function()` | 预览内容改变时触发 | |
251259
| image-upload | `Function({file, callback})` | 打开图片上传开关后,图片上传事件回调,返回文件内容与 callback 函数 | |
260+
| checked-change | `Function(content: string)` | plugins添加checkbox后,预览checkbox checked状态改变回调 | |
252261

253262
### MdRender 参数
254263

@@ -265,6 +274,7 @@ export default defineComponent({
265274
| 事件名 | 回调参数 | 说明 | 跳转 Demo |
266275
| :--------------- | :----------------- | :----------------------------------------- | :-------- |
267276
| md-render-change | `Function(string)` | 内容改变时触发,返回对应 html 渲染结果字段 | |
277+
| checked-change | `Function(content: string)` | plugins添加checkbox后,预览checkbox checked状态改变回调 | |
268278

269279
#### ICustomRenderRule
270280

@@ -283,3 +293,12 @@ interface ICustomXssRule {
283293
value: string[];
284294
}
285295
```
296+
297+
#### MdPlugin
298+
299+
```ts
300+
export interface MdPlugin {
301+
plugin: any;
302+
opts?: Object;
303+
}
304+
```

packages/devui-vue/docs/vite.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export default defineConfig({
99
alias: [
1010
{ find: '@devui/theme', replacement: resolve(__dirname, '../../devui-theme/src') },
1111
{ find: '@devui/shared/components', replacement: resolve(__dirname, '../devui') },
12-
{ find: '@devui', replacement: resolve(__dirname, '../devui') }
12+
{ find: '@devui', replacement: resolve(__dirname, '../devui') },
13+
{ find: 'vue-devui', replacement: resolve(__dirname, '../devui') },
1314
],
1415
},
1516
plugins: [vueJsx({}), svgLoader(), MdTransformer()],

0 commit comments

Comments
 (0)