@chenyomi/leafer-htmltext-editor 1.0.0 → 1.0.1

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/src/TextEditor.ts CHANGED
@@ -1,63 +1,62 @@
1
- import { IText, IEventListenerId } from "@leafer-in/interface";
2
- import { Matrix, PointerEvent } from "@leafer-ui/core";
3
1
  import { InnerEditor, registerInnerEditor } from "@leafer-in/editor";
4
- import { handleShowCurve } from "./TextEditTool/utils";
2
+ import { Matrix, PointerEvent } from "@leafer-ui/core";
3
+ import { quillManager } from ".";
5
4
  import { updataHtmlText } from "./utils";
6
- import { fetchFontAsBase64 } from "./fonts/utils";
7
- import { defaultFonts } from "./fonts/font";
8
5
 
9
6
  @registerInnerEditor()
10
7
  export class TextEditor extends InnerEditor {
11
- public static quill: any;
12
-
13
8
  public get tag() {
14
9
  return "TextEditor";
15
10
  }
16
-
17
- public quill: any;
18
- declare public editTarget: IText;
11
+ declare public editTarget: any;
19
12
 
20
- public editDom: HTMLDivElement | undefined;
13
+ public editDom: any;
21
14
  public config = {
22
15
  selectAll: false,
23
16
  };
24
17
 
25
- public eventIds: IEventListenerId[] = [];
18
+ public eventIds: any[] = [];
26
19
 
27
20
  protected selectText:
28
21
  | { start: number; end: number; text: string }
29
- | undefined;
30
- protected inBody: boolean | undefined;
31
- protected isHTMLText: boolean | undefined;
32
-
22
+ | undefined = undefined;
23
+ protected inBody: boolean | undefined = undefined; // App 的 view 为 canvas 类型时,文本编辑框只能添加到 body 下了
24
+ protected isHTMLText: boolean | undefined = undefined;
25
+ protected _keyEvent: boolean | undefined = undefined;
26
+ public quill: any = null;
27
+ public isComposing: boolean = false;
28
+
29
+ // 拼写检查相关属性
30
+ private misspelledWords: Array<{
31
+ word: string;
32
+ offset: number;
33
+ length: number;
34
+ }> = [];
35
+ private overlay: HTMLDivElement | null = null;
36
+
37
+ // 判断是否为OA系统的属性
38
+ private get isOASystem(): boolean {
39
+ return window.location.host.includes("oa");
40
+ }
33
41
  public onLoad(): void {
34
42
  const { editor } = this;
35
43
  const { config } = editor.app;
36
44
  const text = this.editTarget;
37
45
  const { scaleX, scaleY } = text.worldTransform;
38
46
  const zoomScale = Math.max(Math.abs(scaleX), Math.abs(scaleY));
39
-
40
- this.quill = TextEditor.quill;
47
+ // text.textEditing = true
41
48
 
42
- this.isHTMLText = !(text instanceof Text);
49
+ this.isHTMLText = !(text instanceof Text); // HTMLText
50
+ this._keyEvent = config.keyEvent;
43
51
  config.keyEvent = false;
44
52
 
45
- const element = document.querySelector("#textInnerEditor");
46
- if (!element || !(element instanceof HTMLDivElement)) {
47
- console.error("Cannot find #textInnerEditor element or it's not a div");
48
- return;
49
- }
50
-
51
- const div = (this.editDom = element);
53
+ const div = (this.editDom = document.querySelector("#textInnerEditor"));
52
54
  const { style } = div;
53
55
  style.visibility = "visible";
54
56
 
55
- // 获取外部box的宽高
56
- if (
57
- text.data?.canChangeBox &&
58
- text.parent?.width !== undefined &&
59
- text.parent?.height !== undefined
60
- ) {
57
+ // style.boxSizing = 'border-box'
58
+ // 获取外部box的宽高 这个box也是可以手动调整的
59
+ if (text.data.canChangeBox) {
61
60
  style.width = text.parent.width * zoomScale + "px";
62
61
  style.height = text.parent.height * zoomScale + "px";
63
62
  } else {
@@ -65,59 +64,56 @@ export class TextEditor extends InnerEditor {
65
64
  style.height = "auto";
66
65
  }
67
66
 
67
+ // style.border = '1.5px solid #8499EF'
68
68
  style.outline = "solid #8499EF";
69
69
 
70
70
  // 初始化文本样式
71
- if (text.data?.textData?.fontSize) {
71
+ if (text.data.textData.fontSize) {
72
72
  div.style.fontSize = `${text.data.textData.fontSize * zoomScale}px`;
73
73
  }
74
- if (text.data?.textData?.fontFamily) {
74
+ if (text.data.textData.fontFamily) {
75
75
  div.style.fontFamily = `${text.data.textData.fontFamily}`;
76
76
  }
77
- if (text.data?.textData?.lineHeight) {
77
+ if (text.data.textData.lineHeight) {
78
78
  div.style.lineHeight = text.data.textData.lineHeight;
79
79
  }
80
- if (text.data?.textData?.letterSpacing) {
80
+ if (text.data.textData.letterSpacing) {
81
81
  div.style.letterSpacing = `${text.data.textData.letterSpacing}px`;
82
82
  }
83
- if (text.data?.textData?.textShadow) {
83
+ if (text.data.textData.textShadow) {
84
84
  div.style.textShadow = `${text.data.textData.textShadow}`;
85
85
  } else {
86
86
  div.style.textShadow = "none";
87
87
  }
88
- if (text.data?.textData?.alignContent) {
88
+ if (text.data.textData.alignContent) {
89
89
  const qlEditor: any = div.querySelector(".ql-editor");
90
- if (qlEditor) {
91
- qlEditor.style.alignContent = `${text.data.textData.alignContent}`;
92
- }
90
+ qlEditor.style.alignContent = `${text.data.textData.alignContent}`;
93
91
  }
94
92
 
95
- // 加载文本到编辑器
96
- if (this.quill) {
97
- this.quill.clipboard.dangerouslyPasteHTML(text.text);
98
-
99
- if (text.parent?.children?.[0]?.tag?.includes("Shape")) {
100
- const parentWidth = text.parent.width;
101
- if (parentWidth !== undefined) {
102
- style.width = parentWidth * zoomScale + "px";
103
- style.left = "0px";
104
- }
105
- this.quill.formatLine(0, this.quill.getLength(), "align", "center");
106
- } else {
107
- this.quill.setSelection(0, this.quill.getLength() - 1);
108
- }
93
+ this.quill = quillManager.getQuill();
109
94
 
110
- this.quill.on("text-change", this.onInput);
111
- this.quill.on("selection-change", this.onSelectionChange);
95
+ // 加载文本到编辑器
96
+ this.quill.clipboard.dangerouslyPasteHTML(text.text);
97
+ // this.quill.focus()
98
+ if (text.parent.children[0].tag.includes("Shape")) {
99
+ style.width = text.parent.width * zoomScale + "px";
100
+ style.left = "0px";
101
+ this.quill.formatLine(0, this.quill.getLength(), "align", "center");
102
+ } else {
103
+ this.quill.setSelection(0, this.quill.getLength() - 1);
112
104
  }
113
-
105
+
106
+ // 获取整个编辑器容器的边界
107
+ // const containerBounds = this.quill.container.getBoundingClientRect()
108
+ this.quill.on("text-change", this.onInput);
109
+ this.quill.on("selection-change", this.onSelectionChange);
114
110
  localStorage.removeItem("selection-change");
115
111
 
116
112
  this.eventIds = [
117
113
  // 点击空白关闭
118
114
  editor.app.on_(PointerEvent.DOWN, (e: PointerEvent) => {
119
- let { target } = e.origin;
120
- let find: boolean = false;
115
+ let { target } = e.origin,
116
+ find: boolean = false;
121
117
  while (target) {
122
118
  if (target === div) find = true;
123
119
  target = target.parentElement;
@@ -128,73 +124,90 @@ export class TextEditor extends InnerEditor {
128
124
  }
129
125
  }),
130
126
  ];
127
+ // const { canvas } = useEditor()
128
+ // canvas.app.config.move.drag = false
129
+ // canvas.app.tree.hittable = false
130
+ // canvas.app.editor.hittable = false
131
131
  }
132
-
133
132
  private onSelectionChange = async (e: any) => {
134
133
  e && localStorage.setItem("selection-change", JSON.stringify(e));
135
134
  };
136
135
 
137
136
  private onInput = async () => {
137
+ const { editDom } = this;
138
138
  // 主要是更新编辑内容
139
+ console.log("onInput");
139
140
  updataHtmlText(this.editTarget);
141
+ // 获取当前文字编辑文字内容的边界 这里是在没有修改过外部尺寸的情况下 自适应文字编辑内容的尺寸
142
+ // updateChangeBoxBound(this.editTarget)
143
+
144
+ // 如果是清空了 width要从0改成auto
145
+ // editDom.style.width = documentBounds.width ? documentBounds.width : 'auto'
140
146
  };
141
147
 
142
148
  public onUpdate() {
143
149
  const { editTarget: text } = this;
144
- if (!text.parent?.__local) return;
145
-
146
150
  const { scaleX, scaleY } = text.worldTransform;
147
151
  const zoomScale = Math.max(Math.abs(scaleX), Math.abs(scaleY));
148
152
 
149
153
  // layout
150
- const local = text.parent.__local as any;
151
- let width = local.width || 0;
152
- let height = local.height || 0;
153
- width *= zoomScale;
154
- height *= zoomScale;
154
+ let { width, height } = text.parent.__local;
155
+ ((width *= zoomScale), (height *= zoomScale));
155
156
 
156
157
  const { x, y } = this.inBody
157
158
  ? text.app.clientBounds
158
- : (text.app.tree?.clientBounds || { x: 0, y: 0 });
159
+ : text.app.tree.clientBounds;
159
160
  const { a, b, c, d, e, f } = new Matrix(text.worldTransform)
160
161
  .scale(1 / zoomScale)
161
162
  .translateInner(0, 0);
162
163
 
163
- if (this.editDom) {
164
- const { style } = this.editDom;
165
- style.transform = `matrix(${a},${b},${c},${d},${e},${f})`;
166
- style.left = x + "px";
167
- style.top = y + "px";
168
- }
164
+ const { style } = this.editDom;
165
+ style.transform = `matrix(${a},${b},${c},${d},${e},${f})`;
166
+ style.left = x + "px";
167
+ style.top = y + "px";
169
168
 
170
169
  // 打开内部或者编辑内部文本编辑强制隐藏
171
170
  text.set({
172
171
  visible: false,
173
172
  });
174
173
  }
175
-
176
174
  private isUpdatingPoints = false;
177
-
178
175
  public onUnload(): void {
179
176
  const { editTarget: text, editor, editDom: dom } = this;
180
-
177
+ // const { canvas } = useEditor()
178
+ // canvas.app.config.move.drag = false
179
+ // canvas.app.tree.hittable = true
180
+ // canvas.app.editor.hittable = true
181
181
  if (text) {
182
182
  this.onInput();
183
+
184
+ if (editor.app) editor.app.config.keyEvent = this._keyEvent;
183
185
  editor.off_(this.eventIds);
184
186
 
185
- if (this.editDom) {
186
- this.editDom.style.visibility = "hidden";
187
- }
188
- this.eventIds = [] as any;
187
+ // dom.remove()
188
+ // this.editDom = this.eventIds = undefined
189
+ this.editDom.style.visibility = "hidden";
190
+ this.eventIds = [];
189
191
  }
190
-
191
- text.set({
192
- visible: true,
193
- });
194
-
195
- if (this.quill) {
196
- this.quill.off("text-change", this.onInput);
197
- this.quill.off("selection-change", this.onSelectionChange);
192
+ // 如果大于1
193
+ if (
194
+ text.parent &&
195
+ text.parent.name == "Text" &&
196
+ text.parent.children.some((e: any) => e.tag === "Box")
197
+ ) {
198
+ text.parent.findOne("Box").opacity = 1;
199
+ text.visible = false;
200
+ } else {
201
+ // 恢复显示
202
+ text.set({
203
+ visible: true,
204
+ });
205
+ }
206
+ if (this.quill.getLength() === 1 && text.parent.name === "Text") {
207
+ text.parent.remove();
198
208
  }
209
+ console.log("onUnload");
210
+ this.quill.off("text-change", this.onInput);
211
+ this.quill.off("selection-change", this.onSelectionChange);
199
212
  }
200
213
  }
package/src/index.ts CHANGED
@@ -1,92 +1,154 @@
1
- import { TextEditor } from "./TextEditor";
2
- export { TextEditor } from "./TextEditor";
3
- import "./TextEditTool";
4
- import { Plugin } from "@leafer-ui/core";
5
- import { Delta } from "quill";
6
- import Quill from "quill";
7
- import "quill/dist/quill.core.css";
1
+ export { TextEditor } from './TextEditor'
2
+ import { Plugin } from '@leafer-ui/core'
3
+ import './TextEditTool'
4
+ Plugin.add('text-editor2', 'editor')
8
5
 
9
- // 导出工具函数
10
- export { updataHtmlText, setHTMLText } from "./utils";
11
- export { fontManager, defaultFonts, FontManager } from "./fonts/font";
6
+ import Quill, { Delta } from 'quill'
12
7
 
13
- Plugin.add("leafer-htmltext-editor", "editor");
8
+ class QuillManager {
9
+ private static instance: QuillManager
10
+ private quill: Quill | null = null
11
+ public app_: any | null = null
14
12
 
15
- export function initTextEditorQuill(container?: HTMLElement) {
16
- const textInner = document.getElementById("textInnerEditor");
17
- if (!textInner) {
18
- const el = document.createElement("div");
19
- el.id = "textInnerEditor";
20
- el.style.position = "fixed";
21
- el.style.transformOrigin = "left top";
22
- el.style.overflowWrap = "break-word";
23
- el.style.wordBreak = "break-all";
24
- el.style.visibility = "hidden";
25
- document.body.appendChild(el);
13
+ private constructor() {}
14
+
15
+ static getInstance() {
16
+ if (!QuillManager.instance) {
17
+ QuillManager.instance = new QuillManager()
18
+ }
19
+ return QuillManager.instance
26
20
  }
27
21
 
28
- TextEditor.quill = new Quill("#textInnerEditor", {
29
- theme: null as any,
30
- modules: {
31
- toolbar: false,
32
- keyboard: {
33
- bindings: {
34
- enter: {
35
- key: "Enter",
36
- handler: (range: any, context: any) => {
37
- const [line] = TextEditor.quill.getLine(range.index);
22
+ /** 初始化(只会执行一次) */
23
+ init(app: any) {
24
+ this.app_ = app
25
+ if (this.quill) return this.quill
26
+
27
+ let el = document.getElementById('textInnerEditor')
28
+ if (!el) {
29
+ el = document.createElement('div')
30
+ el.id = 'textInnerEditor'
31
+ el.style.position = 'fixed'
32
+ el.style.transformOrigin = 'left top'
33
+ el.style.overflowWrap = 'break-word'
34
+ el.style.wordBreak = 'break-all'
35
+ el.style.visibility = 'hidden'
36
+ document.body.appendChild(el)
37
+ }
38
+
39
+ this.quill = new Quill('#textInnerEditor', {
40
+ theme: undefined,
41
+ modules: {
42
+ toolbar: false,
43
+ keyboard: {
44
+ bindings: {
45
+ enter: {
46
+ key: 'Enter',
47
+ handler: (range: any) => {
48
+ const [line] = this.quill!.getLine(range.index)
49
+ const BlockBlot: any = Quill.import('blots/block')
50
+ if (!BlockBlot?.bubbleFormats) return true
51
+
52
+ const lineFormats = BlockBlot.bubbleFormats(line)
53
+ const delta = new Delta().retain(range.index).delete(range.length).insert('\n', lineFormats)
38
54
 
39
- // 使用 Quill.import 获取 bubbleFormats
40
- const BlockBlot = Quill.import("blots/block");
41
- if (!BlockBlot || !(BlockBlot as any).bubbleFormats) return;
42
- const lineFormats = (BlockBlot as any).bubbleFormats(line);
55
+ this.quill!.updateContents(delta, Quill.sources.USER)
56
+ this.quill!.setSelection(range.index + 1, Quill.sources.SILENT)
57
+ return false
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ })
64
+ this.app_.editor.quill = this.quill
65
+ this.registerFonts()
43
66
 
44
- const delta = new Delta()
45
- .retain(range.index)
46
- .delete(range.length)
47
- .insert("\n", lineFormats);
67
+ return this.quill
68
+ }
69
+
70
+ /** 获取实例 */
71
+ getQuill() {
72
+ if (!this.quill) {
73
+ throw new Error('Quill editor not initialized. Call init() first.')
74
+ }
75
+ return this.quill
76
+ }
48
77
 
49
- TextEditor.quill.updateContents(delta, Quill.sources.USER);
50
- TextEditor.quill.setSelection(
51
- range.index + 1,
52
- Quill.sources.SILENT,
53
- );
54
- return false;
55
- },
56
- },
57
- },
58
- },
59
- },
60
- });
61
-
62
- const FontAttributor: any = Quill.import("attributors/class/font");
63
- FontAttributor.whitelist = [
64
- "Roboto",
65
- "RobotoMono",
66
- "Inter",
67
- "OpenSans",
68
- "Montserrat",
69
- "RobotoCondensed",
70
- "Arimo",
71
- "NotoSans",
72
- "NotoSansSymbols",
73
- "Merriweather",
74
- "PlayfairDisplay",
75
- "NotoSerif",
76
- "Lato",
77
- "Spectral",
78
- "DancingScript",
79
- "NotoSansSimplifiedChinese",
80
- "NotoSerifSimplifiedChinese",
81
- "NotoSansTraditionalChinese",
82
- "NotoSansHongKong",
83
- "NotoSerifTraditionalChinese",
84
- "NotoSerifHongKong",
85
- "NotoSansJapanese",
86
- "NotoSansKorean",
87
- "Poppins",
88
- ];
89
- Quill.register(FontAttributor, true);
90
-
91
- return TextEditor.quill;
78
+ getCanvas() {
79
+ if (!this.app_) {
80
+ throw new Error('app_ editor not initialized. Call init() first.')
81
+ }
82
+ return this.app_
83
+ }
84
+
85
+ private registerFonts() {
86
+ const FontAttributor: any = Quill.import('attributors/class/font')
87
+ FontAttributor.whitelist = [
88
+ 'Roboto',
89
+ 'RobotoMono',
90
+ 'Inter',
91
+ 'OpenSans',
92
+ 'Montserrat',
93
+ 'RobotoCondensed',
94
+ 'Arimo',
95
+ 'NotoSans',
96
+ 'NotoSansSymbols',
97
+ 'Merriweather',
98
+ 'PlayfairDisplay',
99
+ 'NotoSerif',
100
+ 'Lato',
101
+ 'Spectral',
102
+ 'DancingScript',
103
+ 'NotoSansSimplifiedChinese',
104
+ 'NotoSerifSimplifiedChinese',
105
+ 'NotoSansTraditionalChinese',
106
+ 'NotoSansHongKong',
107
+ 'NotoSerifTraditionalChinese',
108
+ 'NotoSerifHongKong',
109
+ 'NotoSansJapanese',
110
+ 'NotoSansKorean',
111
+ 'Poppins'
112
+ ]
113
+ Quill.register(FontAttributor, true)
114
+ }
115
+ // 判断是否是多选情况下
116
+ public isMultiSelect(): boolean {
117
+ if (!this.app_.editor) return false
118
+ if (this.app_.editor.multiple === true) {
119
+ return true
120
+ } else {
121
+ return false
122
+ }
123
+ }
124
+ public dateEdit(callback: (leaf: any) => void, level = 0, listNew?: any): void {
125
+ // 添加listNew支持,用来指定检索的数据源,防止因为防抖或者延迟执行造成的活跃对象变更
126
+ const { editor } = this.app_
127
+ const list = listNew ? listNew : editor.leafList.list
128
+ // if (this.activeObject.value?.tag === 'Frame') {
129
+ // callback(this.contentFrame)
130
+ // }
131
+ const applyCallback = (leaf: any) => {
132
+ if (level && (leaf.tag === 'Box' || leaf.name === 'Text')) {
133
+ callback(leaf.children?.[0] || leaf)
134
+ } else {
135
+ callback(leaf)
136
+ }
137
+ }
138
+ if (!list.length) return
139
+ if (Array.isArray(list) && list.length > 1) {
140
+ this.app_.lockLayout()
141
+ list.forEach(applyCallback)
142
+ this.app_.unlockLayout()
143
+ editor.updateEditBox()
144
+ } else {
145
+ applyCallback(list[0])
146
+ }
147
+ // 不知道为啥 加这个忘记了 先保留
148
+ // else if (this.activeObject.value?.tag !== 'Frame') {
149
+ // applyCallback(this.activeObject.value)
150
+ // }
151
+ }
92
152
  }
153
+
154
+ export const quillManager = QuillManager.getInstance()