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