yuque-rich-text 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -19
- package/demo/App.vue +2 -1
- package/index.html +5 -5
- package/package.json +1 -1
- package/public/Images/yuque-rich-text.gif +0 -0
- package/src/components/lake-rich/lake-rich.ts +115 -104
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -8,15 +8,99 @@ npm i yuque-rich-text
|
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
## 截图
|
|
11
|
-

|
|
12
|
+
|
|
13
|
+
### 引入相关样式
|
|
14
|
+
|
|
15
|
+
`head`标签中加入
|
|
16
|
+
|
|
17
|
+
``` html
|
|
18
|
+
<head>
|
|
19
|
+
<link rel="stylesheet" type="text/css" href="https://gw.alipayobjects.com/render/p/yuyan_npm/@alipay_lakex-doc/1.71.0/umd/doc.css"/>
|
|
20
|
+
<link rel="stylesheet" type="text/css" href="https://gw.alipayobjects.com/os/lib/antd/4.24.13/dist/antd.css"/>
|
|
21
|
+
</head>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`body`标签内的最后一行加入
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<body>
|
|
28
|
+
<script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
|
|
29
|
+
<script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
|
|
30
|
+
<script src="https://gw.alipayobjects.com/render/p/yuyan_v/180020010000005484/7.1.4/CodeMirror.js"></script>
|
|
31
|
+
<script src="https://ur.alipay.com/tracert_a385.js"></script>
|
|
32
|
+
<script src="https://mdn.alipayobjects.com/design_kitchencore/afts/file/ANSZQ7GHQPMAAAAAAAAAAAAADhulAQBr"></script>
|
|
33
|
+
<script src="https://gw.alipayobjects.com/render/p/yuyan_npm/@alipay_lakex-doc/1.71.0/umd/doc.umd.js"></script>
|
|
34
|
+
</body>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 编辑使用案例
|
|
38
|
+
> 注意不可在onChange事件中修改value的值,否则会进入无限递归。
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<YuqueRichText ref="editorRef" :value="content" @onChange="editChange" @onLoad="load"/>
|
|
44
|
+
<button @click="appendText">追加内容</button>
|
|
45
|
+
<button @click="getContent">获取内容</button>
|
|
46
|
+
<button @click="setText">更新内容</button>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import { ref } from 'vue'
|
|
51
|
+
import { YuqueRichText } from 'yuque-rich-text'
|
|
52
|
+
import type { IEditorRef } from "yuque-rich-text";
|
|
53
|
+
|
|
54
|
+
const editorRef = ref<IEditorRef>()
|
|
55
|
+
const content = ref('初始内容')
|
|
56
|
+
|
|
57
|
+
const appendText = () => {
|
|
58
|
+
if (editorRef.value) {
|
|
59
|
+
editorRef.value.appendContent('<p>这是追加的内容</p>', true)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const setText = () => {
|
|
63
|
+
if (editorRef.value) {
|
|
64
|
+
editorRef.value.setContent('<p>更新的内容</p>')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const getContent = () => {
|
|
69
|
+
if (editorRef.value) {
|
|
70
|
+
const html = editorRef.value.getContent('text/html')
|
|
71
|
+
alert('当前内容:' + html)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const load = ()=>{
|
|
76
|
+
console.log("编辑器加载成功...");
|
|
77
|
+
// 此时可进行增删改查操作
|
|
78
|
+
editorRef.value?.appendContent('<p>这是追加的内容</p><br>', true)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const editChange = (e : string)=>{
|
|
82
|
+
console.log("编辑器内容发生变化:", e);
|
|
83
|
+
}
|
|
84
|
+
</script>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 预览模式
|
|
88
|
+
|
|
89
|
+
> 使用也非常简单, 将组建的`isview`属性改为`true`即可。
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<template>
|
|
93
|
+
<YuqueRichText :isview="true" :value="content" />
|
|
94
|
+
</template>
|
|
95
|
+
```
|
|
12
96
|
|
|
13
97
|
### Props
|
|
14
98
|
|
|
15
99
|
```js
|
|
16
100
|
export interface EditorProps {
|
|
17
101
|
value: string; // 传递给组件的内容
|
|
18
|
-
children?: any;
|
|
19
|
-
isview?: boolean; //
|
|
102
|
+
children?: any; // 暂无实现
|
|
103
|
+
isview?: boolean; // 为true该组件是只读的,为空或false则是编辑模式
|
|
20
104
|
uploadImage?: (params: { data: string | File }) => Promise<{
|
|
21
105
|
url: string;
|
|
22
106
|
size: number;
|
|
@@ -60,7 +144,7 @@ npm i yuque-rich-text
|
|
|
60
144
|
* @param type 内容的格式
|
|
61
145
|
* @return 文档内容
|
|
62
146
|
*/
|
|
63
|
-
getContent: (type: "lake" | "text/html") =>
|
|
147
|
+
getContent: (type: "lake" | "text/html") => string;
|
|
64
148
|
/**
|
|
65
149
|
* 判断当前文档是否是空文档
|
|
66
150
|
* @return true表示当前是空文档
|
|
@@ -94,21 +178,6 @@ npm i yuque-rich-text
|
|
|
94
178
|
}
|
|
95
179
|
```
|
|
96
180
|
|
|
97
|
-
### 编辑器
|
|
98
|
-
> 注意不可在onChange事件中修改value的值,否则会进入无限递归。
|
|
99
|
-
```js
|
|
100
|
-
<template>
|
|
101
|
-
<YuqueRichText ref="editRef" :value="modelValue"/>
|
|
102
|
-
</template>
|
|
103
|
-
|
|
104
|
-
<script setup lang="ts">
|
|
105
|
-
import { ref, watch, PropType } from "vue";
|
|
106
|
-
import { YuqueRichText } from "yuque-rich-text";
|
|
107
|
-
|
|
108
|
-
const editRef = ref<InstanceType<typeof YuqueRichText>>();
|
|
109
|
-
const modelValue = ref("hello yuque richtext");
|
|
110
|
-
</script>
|
|
111
|
-
```
|
|
112
181
|
|
|
113
182
|
## ⚠️ Disclaimer
|
|
114
183
|
This is an **unofficial third-party extension** for `[www.yuque.com]`. It is not affiliated with, maintained by, or endorsed by `[www.yuque.com]`.
|
package/demo/App.vue
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
<script setup lang="ts">
|
|
22
22
|
import { ref, watch, PropType } from "vue";
|
|
23
23
|
import { YuqueRichText } from "yuque-rich-text";
|
|
24
|
+
import type { IEditorRef } from "yuque-rich-text";
|
|
24
25
|
|
|
25
|
-
const editRef = ref<
|
|
26
|
+
const editRef = ref<IEditorRef>();
|
|
26
27
|
const modelValue = ref("123");
|
|
27
28
|
const output = ref("");
|
|
28
29
|
|
package/index.html
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
<div id="app"></div>
|
|
13
13
|
<script type="module" src="/demo/main.ts"></script>
|
|
14
14
|
<script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
|
|
15
|
-
<script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
|
|
16
|
-
<script src="https://gw.alipayobjects.com/render/p/yuyan_v/180020010000005484/7.1.4/CodeMirror.js"></script>
|
|
17
|
-
<script src="https://ur.alipay.com/tracert_a385.js"></script>
|
|
18
|
-
<script src="https://mdn.alipayobjects.com/design_kitchencore/afts/file/ANSZQ7GHQPMAAAAAAAAAAAAADhulAQBr"></script>
|
|
19
|
-
<script src="https://gw.alipayobjects.com/render/p/yuyan_npm/@alipay_lakex-doc/1.71.0/umd/doc.umd.js"></script>
|
|
15
|
+
<script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
|
|
16
|
+
<script src="https://gw.alipayobjects.com/render/p/yuyan_v/180020010000005484/7.1.4/CodeMirror.js"></script>
|
|
17
|
+
<script src="https://ur.alipay.com/tracert_a385.js"></script>
|
|
18
|
+
<script src="https://mdn.alipayobjects.com/design_kitchencore/afts/file/ANSZQ7GHQPMAAAAAAAAAAAAADhulAQBr"></script>
|
|
19
|
+
<script src="https://gw.alipayobjects.com/render/p/yuyan_npm/@alipay_lakex-doc/1.71.0/umd/doc.umd.js"></script>
|
|
20
20
|
</body>
|
|
21
21
|
</html>
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -2,9 +2,9 @@ import { ref, watch, h, onMounted, onUnmounted, defineComponent } from "vue";
|
|
|
2
2
|
import { templateHtml } from "./template";
|
|
3
3
|
import loadLakeEditor from "./load";
|
|
4
4
|
import { InjectEditorPlugin } from "./editor-plugin";
|
|
5
|
-
import { slash } from
|
|
5
|
+
import { slash } from "./slash-options";
|
|
6
6
|
|
|
7
|
-
const blockquoteID =
|
|
7
|
+
const blockquoteID = "yqextensionblockquoteid";
|
|
8
8
|
export interface EditorProps {
|
|
9
9
|
value: string;
|
|
10
10
|
children?: any;
|
|
@@ -20,7 +20,7 @@ export interface EditorProps {
|
|
|
20
20
|
filename: string;
|
|
21
21
|
}>;
|
|
22
22
|
}
|
|
23
|
-
export interface EditorEmits{
|
|
23
|
+
export interface EditorEmits {
|
|
24
24
|
onChange?: (value: string) => void;
|
|
25
25
|
onLoad?: () => void;
|
|
26
26
|
onSave?: () => void;
|
|
@@ -47,7 +47,7 @@ export interface IEditorRef {
|
|
|
47
47
|
* @param type 内容的格式
|
|
48
48
|
* @return 文档内容
|
|
49
49
|
*/
|
|
50
|
-
getContent: (type: "lake" | "text/html") =>
|
|
50
|
+
getContent: (type: "lake" | "text/html") => string;
|
|
51
51
|
/**
|
|
52
52
|
* 判断当前文档是否是空文档
|
|
53
53
|
* @return true表示当前是空文档
|
|
@@ -82,7 +82,7 @@ export interface IEditorRef {
|
|
|
82
82
|
|
|
83
83
|
export default defineComponent({
|
|
84
84
|
props: {
|
|
85
|
-
value:{
|
|
85
|
+
value: {
|
|
86
86
|
type: String,
|
|
87
87
|
default: "",
|
|
88
88
|
},
|
|
@@ -90,9 +90,9 @@ export default defineComponent({
|
|
|
90
90
|
type: Boolean,
|
|
91
91
|
default: false,
|
|
92
92
|
required: false,
|
|
93
|
-
}
|
|
93
|
+
},
|
|
94
94
|
},
|
|
95
|
-
emits: [
|
|
95
|
+
emits: ["onChange", "onLoad", "onSave", "uploadImage"],
|
|
96
96
|
setup(props: EditorProps, { emit, expose }) {
|
|
97
97
|
const isBrowser = typeof window !== "undefined";
|
|
98
98
|
const iframeRef = ref<HTMLIFrameElement>();
|
|
@@ -111,26 +111,25 @@ export default defineComponent({
|
|
|
111
111
|
// 加载编辑器
|
|
112
112
|
loadLakeEditor(win).then(() => {
|
|
113
113
|
// 创建编辑器
|
|
114
|
-
let editInstance = props.isview
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
);
|
|
114
|
+
let editInstance = props.isview
|
|
115
|
+
? createOpenViewer
|
|
116
|
+
: createOpenEditor;
|
|
117
|
+
const newEditor = editInstance(doc.getElementById("root"), {
|
|
118
|
+
scrollNode: () => {
|
|
119
|
+
return doc.querySelector(".ne-editor-wrap");
|
|
120
|
+
},
|
|
121
|
+
image: {
|
|
122
|
+
uploadFileURL: "/api/upload/image",
|
|
123
|
+
crawlURL: "/api/upload/image",
|
|
124
|
+
createUploadPromise: props.uploadImage,
|
|
125
|
+
},
|
|
126
|
+
video: {
|
|
127
|
+
uploadFileURL: "/api/upload/video",
|
|
128
|
+
createUploadPromise: props.uploadVideo,
|
|
129
|
+
},
|
|
130
|
+
placeholder: "输入内容...",
|
|
131
|
+
defaultFontsize: 14,
|
|
132
|
+
});
|
|
134
133
|
newEditor.on("visitLink", (url: string) => {
|
|
135
134
|
window.open(url, "__blank");
|
|
136
135
|
});
|
|
@@ -154,13 +153,16 @@ export default defineComponent({
|
|
|
154
153
|
emit("onSave");
|
|
155
154
|
}
|
|
156
155
|
};
|
|
157
|
-
watch(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
156
|
+
watch(
|
|
157
|
+
[() => props.value, () => editor.value],
|
|
158
|
+
([value, edit], [oldValue, oldEdit]) => {
|
|
159
|
+
if (!editor.value) return;
|
|
160
|
+
|
|
161
|
+
editor.value?.setDocument("lake", props.value);
|
|
162
|
+
editor.value?.execCommand("paragraphSpacing", "relax");
|
|
163
|
+
emit("onLoad");
|
|
164
|
+
}
|
|
165
|
+
);
|
|
164
166
|
watch([() => editor.value, () => iframeRef.value], (input) => {
|
|
165
167
|
if (!editor.value || !iframeRef.value) return;
|
|
166
168
|
iframeRef.value?.contentDocument?.addEventListener(
|
|
@@ -173,47 +175,52 @@ export default defineComponent({
|
|
|
173
175
|
appendContent: (html: string, breakLine = false) => {
|
|
174
176
|
if (!editor.value) return;
|
|
175
177
|
if (breakLine) {
|
|
176
|
-
|
|
178
|
+
editor.value.execCommand("breakLine");
|
|
177
179
|
}
|
|
178
|
-
editor.value.kernel.execCommand(
|
|
180
|
+
editor.value.kernel.execCommand("insertHTML", html);
|
|
179
181
|
iframeRef.value?.focus();
|
|
180
|
-
editor.value.execCommand(
|
|
182
|
+
editor.value.execCommand("focus");
|
|
181
183
|
editor.value.renderer.scrollToCurrentSelection();
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
},
|
|
185
|
+
setContent: (
|
|
184
186
|
content: string,
|
|
185
|
-
type:
|
|
186
|
-
|
|
187
|
+
type: "text/lake" | "text/html" = "text/html"
|
|
188
|
+
) => {
|
|
187
189
|
if (!editor.value) return;
|
|
188
190
|
iframeRef.value?.focus();
|
|
189
191
|
editor.value.setDocument(type, content);
|
|
190
|
-
editor.value.execCommand(
|
|
192
|
+
editor.value.execCommand("focus", "end");
|
|
191
193
|
// 寻找定位的block 插入到block上方
|
|
192
|
-
const node =
|
|
194
|
+
const node =
|
|
195
|
+
editor.value.kernel.model.document.getNodeById(
|
|
196
|
+
blockquoteID
|
|
197
|
+
);
|
|
193
198
|
if (node) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
const rootNode =
|
|
200
|
+
editor.value.kernel.model.document.rootNode;
|
|
201
|
+
if (rootNode.firstNode === node) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
editor.value.kernel.execCommand("selection", {
|
|
205
|
+
ranges: [
|
|
206
|
+
{
|
|
207
|
+
start: {
|
|
208
|
+
node: rootNode.children[node.offset - 1],
|
|
209
|
+
offset: rootNode.children[node.offset - 1]
|
|
210
|
+
.childCount,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
});
|
|
215
|
+
editor.value.execCommand("focus");
|
|
209
216
|
}
|
|
210
|
-
|
|
211
|
-
|
|
217
|
+
},
|
|
218
|
+
isEmpty: () => {
|
|
212
219
|
if (!editor) return true;
|
|
213
|
-
return editor.value.queryCommandValue(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (!editor.value) return
|
|
220
|
+
return editor.value.queryCommandValue("isEmpty");
|
|
221
|
+
},
|
|
222
|
+
getContent: (type: "lake" | "text/html" | "description") => {
|
|
223
|
+
if (!editor.value) return "";
|
|
217
224
|
// let times = 0;
|
|
218
225
|
// while (!editor.value.canGetDocument()) {
|
|
219
226
|
// // 10s 后返回超时
|
|
@@ -223,45 +230,48 @@ export default defineComponent({
|
|
|
223
230
|
// times++;
|
|
224
231
|
// await sleep(100);
|
|
225
232
|
// }
|
|
226
|
-
if (type ===
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
233
|
+
if (type === "lake") {
|
|
234
|
+
return editor.value.getDocument("text/lake", {
|
|
235
|
+
includeMeta: true,
|
|
236
|
+
});
|
|
237
|
+
} else if (type === "text/html") {
|
|
238
|
+
return editor.value.getDocument("text/html");
|
|
230
239
|
}
|
|
231
|
-
return editor.value.getDocument(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (!editor) return
|
|
235
|
-
return editor.value.queryCommandValue(
|
|
236
|
-
|
|
237
|
-
|
|
240
|
+
return editor.value.getDocument("description");
|
|
241
|
+
},
|
|
242
|
+
getSummaryContent: () => {
|
|
243
|
+
if (!editor) return "";
|
|
244
|
+
return editor.value.queryCommandValue("getSummary", "lake");
|
|
245
|
+
},
|
|
246
|
+
wordCount: () => {
|
|
238
247
|
if (!editor) return 0;
|
|
239
|
-
return editor.value.queryCommandValue(
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
return editor.value.queryCommandValue("wordCount");
|
|
249
|
+
},
|
|
250
|
+
focusToStart: (offset = 0) => {
|
|
242
251
|
if (!editor) return;
|
|
243
252
|
iframeRef.value?.focus();
|
|
244
253
|
if (offset) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
editor.value.kernel.execCommand("selection", {
|
|
255
|
+
ranges: [
|
|
256
|
+
{
|
|
257
|
+
start: {
|
|
258
|
+
node: editor.value.kernel.model.document
|
|
259
|
+
.rootNode.children[offset],
|
|
260
|
+
offset: 0,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
});
|
|
265
|
+
editor.value.execCommand("focus");
|
|
256
266
|
} else {
|
|
257
|
-
|
|
267
|
+
editor.value.execCommand("focus", "start");
|
|
258
268
|
}
|
|
259
|
-
|
|
260
|
-
|
|
269
|
+
},
|
|
270
|
+
insertBreakLine: () => {
|
|
261
271
|
if (!editor) return;
|
|
262
|
-
editor.value.execCommand(
|
|
263
|
-
|
|
264
|
-
})
|
|
272
|
+
editor.value.execCommand("breakLine");
|
|
273
|
+
},
|
|
274
|
+
});
|
|
265
275
|
onMounted(() => {
|
|
266
276
|
unLoad.value = loadLake();
|
|
267
277
|
});
|
|
@@ -273,14 +283,15 @@ export default defineComponent({
|
|
|
273
283
|
true
|
|
274
284
|
);
|
|
275
285
|
});
|
|
276
|
-
return ()=>
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
return () =>
|
|
287
|
+
h("iframe", {
|
|
288
|
+
ref: iframeRef,
|
|
289
|
+
class: "lake-editor",
|
|
290
|
+
height: "100%",
|
|
291
|
+
width: "100%",
|
|
292
|
+
srcDoc: templateHtml,
|
|
293
|
+
allow: "*",
|
|
294
|
+
style: "background: transparent; border: none;",
|
|
295
|
+
});
|
|
285
296
|
},
|
|
286
297
|
});
|