yuque-rich-text 1.0.0 → 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 CHANGED
@@ -2,16 +2,105 @@
2
2
 
3
3
  由于本人觉得语雀编辑器非常好用,很符合我的使用习惯,然后发现语雀的[Chrome浏览器插件](https://github.com/yuque/yuque-chrome-extension)实现了编辑器的功能,所以将其富文本的功能拆分位一个单独的Vue3组件。
4
4
 
5
+ ## 安装
6
+ ```sh
7
+ npm i yuque-rich-text
8
+ ```
9
+
5
10
  ## 截图
6
- ![组件实例](https://github.com/Entity-Now/yuque-rich-text/blob/master/public/Images/preview.png)
11
+ ![组件实例](https://github.com/Entity-Now/yuque-rich-text/blob/master/public/Images/yuque-rich-text.gif)
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
+ ```
7
96
 
8
97
  ### Props
9
98
 
10
99
  ```js
11
100
  export interface EditorProps {
12
101
  value: string; // 传递给组件的内容
13
- children?: any;
14
- isview?: boolean; // 预览模式,用于在客户端页面展示结果。
102
+ children?: any; // 暂无实现
103
+ isview?: boolean; // 为true该组件是只读的,为空或false则是编辑模式
15
104
  uploadImage?: (params: { data: string | File }) => Promise<{
16
105
  url: string;
17
106
  size: number;
@@ -55,7 +144,7 @@
55
144
  * @param type 内容的格式
56
145
  * @return 文档内容
57
146
  */
58
- getContent: (type: "lake" | "text/html") => Promise<string>;
147
+ getContent: (type: "lake" | "text/html") => string;
59
148
  /**
60
149
  * 判断当前文档是否是空文档
61
150
  * @return true表示当前是空文档
@@ -89,21 +178,6 @@
89
178
  }
90
179
  ```
91
180
 
92
- ### 编辑器
93
- > 注意不可在onChange事件中修改value的值,否则会进入无限递归。
94
- ```js
95
- <template>
96
- <YuqueRichText ref="editRef" :value="modelValue"/>
97
- </template>
98
-
99
- <script setup lang="ts">
100
- import { ref, watch, PropType } from "vue";
101
- import { YuqueRichText } from "yuque-rich-text";
102
-
103
- const editRef = ref<InstanceType<typeof YuqueRichText>>();
104
- const modelValue = ref("hello yuque richtext");
105
- </script>
106
- ```
107
181
 
108
182
  ## ⚠️ Disclaimer
109
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<InstanceType<typeof YuqueRichText>>();
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yuque-rich-text",
3
3
  "private": false,
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "author": "entity-now",
6
6
  "description": "由于本人觉得语雀编辑器非常好用,很符合我的使用习惯,然后发现语雀的浏览器插件实现了编辑器的功能,所以将其富文本的功能拆分位一个单独的Vue3组件。",
7
7
  "keywords": [
@@ -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 './slash-options';
5
+ import { slash } from "./slash-options";
6
6
 
7
- const blockquoteID = 'yqextensionblockquoteid';
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") => Promise<string>;
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: ['onChange', 'onLoad', 'onSave', 'uploadImage'],
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 ? createOpenViewer : createOpenEditor;
115
- const newEditor = editInstance(
116
- doc.getElementById("root"),
117
- {
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
- }
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([() => props.value, () => editor.value], ([value, edit], [oldValue, oldEdit]) => {
158
- if (!editor.value) return;
159
-
160
- editor.value?.setDocument("lake", props.value);
161
- editor.value?.execCommand("paragraphSpacing", "relax");
162
- emit("onLoad");
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
- editor.value.execCommand('breakLine');
178
+ editor.value.execCommand("breakLine");
177
179
  }
178
- editor.value.kernel.execCommand('insertHTML', html);
180
+ editor.value.kernel.execCommand("insertHTML", html);
179
181
  iframeRef.value?.focus();
180
- editor.value.execCommand('focus');
182
+ editor.value.execCommand("focus");
181
183
  editor.value.renderer.scrollToCurrentSelection();
182
- },
183
- setContent: (
184
+ },
185
+ setContent: (
184
186
  content: string,
185
- type: 'text/lake' | 'text/html' = 'text/html',
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('focus', 'end');
192
+ editor.value.execCommand("focus", "end");
191
193
  // 寻找定位的block 插入到block上方
192
- const node = editor.value.kernel.model.document.getNodeById(blockquoteID);
194
+ const node =
195
+ editor.value.kernel.model.document.getNodeById(
196
+ blockquoteID
197
+ );
193
198
  if (node) {
194
- const rootNode = editor.value.kernel.model.document.rootNode;
195
- if (rootNode.firstNode === node) {
196
- return;
197
- }
198
- editor.value.kernel.execCommand('selection', {
199
- ranges: [
200
- {
201
- start: {
202
- node: rootNode.children[node.offset - 1],
203
- offset: rootNode.children[node.offset - 1].childCount,
204
- },
205
- },
206
- ],
207
- });
208
- editor.value.execCommand('focus');
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
- isEmpty: () => {
217
+ },
218
+ isEmpty: () => {
212
219
  if (!editor) return true;
213
- return editor.value.queryCommandValue('isEmpty');
214
- },
215
- getContent: (type: 'lake' | 'text/html' | 'description') => {
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 === 'lake') {
227
- return editor.value.getDocument('text/lake', { includeMeta: true });
228
- } else if (type === 'text/html') {
229
- return editor.value.getDocument('text/html');
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('description');
232
- },
233
- getSummaryContent: () => {
234
- if (!editor) return '';
235
- return editor.value.queryCommandValue('getSummary', 'lake');
236
- },
237
- wordCount: () => {
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('wordCount');
240
- },
241
- focusToStart: (offset = 0) => {
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
- editor.value.kernel.execCommand('selection', {
246
- ranges: [
247
- {
248
- start: {
249
- node: editor.value.kernel.model.document.rootNode.children[offset],
250
- offset: 0,
251
- },
252
- },
253
- ],
254
- });
255
- editor.value.execCommand('focus');
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
- editor.value.execCommand('focus', 'start');
267
+ editor.value.execCommand("focus", "start");
258
268
  }
259
- },
260
- insertBreakLine: () => {
269
+ },
270
+ insertBreakLine: () => {
261
271
  if (!editor) return;
262
- editor.value.execCommand('breakLine');
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 ()=> h("iframe", {
277
- ref: iframeRef,
278
- class: "lake-editor",
279
- height: "100%",
280
- width: "100%",
281
- srcDoc: templateHtml,
282
- allow: "*",
283
- style: "background: transparent; border: none;",
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
  });
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- import YuqueRichText from '@/components/lake-rich/lake-rich'
1
+ import YuqueRichText from './components/lake-rich/lake-rich'
2
+ export type { IEditorRef, EditorEmits, EditorProps } from './components/lake-rich/lake-rich'
2
3
 
3
4
  export {
4
5
  YuqueRichText