@chenyomi/leafer-htmltext-editor 1.0.0

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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/dist/TextEditTool/index.d.ts +17 -0
  4. package/dist/TextEditTool/index.d.ts.map +1 -0
  5. package/dist/TextEditTool/index.js +138 -0
  6. package/dist/TextEditTool/utils.d.ts +8 -0
  7. package/dist/TextEditTool/utils.d.ts.map +1 -0
  8. package/dist/TextEditTool/utils.js +173 -0
  9. package/dist/TextEditor.d.ts +27 -0
  10. package/dist/TextEditor.d.ts.map +1 -0
  11. package/dist/TextEditor.js +166 -0
  12. package/dist/esm/TextEditTool/index.d.ts +17 -0
  13. package/dist/esm/TextEditTool/index.d.ts.map +1 -0
  14. package/dist/esm/TextEditTool/index.js +135 -0
  15. package/dist/esm/TextEditTool/utils.d.ts +8 -0
  16. package/dist/esm/TextEditTool/utils.d.ts.map +1 -0
  17. package/dist/esm/TextEditTool/utils.js +165 -0
  18. package/dist/esm/TextEditor.d.ts +27 -0
  19. package/dist/esm/TextEditor.d.ts.map +1 -0
  20. package/dist/esm/TextEditor.js +163 -0
  21. package/dist/esm/fonts/font.d.ts +17 -0
  22. package/dist/esm/fonts/font.d.ts.map +1 -0
  23. package/dist/esm/fonts/font.js +68 -0
  24. package/dist/esm/fonts/utils.d.ts +9 -0
  25. package/dist/esm/fonts/utils.d.ts.map +1 -0
  26. package/dist/esm/fonts/utils.js +170 -0
  27. package/dist/esm/index.d.ts +7 -0
  28. package/dist/esm/index.d.ts.map +1 -0
  29. package/dist/esm/utils.d.ts +3 -0
  30. package/dist/esm/utils.d.ts.map +1 -0
  31. package/dist/esm/utils.js +284 -0
  32. package/dist/fonts/font.d.ts +17 -0
  33. package/dist/fonts/font.d.ts.map +1 -0
  34. package/dist/fonts/font.js +72 -0
  35. package/dist/fonts/utils.d.ts +9 -0
  36. package/dist/fonts/utils.d.ts.map +1 -0
  37. package/dist/fonts/utils.js +180 -0
  38. package/dist/index.d.ts +7 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.esm.js +79 -0
  41. package/dist/index.js +92 -0
  42. package/dist/utils.d.ts +3 -0
  43. package/dist/utils.d.ts.map +1 -0
  44. package/dist/utils.js +289 -0
  45. package/package.json +55 -0
  46. package/src/TextEditTool/index.ts +145 -0
  47. package/src/TextEditTool/utils.ts +216 -0
  48. package/src/TextEditor.ts +200 -0
  49. package/src/fonts/font.ts +86 -0
  50. package/src/fonts/utils.ts +232 -0
  51. package/src/index.ts +92 -0
  52. package/src/utils.ts +331 -0
@@ -0,0 +1,232 @@
1
+ const FONT_CSS_TAG = "data-fonts"
2
+ const FONT_CSS_TAG_BASE64 = "base64-fonts"
3
+
4
+ export async function fetchFontAsBase64(url: string): Promise<string> {
5
+ const response = await fetch(url);
6
+ if (!response.ok) {
7
+ throw new Error(`Failed to fetch font from ${url}`);
8
+ }
9
+ // 获取文件类型
10
+ const contentType = response.headers.get('Content-Type');
11
+ let mimeType: string;
12
+
13
+ // 根据 Content-Type 设置 MIME 类型
14
+ if (contentType?.includes('font/woff2')) {
15
+ mimeType = 'font/woff2';
16
+ } else if (contentType?.includes('font/woff')) {
17
+ mimeType = 'font/woff';
18
+ } else if (contentType?.includes('font/ttf') || contentType?.includes('font/sfnt')) {
19
+ mimeType = 'font/ttf';
20
+ } else if (contentType?.includes('application/octet-stream')) {
21
+ // 如果 Content-Type 是通用的二进制流,尝试从文件扩展名推断类型
22
+ const extension = url.split('.').pop()?.toLowerCase();
23
+ if (extension === 'woff2') {
24
+ mimeType = 'font/woff2';
25
+ } else if (extension === 'woff') {
26
+ mimeType = 'font/woff';
27
+ } else if (extension === 'ttf') {
28
+ mimeType = 'font/ttf';
29
+ } else {
30
+ throw new Error(`Unsupported font file type: ${extension}`);
31
+ }
32
+ } else {
33
+ throw new Error(`Unsupported Content-Type: ${contentType}`);
34
+ }
35
+
36
+ // 将文件转换为 Base64
37
+ const arrayBuffer = await response.arrayBuffer();
38
+ const base64String = arrayBufferToBase64(arrayBuffer);
39
+
40
+ // 返回 data: URL
41
+ return `data:${mimeType};charset=utf-8;base64,${base64String}`;
42
+ }
43
+
44
+ function arrayBufferToBase64(buffer: ArrayBuffer): string {
45
+ let binary = '';
46
+ const bytes = new Uint8Array(buffer);
47
+ const len = bytes.byteLength;
48
+ for (let i = 0; i < len; i++) {
49
+ binary += String.fromCharCode(bytes[i]);
50
+ }
51
+ return window.btoa(binary);
52
+ }
53
+
54
+ /**
55
+ * 批量添加自定义字体样式
56
+ * @param fontList
57
+ */
58
+ export function addCustomFonts(fontList: any = []) {
59
+ let styleTag = document.createElement('style');
60
+ styleTag.setAttribute(FONT_CSS_TAG, 'true');
61
+ let fontRules = fontList.map((font: any) => `@font-face {
62
+ font-family: "${font.name}";
63
+ src: local("${font.name}"), url("${font.download}")
64
+ }`).join('\n');
65
+ styleTag.textContent = fontRules;
66
+ document.head.appendChild(styleTag);
67
+ }
68
+
69
+ /**
70
+ * 获取自定义字体样式
71
+ */
72
+ export function getCustomFontsStyle() {
73
+ const styleTag = document.querySelector('style[data-fonts]');
74
+ return styleTag?.textContent || '';
75
+ }
76
+
77
+ /**
78
+ * 添加单个自定义字体样式
79
+ * @param font
80
+ */
81
+ export function addCustomFont(font: any) {
82
+ let styleTag = document.querySelector('style[data-fonts]') as HTMLStyleElement | null;
83
+ // 如果不存在样式标签,则创建一个新的style标签
84
+ if (!styleTag) {
85
+ styleTag = document.createElement('style');
86
+ styleTag.setAttribute(FONT_CSS_TAG, 'true');
87
+ document.head.appendChild(styleTag);
88
+ }
89
+
90
+ if (!styleTag.sheet) return;
91
+
92
+ let existingFonts: string[] = [];
93
+ existingFonts = Array.from(styleTag.sheet.cssRules).map((rule: CSSRule) => {
94
+ const match = rule.cssText.match(/font-family: "([^"]+)"/);
95
+ return match ? match[1] : null;
96
+ }).filter((font): font is string => font !== null);
97
+
98
+ // 判断要添加的字体是否已经存在于样式表中
99
+ if (!existingFonts.includes(font.name)) {
100
+ // 创建新的 @font-face 规则
101
+ const newFontRule = `@font-face {
102
+ font-family: "${font.name}";
103
+ src: url("${font.download}");
104
+ }`;
105
+
106
+ // 插入新的 @font-face 规则到样式表中
107
+ try {
108
+ styleTag.sheet.insertRule(newFontRule, styleTag.sheet.cssRules.length);
109
+ } catch (e) {
110
+ console.error('Failed to insert font rule:', e);
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * 批量加载字体(要在生成完字体css后再调用次方法执行)
117
+ * @param fontNameList 字体名称
118
+ */
119
+ export async function batchLoadFont(fontNameList: any = []) {
120
+ // FontFaceObserver 需要额外安装,这里注释掉
121
+ // 可以使用浏览器原生的 document.fonts API
122
+ try {
123
+ for (const fontFamily of fontNameList) {
124
+ await document.fonts.load(`16px ${fontFamily}`);
125
+ }
126
+ } catch (e) {
127
+ console.warn('Font loading failed:', e);
128
+ }
129
+ }
130
+
131
+ function getFontUrlFromCSS(selectors: string, fontFamilyName: string): string | null {
132
+ // 获取所有 <style> 标签
133
+ const styleTags = document.querySelectorAll(selectors);
134
+
135
+ for (let i = 0; i < styleTags.length; i++) {
136
+ const styleTag = styleTags[i];
137
+ // 获取 CSS 内容
138
+ const cssText = styleTag.textContent || '';
139
+
140
+ // 使用正则表达式匹配 @font-face 规则
141
+ const fontFaceRules = cssText.match(/@font-face\s*\{[^}]+\}/g);
142
+
143
+ if (fontFaceRules) {
144
+ for (const rule of fontFaceRules) {
145
+ // 提取 font-family
146
+ const fontFamilyMatch = rule.match(/font-family:\s*(["'])(.*?)\1/);
147
+ if (fontFamilyMatch && fontFamilyMatch[2] === fontFamilyName) {
148
+ // 提取 src
149
+ const srcMatch = rule.match(/src:\s*url\((["'])(.*?)\1\)/);
150
+ if (srcMatch) {
151
+ return srcMatch[2]; // 返回字体文件 URL
152
+ } else {
153
+ const srcMatch2 = rule.match(/src:\s*(?:local\([^)]+\)\s*,\s*)?url\((["'])(.*?)\1\)/);
154
+ if (srcMatch2) {
155
+ return srcMatch2[2];
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ return null; // 未找到匹配的字体
164
+ }
165
+
166
+ /**
167
+ * 添加单个自定义字体样式(Base64格式)
168
+ * @param font
169
+ */
170
+ export async function addCustomFontBase64(font: any) {
171
+ const fontUrl = getFontUrlFromCSS(`style[${FONT_CSS_TAG}]`, font);
172
+ if (!fontUrl) {
173
+ console.warn('Font URL not found for:', font);
174
+ return;
175
+ }
176
+
177
+ console.log('fontUrl=', fontUrl);
178
+ let styleTag = document.querySelector(`style[${FONT_CSS_TAG_BASE64}]`) as HTMLStyleElement | null;
179
+ // 如果不存在样式标签,则创建一个新的style标签
180
+ if (!styleTag) {
181
+ styleTag = document.createElement('style');
182
+ styleTag.setAttribute(FONT_CSS_TAG_BASE64, 'true');
183
+ document.head.appendChild(styleTag);
184
+ }
185
+
186
+ // 确保样式标签已经被添加到文档中
187
+ if (!styleTag.sheet) {
188
+ await new Promise(resolve => setTimeout(resolve, 10)); // 等待样式标签被加载
189
+ }
190
+
191
+ if (!styleTag.sheet) return;
192
+
193
+ let existingFonts = Array.from(styleTag.sheet.cssRules).map((rule: CSSRule) => {
194
+ const match = rule.cssText.match(/font-family: "([^"]+)"/);
195
+ return match ? match[1] : null;
196
+ }).filter((font): font is string => font !== null);
197
+
198
+ // 判断要添加的字体是否已经存在于样式表中
199
+ if (!existingFonts.includes(font)) {
200
+ const base64Url = await fetchFontAsBase64(fontUrl);
201
+ // 创建新的 @font-face 规则
202
+ const newFontRule = `@font-face {
203
+ font-family: "${font}";
204
+ src: local("${font}"), url("${base64Url}");
205
+ }`;
206
+
207
+ // 插入新的 @font-face 规则到样式表中
208
+ try {
209
+ styleTag.sheet.insertRule(newFontRule, styleTag.sheet.cssRules.length);
210
+ styleTag.textContent += newFontRule;
211
+ } catch (e) {
212
+ console.error('Failed to insert font rule:', e);
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * 获取自定义字体样式
219
+ */
220
+ export function getBase64CustomFontsStyle() {
221
+ const styleTag = document.querySelector(`style[${FONT_CSS_TAG_BASE64}]`);
222
+ return styleTag?.textContent || '';
223
+ }
224
+
225
+ /**
226
+ * 加载单个字体并转换为 Base64 格式
227
+ * @param font 字体名称
228
+ * @returns Promise<void>
229
+ */
230
+ export async function loadFont(font: string): Promise<void> {
231
+ await addCustomFontBase64(font);
232
+ }
package/src/index.ts ADDED
@@ -0,0 +1,92 @@
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";
8
+
9
+ // 导出工具函数
10
+ export { updataHtmlText, setHTMLText } from "./utils";
11
+ export { fontManager, defaultFonts, FontManager } from "./fonts/font";
12
+
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);
26
+ }
27
+
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);
38
+
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);
43
+
44
+ const delta = new Delta()
45
+ .retain(range.index)
46
+ .delete(range.length)
47
+ .insert("\n", lineFormats);
48
+
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;
92
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,331 @@
1
+ import { TextEditor } from './TextEditor';
2
+
3
+ // 获取编辑器和quill实例的辅助函数
4
+ function getEditorContext() {
5
+ const quill = TextEditor.quill;
6
+ return { quill };
7
+ }
8
+
9
+ // 更新text样式
10
+ export const updataHtmlText = async (e?: any, base64font?: any, fontObj?: any) => {
11
+ const { scaleX, scaleY } = e.worldTransform;
12
+ const zoomScale = Math.max(Math.abs(scaleX), Math.abs(scaleY));
13
+ const dom: HTMLElement | null = document.querySelector('#textInnerEditor');
14
+
15
+ if (dom && e.data.textData?.fontFamily) {
16
+ dom.style.fontFamily = e.data.textData.fontFamily;
17
+ }
18
+ if (dom && e.data.textData?.fontSize) {
19
+ dom.style.fontSize = `${e.data.textData.fontSize * zoomScale}px`;
20
+ }
21
+ if (dom && e.data.textData?.lineHeight) {
22
+ dom.style.lineHeight = e.data.textData.lineHeight;
23
+ }
24
+ if (dom && e.data.textData?.letterSpacing) {
25
+ dom.style.letterSpacing = `${e.data.textData.letterSpacing}px`;
26
+ }
27
+ if (dom && e.data.textData?.textShadow) {
28
+ dom.style.textShadow = e.data.textData.textShadow;
29
+ } else if (dom) {
30
+ dom.style.textShadow = 'none';
31
+ }
32
+ if (dom && e.data.textData?.alignContent) {
33
+ const qlEditor: any = dom.querySelector('.ql-editor');
34
+ if (qlEditor) {
35
+ qlEditor.style.alignContent = e.data.textData.alignContent;
36
+ }
37
+ }
38
+
39
+ const { quill } = getEditorContext();
40
+ if (!quill) return;
41
+
42
+ // 如果是空的就把text置空
43
+ const html = quill.getSemanticHTML();
44
+ if (html === '<p></p>') {
45
+ if (e.text.includes('<style>@font-face')) {
46
+ e.text = e.text.split('</style>')[0] + '</style>';
47
+ } else {
48
+ e.text = '';
49
+ }
50
+ return;
51
+ }
52
+
53
+ if (e.text.includes('<style>@font-face')) {
54
+ // text已经加载过字体
55
+ const style = e.text.split('</style>')[0];
56
+ if (fontObj && !style.includes(fontObj.code)) {
57
+ // 在字体style标签检索是否有当前字体 没有就添加字体style
58
+ const addStyle = `@font-face {font-family: ${fontObj.code};src: url(${base64font}) format('woff2') } .ql-font-${fontObj?.code?.replace(/\s+/g, '')} {font-family: ${fontObj.name};}`;
59
+ e.text =
60
+ style +
61
+ addStyle +
62
+ '</style>' +
63
+ addFontSizeToP(
64
+ e,
65
+ html,
66
+ e.data.textData.fontSize,
67
+ e.data.textData.lineHeight,
68
+ e.data.textData.letterSpacing,
69
+ e.data.textData.textShadow,
70
+ e.data.textData?.alignContent
71
+ );
72
+ } else {
73
+ // 样式当前字体已经存在
74
+ e.text =
75
+ style +
76
+ '</style>' +
77
+ addFontSizeToP(
78
+ e,
79
+ html,
80
+ e.data.textData.fontSize,
81
+ e.data.textData.lineHeight,
82
+ e.data.textData.letterSpacing,
83
+ e.data.textData.textShadow,
84
+ e.data.textData?.alignContent
85
+ );
86
+ }
87
+ } else if (base64font && fontObj) {
88
+ // 未加载过字体初次添加字体 存在base64font && fontObj才触发
89
+ const style = `<style>@font-face {font-family: ${e.data.textData.fontFamily.split(',')[0]};src: url(${base64font}) format('woff2') } .ql-font-${fontObj?.code?.replace(/\s+/g, '')} {font-family: ${fontObj.name};}</style>`;
90
+ e.text =
91
+ style +
92
+ addFontSizeToP(
93
+ e,
94
+ html,
95
+ e.data.textData.fontSize,
96
+ e.data.textData.lineHeight,
97
+ e.data.textData.letterSpacing,
98
+ e.data.textData.textShadow,
99
+ e.data.textData?.alignContent
100
+ );
101
+ } else {
102
+ // text没有包含字体就不加字体style
103
+ e.text = addFontSizeToP(
104
+ e,
105
+ html,
106
+ e.data.textData.fontSize,
107
+ e.data.textData.lineHeight,
108
+ e.data.textData.letterSpacing,
109
+ e.data.textData.textShadow,
110
+ e.data.textData?.alignContent
111
+ );
112
+ }
113
+ };
114
+
115
+ const addFontSizeToP = (
116
+ e: any,
117
+ html: any,
118
+ fontSize = 16,
119
+ lineHeight = '1.5',
120
+ letterSpacing = '0',
121
+ textShadow = 'none',
122
+ alignContent = 'start'
123
+ ) => {
124
+ const { quill } = getEditorContext();
125
+ if (!quill) return html;
126
+
127
+ const { scaleX, scaleY } = e.worldTransform;
128
+ const zoomScale = Math.max(Math.abs(scaleX), Math.abs(scaleY));
129
+ const wrapper = document.createElement('div');
130
+ wrapper.innerHTML = html;
131
+
132
+ // 添加的样式 用于HTMLText渲染
133
+ const wrapperStyle: any = {
134
+ fontSize: `${fontSize}px`,
135
+ lineHeight,
136
+ letterSpacing: `${letterSpacing}px`,
137
+ textShadow
138
+ };
139
+
140
+ wrapper.querySelectorAll('p,ol,ul').forEach((p: any) => {
141
+ Object.assign(p.style, wrapperStyle);
142
+ });
143
+
144
+ // 这个上下标还是要控制一下尺寸 因为html和canvas规范不一样 所以这里要控制一下
145
+ let str = wrapper.innerHTML;
146
+
147
+ // 排查如果有回车换行的加上一个字符
148
+ if (/<p\b[^>]*><\/p>/.test(str)) {
149
+ str = str.replace(/<p\b([^>]*)><\/p>/g, '<p$1>&nbsp;</p>');
150
+ }
151
+
152
+ let height;
153
+ const div: any = document.querySelector('#textInnerEditor');
154
+
155
+ // 获取包含自动换行的实际内容高度
156
+ const actualHeight = Number((quill.scroll.domNode.scrollHeight / zoomScale).toFixed(0));
157
+ const actualWidth = Number((quill.scroll.domNode.scrollWidth / zoomScale).toFixed(0));
158
+
159
+ if (['center', 'end'].includes(e.data.textData.alignContent)) {
160
+ if (e.parent.height < actualHeight) {
161
+ e.data.textData.alignContent = 'start';
162
+ height = (actualHeight || e.__layout.boxBounds.height) + 'px';
163
+ } else {
164
+ height = `${e.parent.height}px`;
165
+ }
166
+ } else {
167
+ if (e.parent.height < actualHeight) {
168
+ height = (actualHeight || e.__layout.boxBounds.height) + 'px';
169
+ } else {
170
+ height = `${e.parent.height}px`;
171
+ }
172
+ }
173
+
174
+ // 水平居中相关样式内嵌
175
+ const style = `<style>sub,sup{font-size:63%;}.ql-ui{position:absolute}ol,ul{counter-reset:list-0;padding-left:1.5em;margin:0}ol>li,ul>li{counter-increment:list-0;list-style-type:none;position:relative;padding-left:0;margin:0}ol>li::before{content:counter(list-0,decimal) '. ';position:absolute;left:-1.5em;width:1.2em;text-align:right}ul>li::before{content:'\\u2022';position:absolute;left:-1.5em;width:1.2em;text-align:right}li[data-list]{counter-set:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-align-center{text-align:center}.ql-align-right{text-align:right}.ql-align-left{text-align:left}.ql-align-justify{text-align:justify}</style>`;
176
+
177
+ let divBox = '';
178
+ if (e.parent.children[0].tag.includes('Shape')) {
179
+ divBox =
180
+ style +
181
+ `<div style="width: ${e.parent.width}px;height: ${e.parent.height}px;overflow-wrap:break-word;word-break:break-all;align-content:center;">${str}</div>`;
182
+ } else if (e.data.canChangeBox) {
183
+ divBox =
184
+ style +
185
+ `<div style="width: ${e.parent.width}px;height:${height};overflow-wrap:break-word;word-break:break-all;align-content:${alignContent};">${str}</div>`;
186
+ } else {
187
+ // 斜体补偿宽度 控制
188
+ if (e.data.textData.italic) {
189
+ divBox = style + str;
190
+ } else {
191
+ divBox = style + `<div style="width: ${actualWidth - 10}px">${str}</div>`;
192
+ }
193
+ }
194
+ console.log(divBox, '最终的html内容');
195
+ return divBox;
196
+ };
197
+
198
+ export const setHTMLText = (
199
+ key: string,
200
+ value?: any,
201
+ base64font?: any,
202
+ editor?: any,
203
+ isInnerEditor?: boolean
204
+ ) => {
205
+ const { quill } = getEditorContext();
206
+ if (!quill) {
207
+ console.error('Quill editor not initialized');
208
+ return;
209
+ }
210
+
211
+ const rangeStr = localStorage.getItem('selection-change');
212
+ const range = rangeStr ? JSON.parse(rangeStr) : null;
213
+
214
+ if (range && isInnerEditor) {
215
+ quill.setSelection(range.index, range.length);
216
+ }
217
+
218
+ if (!editor || !editor.dateEdit) {
219
+ console.warn('Editor context not available for dateEdit');
220
+ return;
221
+ }
222
+
223
+ editor.dateEdit(async (e: any) => {
224
+ if (key === 'font') {
225
+ const fontSimpleName = value.code.replace(/\s+/g, '');
226
+ if (isInnerEditor) {
227
+ if (range && range.length) {
228
+ quill.formatText(range.index, range.length, key, fontSimpleName);
229
+ } else {
230
+ quill.formatText(0, quill.getLength() - 1, key, fontSimpleName);
231
+ }
232
+ updataHtmlText(e, base64font ?? null, value ?? null);
233
+ } else {
234
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
235
+ quill.formatText(0, quill.getLength() - 1, key, fontSimpleName);
236
+ updataHtmlText(e, base64font ?? null, value ?? null);
237
+ }
238
+ } else if (key === 'fontSize') {
239
+ e.data.textData[key] = value;
240
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
241
+ updataHtmlText(e, base64font ?? null);
242
+ } else if (key === 'textCase') {
243
+ if (isInnerEditor && range) {
244
+ const text = quill.getText(range.index, range.length);
245
+ const formats = quill.getFormat(range.index, range.length);
246
+ quill.deleteText(range.index, range.length);
247
+ let convertedText;
248
+ if (text === text.toUpperCase() && /[A-Z]/.test(text)) {
249
+ convertedText = text.toLowerCase();
250
+ } else if (text === text.toLowerCase() && /[a-z]/.test(text)) {
251
+ convertedText = text.toUpperCase();
252
+ } else {
253
+ convertedText = text.toUpperCase();
254
+ }
255
+
256
+ quill.insertText(range.index, convertedText, formats);
257
+ if (range && isInnerEditor) {
258
+ quill.setSelection(range.index, range.length);
259
+ }
260
+ }
261
+ } else if (key === 'script') {
262
+ let val = 'sub';
263
+ if (value === 'super') val = 'sup';
264
+ if (isInnerEditor) {
265
+ if (range && range.length) {
266
+ quill.formatText(range.index, range.length, key, quill.getFormat(range).script === value ? false : val);
267
+ } else {
268
+ quill.formatText(0, quill.getLength() - 1, key, quill.getFormat().script === value ? false : val);
269
+ }
270
+ } else {
271
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
272
+ quill.formatText(0, quill.getLength() - 1, key, quill.getFormat().script === value ? false : val);
273
+ updataHtmlText(e);
274
+ }
275
+ } else if (key === 'align') {
276
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
277
+ if (isInnerEditor) {
278
+ quill.format(key, value);
279
+ } else {
280
+ quill.formatLine(0, quill.getLength(), key, value);
281
+ }
282
+ updataHtmlText(e);
283
+ } else if (key === 'alignContent') {
284
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
285
+ e.data.textData[key] = value;
286
+ updataHtmlText(e);
287
+ } else if (key === 'color') {
288
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
289
+ quill.formatText(0, quill.getLength() - 1, key, value);
290
+ if (e.tag === 'HTMLText') {
291
+ updataHtmlText(e);
292
+ } else if (e.parent.findOne && e.parent.findOne('HTMLText')) {
293
+ updataHtmlText(e.parent.findOne('HTMLText'));
294
+ }
295
+ } else if (key === 'textShadow') {
296
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
297
+ e.data.textData[key] = value;
298
+ updataHtmlText(e);
299
+ } else if (key === 'list') {
300
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
301
+ if (isInnerEditor) {
302
+ const [line] = quill.getLine(range?.index || 0);
303
+ if (line.formats().list) {
304
+ quill.format(key, false);
305
+ } else {
306
+ quill.format(key, value);
307
+ }
308
+ } else {
309
+ const [line] = quill.getLine(range?.index || 0);
310
+ if (line.formats().list) {
311
+ quill.formatLine(0, quill.getLength(), key, false);
312
+ } else {
313
+ quill.formatLine(0, quill.getLength(), key, value);
314
+ }
315
+ }
316
+ updataHtmlText(e);
317
+ } else {
318
+ if (isInnerEditor) {
319
+ if (range && range.length) {
320
+ quill.formatText(range.index, range.length, key, !quill.getFormat(range)[key]);
321
+ } else {
322
+ quill.formatText(0, quill.getLength() - 1, key, !quill.getFormat()[key]);
323
+ }
324
+ } else {
325
+ editor.isMultiSelect && editor.isMultiSelect() && quill.clipboard.dangerouslyPasteHTML(e.text);
326
+ quill.formatText(0, quill.getLength() - 1, key, !quill.getFormat()[key]);
327
+ updataHtmlText(e);
328
+ }
329
+ }
330
+ }, 1);
331
+ };