@formulaxjs/editor 0.1.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.
- package/LICENSE +35 -0
- package/README.md +45 -0
- package/dist/index.cjs +1234 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.global.js +25315 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +1198 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_FORMULA_ATTRIBUTE: () => DEFAULT_FORMULA_ATTRIBUTE,
|
|
24
|
+
DEFAULT_FORMULA_CLASS: () => DEFAULT_FORMULA_CLASS,
|
|
25
|
+
FORMULA_FLAG_ATTRIBUTE: () => FORMULA_FLAG_ATTRIBUTE,
|
|
26
|
+
FormulaEditor: () => FormulaEditor,
|
|
27
|
+
createFormulaElement: () => createFormulaElement,
|
|
28
|
+
createFormulaMarkup: () => createFormulaMarkup,
|
|
29
|
+
editorStyles: () => editorStyles,
|
|
30
|
+
ensureFormulaXModalStyles: () => ensureFormulaXModalStyles,
|
|
31
|
+
escapeAttribute: () => escapeAttribute,
|
|
32
|
+
escapeHtml: () => escapeHtml,
|
|
33
|
+
findFormulaElement: () => findFormulaElement,
|
|
34
|
+
formulaXModalStyles: () => formulaXModalStyles,
|
|
35
|
+
getFormulaLatexFromElement: () => getFormulaLatexFromElement,
|
|
36
|
+
isFormulaElement: () => isFormulaElement,
|
|
37
|
+
mountFormulaXKityEditor: () => mountFormulaXKityEditor,
|
|
38
|
+
renderInteractiveHtml: () => renderInteractiveHtml,
|
|
39
|
+
replaceFormulaElement: () => replaceFormulaElement,
|
|
40
|
+
serializeSvgForInsertion: () => serializeSvgForInsertion,
|
|
41
|
+
t: () => t,
|
|
42
|
+
translations: () => translations
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// src/dom-renderer.ts
|
|
47
|
+
var joinPath = (path) => path.join(".");
|
|
48
|
+
var renderInteractiveHtml = (doc, activePath) => `
|
|
49
|
+
<div class="fx-editor-surface" data-role="surface">
|
|
50
|
+
${renderChildren(doc.body, [], activePath)}
|
|
51
|
+
</div>
|
|
52
|
+
`;
|
|
53
|
+
var renderChildren = (nodes, basePath, activePath) => {
|
|
54
|
+
const html = [];
|
|
55
|
+
for (let index = 0; index <= nodes.length; index += 1) {
|
|
56
|
+
const path = joinPath([...basePath, index]);
|
|
57
|
+
const isActive = path === joinPath(activePath);
|
|
58
|
+
html.push(
|
|
59
|
+
`<button class="fx-slot${isActive ? " is-active" : ""}" data-path="${path}" type="button" title="${path}"></button>`
|
|
60
|
+
);
|
|
61
|
+
if (index < nodes.length) {
|
|
62
|
+
html.push(renderNode(nodes[index], [...basePath, index], activePath));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return html.join("");
|
|
66
|
+
};
|
|
67
|
+
var renderNode = (node, path, activePath) => {
|
|
68
|
+
const pathValue = joinPath(path);
|
|
69
|
+
const isActive = (p) => joinPath(p) === joinPath(activePath);
|
|
70
|
+
switch (node.type) {
|
|
71
|
+
case "text":
|
|
72
|
+
return `<span class="fx-node fx-text" data-node-path="${pathValue}">${node.value}</span>`;
|
|
73
|
+
case "group":
|
|
74
|
+
return `<span class="fx-node fx-group" data-node-path="${pathValue}">${renderChildren(node.body, [...path, 0], activePath)}</span>`;
|
|
75
|
+
case "frac":
|
|
76
|
+
return `<span class="fx-node fx-frac" data-node-path="${pathValue}">
|
|
77
|
+
<span class="fx-frac-num${isActive([...path, 0]) ? " is-active" : ""}" data-path="${joinPath([...path, 0])}">${renderChildren(node.numerator, [...path, 0], activePath)}</span>
|
|
78
|
+
<span class="fx-frac-line"></span>
|
|
79
|
+
<span class="fx-frac-den${isActive([...path, 1]) ? " is-active" : ""}" data-path="${joinPath([...path, 1])}">${renderChildren(node.denominator, [...path, 1], activePath)}</span>
|
|
80
|
+
</span>`;
|
|
81
|
+
case "supsub":
|
|
82
|
+
return `<span class="fx-node fx-supsub" data-node-path="${pathValue}">
|
|
83
|
+
<span class="fx-supsub-base">${renderChildren(node.base, [...path, 0], activePath)}</span>
|
|
84
|
+
<span class="fx-supsub-stack">
|
|
85
|
+
<span class="fx-sup${isActive([...path, 1]) ? " is-active" : ""}" data-path="${joinPath([...path, 1])}">${renderChildren(node.sup ?? [], [...path, 1], activePath)}</span>
|
|
86
|
+
<span class="fx-sub${isActive([...path, 2]) ? " is-active" : ""}" data-path="${joinPath([...path, 2])}">${renderChildren(node.sub ?? [], [...path, 2], activePath)}</span>
|
|
87
|
+
</span>
|
|
88
|
+
</span>`;
|
|
89
|
+
case "sqrt":
|
|
90
|
+
return `<span class="fx-node fx-sqrt" data-node-path="${pathValue}">
|
|
91
|
+
<span class="fx-sqrt-symbol">\u221A</span>
|
|
92
|
+
<span class="fx-sqrt-body${isActive([...path, 0]) ? " is-active" : ""}" data-path="${joinPath([...path, 0])}">${renderChildren(node.value, [...path, 0], activePath)}</span>
|
|
93
|
+
</span>`;
|
|
94
|
+
case "fenced":
|
|
95
|
+
return `<span class="fx-node fx-fenced" data-node-path="${pathValue}">
|
|
96
|
+
<span class="fx-fence">${node.left}</span>
|
|
97
|
+
<span class="fx-fenced-body${isActive([...path, 0]) ? " is-active" : ""}" data-path="${joinPath([...path, 0])}">${renderChildren(node.body, [...path, 0], activePath)}</span>
|
|
98
|
+
<span class="fx-fence">${node.right}</span>
|
|
99
|
+
</span>`;
|
|
100
|
+
case "doc":
|
|
101
|
+
return renderChildren(node.body, [0], activePath);
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`Unsupported node type: ${String(node.type)}`);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/editor.ts
|
|
107
|
+
var import_core = require("@formulaxjs/core");
|
|
108
|
+
|
|
109
|
+
// src/styles.ts
|
|
110
|
+
var editorStyles = `
|
|
111
|
+
@keyframes fx-blink {
|
|
112
|
+
0%, 50% { opacity: 1; }
|
|
113
|
+
51%, 100% { opacity: 0; }
|
|
114
|
+
}
|
|
115
|
+
.fx-editor {
|
|
116
|
+
border: 1px solid #d8d4c7;
|
|
117
|
+
border-radius: 2px;
|
|
118
|
+
background: #fffefb;
|
|
119
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7);
|
|
120
|
+
padding: 24px 28px;
|
|
121
|
+
font-family: Cambria, 'Times New Roman', serif;
|
|
122
|
+
font-size: 25px;
|
|
123
|
+
line-height: 1.7;
|
|
124
|
+
min-height: 120px;
|
|
125
|
+
position: relative;
|
|
126
|
+
}
|
|
127
|
+
.fx-editor:focus-within {
|
|
128
|
+
border-color: #7bbb59;
|
|
129
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7), 0 0 0 3px rgba(123, 187, 89, 0.18);
|
|
130
|
+
}
|
|
131
|
+
.fx-editor-surface {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-wrap: wrap;
|
|
134
|
+
align-items: baseline;
|
|
135
|
+
gap: 3px;
|
|
136
|
+
cursor: text;
|
|
137
|
+
}
|
|
138
|
+
.fx-node {
|
|
139
|
+
position: relative;
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
padding: 2px 3px;
|
|
143
|
+
border-radius: 2px;
|
|
144
|
+
transition: background 0.12s ease;
|
|
145
|
+
}
|
|
146
|
+
.fx-node:hover {
|
|
147
|
+
background: rgba(122, 186, 89, 0.12);
|
|
148
|
+
}
|
|
149
|
+
.fx-text {
|
|
150
|
+
color: #2b2925;
|
|
151
|
+
}
|
|
152
|
+
.fx-group {
|
|
153
|
+
background: rgba(243, 241, 230, 0.82);
|
|
154
|
+
border: 1px dashed #c8c0a8;
|
|
155
|
+
}
|
|
156
|
+
.fx-frac {
|
|
157
|
+
display: inline-flex;
|
|
158
|
+
flex-direction: column;
|
|
159
|
+
align-items: center;
|
|
160
|
+
vertical-align: middle;
|
|
161
|
+
padding: 0 5px;
|
|
162
|
+
}
|
|
163
|
+
.fx-frac-num, .fx-frac-den {
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-wrap: wrap;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
padding: 3px 8px;
|
|
169
|
+
min-width: 24px;
|
|
170
|
+
min-height: 28px;
|
|
171
|
+
border-radius: 2px;
|
|
172
|
+
transition: all 0.15s ease;
|
|
173
|
+
}
|
|
174
|
+
.fx-frac-num {
|
|
175
|
+
border-bottom: 2px solid #2e2c29;
|
|
176
|
+
padding-bottom: 6px;
|
|
177
|
+
}
|
|
178
|
+
.fx-frac-den {
|
|
179
|
+
border-top: 2px solid #2e2c29;
|
|
180
|
+
padding-top: 6px;
|
|
181
|
+
}
|
|
182
|
+
.fx-frac-num:hover, .fx-frac-den:hover {
|
|
183
|
+
background: rgba(122, 186, 89, 0.1);
|
|
184
|
+
}
|
|
185
|
+
.fx-frac-num.is-active, .fx-frac-den.is-active {
|
|
186
|
+
background: rgba(122, 186, 89, 0.16);
|
|
187
|
+
box-shadow: inset 0 0 0 1px rgba(83, 184, 86, 0.5);
|
|
188
|
+
}
|
|
189
|
+
.fx-supsub {
|
|
190
|
+
display: inline-flex;
|
|
191
|
+
align-items: baseline;
|
|
192
|
+
gap: 0;
|
|
193
|
+
}
|
|
194
|
+
.fx-supsub-base {
|
|
195
|
+
display: inline-flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
padding: 0 2px;
|
|
198
|
+
}
|
|
199
|
+
.fx-supsub-stack {
|
|
200
|
+
display: inline-flex;
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
align-items: flex-start;
|
|
203
|
+
font-size: 0.65em;
|
|
204
|
+
margin-left: 2px;
|
|
205
|
+
}
|
|
206
|
+
.fx-sup, .fx-sub {
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
min-width: 16px;
|
|
210
|
+
min-height: 20px;
|
|
211
|
+
padding: 0 4px;
|
|
212
|
+
border-radius: 2px;
|
|
213
|
+
transition: all 0.15s ease;
|
|
214
|
+
}
|
|
215
|
+
.fx-sup {
|
|
216
|
+
vertical-align: super;
|
|
217
|
+
}
|
|
218
|
+
.fx-sub {
|
|
219
|
+
vertical-align: sub;
|
|
220
|
+
}
|
|
221
|
+
.fx-sup:hover, .fx-sub:hover {
|
|
222
|
+
background: rgba(122, 186, 89, 0.1);
|
|
223
|
+
}
|
|
224
|
+
.fx-sup.is-active, .fx-sub.is-active {
|
|
225
|
+
background: rgba(122, 186, 89, 0.16);
|
|
226
|
+
box-shadow: inset 0 0 0 1px rgba(83, 184, 86, 0.5);
|
|
227
|
+
}
|
|
228
|
+
.fx-sqrt {
|
|
229
|
+
display: inline-flex;
|
|
230
|
+
align-items: baseline;
|
|
231
|
+
}
|
|
232
|
+
.fx-sqrt-symbol {
|
|
233
|
+
font-size: 1.1em;
|
|
234
|
+
color: #2e2c29;
|
|
235
|
+
margin-right: 2px;
|
|
236
|
+
}
|
|
237
|
+
.fx-sqrt-body {
|
|
238
|
+
display: inline-flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
border-top: 2px solid #2e2c29;
|
|
241
|
+
padding: 4px 6px 0;
|
|
242
|
+
margin-left: 2px;
|
|
243
|
+
}
|
|
244
|
+
.fx-sqrt-body:hover {
|
|
245
|
+
background: rgba(122, 186, 89, 0.1);
|
|
246
|
+
}
|
|
247
|
+
.fx-sqrt-body.is-active {
|
|
248
|
+
background: rgba(122, 186, 89, 0.16);
|
|
249
|
+
}
|
|
250
|
+
.fx-fenced {
|
|
251
|
+
display: inline-flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
background: rgba(243, 241, 230, 0.78);
|
|
254
|
+
border-radius: 3px;
|
|
255
|
+
padding: 0 4px;
|
|
256
|
+
}
|
|
257
|
+
.fx-fence {
|
|
258
|
+
color: #59554d;
|
|
259
|
+
font-size: 1.2em;
|
|
260
|
+
padding: 0 2px;
|
|
261
|
+
}
|
|
262
|
+
.fx-fenced-body {
|
|
263
|
+
display: inline-flex;
|
|
264
|
+
align-items: center;
|
|
265
|
+
padding: 0 6px;
|
|
266
|
+
}
|
|
267
|
+
.fx-fenced-body:hover {
|
|
268
|
+
background: rgba(122, 186, 89, 0.1);
|
|
269
|
+
border-radius: 2px;
|
|
270
|
+
}
|
|
271
|
+
.fx-fenced-body.is-active {
|
|
272
|
+
background: rgba(122, 186, 89, 0.16);
|
|
273
|
+
border-radius: 2px;
|
|
274
|
+
}
|
|
275
|
+
.fx-slot {
|
|
276
|
+
width: 3px;
|
|
277
|
+
height: 1.2em;
|
|
278
|
+
border: none;
|
|
279
|
+
background: #53b856;
|
|
280
|
+
padding: 0;
|
|
281
|
+
margin: 0 1px;
|
|
282
|
+
cursor: text;
|
|
283
|
+
border-radius: 1px;
|
|
284
|
+
vertical-align: middle;
|
|
285
|
+
}
|
|
286
|
+
.fx-slot.is-active {
|
|
287
|
+
animation: fx-blink 1s infinite;
|
|
288
|
+
}
|
|
289
|
+
.fx-slot:hover {
|
|
290
|
+
background: #77c75b;
|
|
291
|
+
}
|
|
292
|
+
`;
|
|
293
|
+
|
|
294
|
+
// src/editor.ts
|
|
295
|
+
var FormulaEditor = class {
|
|
296
|
+
state;
|
|
297
|
+
root;
|
|
298
|
+
onChange;
|
|
299
|
+
locale;
|
|
300
|
+
constructor(options) {
|
|
301
|
+
this.root = options.root;
|
|
302
|
+
this.state = options.initialState ?? (0, import_core.createEmptyState)();
|
|
303
|
+
this.onChange = options.onChange;
|
|
304
|
+
this.locale = options.locale ?? "en";
|
|
305
|
+
this.root.classList.add("fx-editor");
|
|
306
|
+
this.root.tabIndex = 0;
|
|
307
|
+
this.ensureStyles();
|
|
308
|
+
this.bindEvents();
|
|
309
|
+
this.render();
|
|
310
|
+
}
|
|
311
|
+
getState() {
|
|
312
|
+
return structuredClone(this.state);
|
|
313
|
+
}
|
|
314
|
+
getLocale() {
|
|
315
|
+
return this.locale;
|
|
316
|
+
}
|
|
317
|
+
setState(state) {
|
|
318
|
+
this.state = structuredClone(state);
|
|
319
|
+
this.render();
|
|
320
|
+
}
|
|
321
|
+
dispatch(command) {
|
|
322
|
+
this.state = (0, import_core.applyCommand)(this.state, command);
|
|
323
|
+
this.render();
|
|
324
|
+
this.onChange?.(this.getState());
|
|
325
|
+
}
|
|
326
|
+
ensureStyles() {
|
|
327
|
+
if (document.getElementById("fx-editor-styles")) return;
|
|
328
|
+
const style = document.createElement("style");
|
|
329
|
+
style.id = "fx-editor-styles";
|
|
330
|
+
style.textContent = editorStyles;
|
|
331
|
+
document.head.appendChild(style);
|
|
332
|
+
}
|
|
333
|
+
bindEvents() {
|
|
334
|
+
this.root.addEventListener("click", (event) => {
|
|
335
|
+
const target = event.target;
|
|
336
|
+
const pathValue = target.dataset.path;
|
|
337
|
+
if (pathValue === void 0) return;
|
|
338
|
+
const path = pathValue === "" ? [] : pathValue.split(".").map(Number);
|
|
339
|
+
this.moveSelection(path);
|
|
340
|
+
});
|
|
341
|
+
this.root.addEventListener("keydown", (event) => {
|
|
342
|
+
if (event.key === "Backspace") {
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
this.dispatch((0, import_core.backspace)());
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (event.key === "/") {
|
|
348
|
+
event.preventDefault();
|
|
349
|
+
this.dispatch((0, import_core.insertFraction)());
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (event.key === "^") {
|
|
353
|
+
event.preventDefault();
|
|
354
|
+
this.dispatch((0, import_core.insertSuperscript)());
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (event.key === "_") {
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
this.dispatch((0, import_core.insertSubscript)());
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (event.key === "r" && event.ctrlKey) {
|
|
363
|
+
event.preventDefault();
|
|
364
|
+
this.dispatch((0, import_core.insertSqrt)());
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (event.key === "(") {
|
|
368
|
+
event.preventDefault();
|
|
369
|
+
this.dispatch((0, import_core.insertFenced)("(", ")"));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
|
|
373
|
+
event.preventDefault();
|
|
374
|
+
this.dispatch((0, import_core.insertText)(event.key));
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
moveSelection(path) {
|
|
379
|
+
this.state = {
|
|
380
|
+
...this.state,
|
|
381
|
+
selection: {
|
|
382
|
+
anchor: [...path],
|
|
383
|
+
focus: [...path]
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
this.render();
|
|
387
|
+
this.onChange?.(this.getState());
|
|
388
|
+
}
|
|
389
|
+
render() {
|
|
390
|
+
this.root.innerHTML = renderInteractiveHtml(this.state.doc, this.state.selection.focus);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// src/formula-modal.ts
|
|
395
|
+
var import_core2 = require("@formulaxjs/core");
|
|
396
|
+
var import_kity_runtime = require("@formulaxjs/kity-runtime");
|
|
397
|
+
|
|
398
|
+
// src/formula-node.ts
|
|
399
|
+
var DEFAULT_FORMULA_ATTRIBUTE = "data-formulax-latex";
|
|
400
|
+
var FORMULA_FLAG_ATTRIBUTE = "data-formulax";
|
|
401
|
+
var DEFAULT_FORMULA_CLASS = "formulax-math";
|
|
402
|
+
function escapeAttribute(value) {
|
|
403
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
404
|
+
}
|
|
405
|
+
function escapeHtml(value) {
|
|
406
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
407
|
+
}
|
|
408
|
+
function createFormulaMarkup(latex, options = {}) {
|
|
409
|
+
const attributeName = options.attributeName ?? DEFAULT_FORMULA_ATTRIBUTE;
|
|
410
|
+
const className = options.className ?? DEFAULT_FORMULA_CLASS;
|
|
411
|
+
const displayClass = options.displayMode ? `${className} ${className}--block` : className;
|
|
412
|
+
const safeLatex = escapeAttribute(latex);
|
|
413
|
+
const cursorStyle = options.cursorStyle?.trim() || "pointer";
|
|
414
|
+
const extraAttributes = {
|
|
415
|
+
...options.extraAttributes ?? {},
|
|
416
|
+
style: mergeInlineStyles(
|
|
417
|
+
typeof options.extraAttributes?.style === "string" ? options.extraAttributes.style : "",
|
|
418
|
+
cursorStyle ? `cursor: ${cursorStyle}` : ""
|
|
419
|
+
)
|
|
420
|
+
};
|
|
421
|
+
const serializedAttributes = Object.entries(extraAttributes).filter(([, value]) => value !== null && value !== void 0 && value !== false).map(([key, value]) => value === true ? key : `${key}="${escapeAttribute(String(value))}"`);
|
|
422
|
+
return [
|
|
423
|
+
"<span",
|
|
424
|
+
` class="${escapeAttribute(displayClass)}"`,
|
|
425
|
+
` ${FORMULA_FLAG_ATTRIBUTE}="true"`,
|
|
426
|
+
` ${attributeName}="${safeLatex}"`,
|
|
427
|
+
` data-latex="${safeLatex}"`,
|
|
428
|
+
' contenteditable="false"',
|
|
429
|
+
' role="button"',
|
|
430
|
+
' tabindex="0"',
|
|
431
|
+
serializedAttributes.length ? ` ${serializedAttributes.join(" ")}` : "",
|
|
432
|
+
">",
|
|
433
|
+
options.renderHtml ?? `<span class="${escapeAttribute(className)}__render">${escapeHtml(latex || "\\square")}</span>`,
|
|
434
|
+
"</span>"
|
|
435
|
+
].join("");
|
|
436
|
+
}
|
|
437
|
+
function mergeInlineStyles(existingStyle, nextStyle) {
|
|
438
|
+
const existing = existingStyle.trim().replace(/;+\s*$/, "");
|
|
439
|
+
const next = nextStyle.trim().replace(/;+\s*$/, "");
|
|
440
|
+
if (!existing) return next;
|
|
441
|
+
if (!next) return existing;
|
|
442
|
+
return `${existing}; ${next}`;
|
|
443
|
+
}
|
|
444
|
+
function createFormulaElement(ownerDocument, latex, options = {}) {
|
|
445
|
+
const wrapper = ownerDocument.createElement("span");
|
|
446
|
+
wrapper.innerHTML = createFormulaMarkup(latex, options);
|
|
447
|
+
return wrapper.firstElementChild;
|
|
448
|
+
}
|
|
449
|
+
function replaceFormulaElement(target, latex, options = {}) {
|
|
450
|
+
const next = createFormulaElement(target.ownerDocument ?? document, latex, options);
|
|
451
|
+
if (!next) return null;
|
|
452
|
+
target.replaceWith(next);
|
|
453
|
+
return next;
|
|
454
|
+
}
|
|
455
|
+
function getFormulaLatexFromElement(element, attributeName = DEFAULT_FORMULA_ATTRIBUTE) {
|
|
456
|
+
return element.getAttribute(attributeName) ?? element.getAttribute("data-latex") ?? "";
|
|
457
|
+
}
|
|
458
|
+
function isFormulaElement(node) {
|
|
459
|
+
if (!node || typeof node !== "object") return false;
|
|
460
|
+
const element = node;
|
|
461
|
+
return typeof element.getAttribute === "function" && element.getAttribute(FORMULA_FLAG_ATTRIBUTE) === "true";
|
|
462
|
+
}
|
|
463
|
+
function findFormulaElement(node) {
|
|
464
|
+
if (!node) return null;
|
|
465
|
+
const element = node.nodeType === 1 ? node : node.parentElement;
|
|
466
|
+
return element?.closest?.(`[${FORMULA_FLAG_ATTRIBUTE}="true"]`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/formula-modal.ts
|
|
470
|
+
var EMPTY_FORMULA_PLACEHOLDER = "\\placeholder ";
|
|
471
|
+
var STYLE_ID = "fx-formula-modal-styles";
|
|
472
|
+
var formulaXModalStyles = `
|
|
473
|
+
.fx-formula-modal-open {
|
|
474
|
+
overflow: hidden;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.fx-formula-modal-root {
|
|
478
|
+
position: fixed;
|
|
479
|
+
inset: 0;
|
|
480
|
+
z-index: 2147483000;
|
|
481
|
+
display: flex;
|
|
482
|
+
align-items: center;
|
|
483
|
+
justify-content: center;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.fx-formula-modal-backdrop {
|
|
487
|
+
position: absolute;
|
|
488
|
+
inset: 0;
|
|
489
|
+
background: rgba(15, 23, 42, 0.48);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.fx-formula-modal {
|
|
493
|
+
--fx-formula-editor-body-height: 264px;
|
|
494
|
+
--fx-formula-workspace-height: 168px;
|
|
495
|
+
position: relative;
|
|
496
|
+
width: min(860px, calc(100vw - 32px));
|
|
497
|
+
height: auto;
|
|
498
|
+
max-height: calc(100vh - 32px);
|
|
499
|
+
background: #fff;
|
|
500
|
+
border-radius: 14px;
|
|
501
|
+
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.28);
|
|
502
|
+
display: flex;
|
|
503
|
+
flex-direction: column;
|
|
504
|
+
overflow: visible;
|
|
505
|
+
isolation: isolate;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.fx-formula-modal__header,
|
|
509
|
+
.fx-formula-modal__footer,
|
|
510
|
+
.fx-formula-modal__title,
|
|
511
|
+
.fx-formula-modal__close,
|
|
512
|
+
.fx-formula-modal__button,
|
|
513
|
+
.fx-formula-editor-loading,
|
|
514
|
+
.fx-formula-editor-error {
|
|
515
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.fx-formula-modal__header {
|
|
519
|
+
min-height: 56px;
|
|
520
|
+
padding: 0 20px;
|
|
521
|
+
border-bottom: 1px solid #e5e7eb;
|
|
522
|
+
display: flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
justify-content: space-between;
|
|
525
|
+
flex-shrink: 0;
|
|
526
|
+
position: relative;
|
|
527
|
+
z-index: 3;
|
|
528
|
+
background: #fff;
|
|
529
|
+
border-radius: 14px 14px 0 0;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.fx-formula-modal__title {
|
|
533
|
+
font-size: 16px;
|
|
534
|
+
font-weight: 650;
|
|
535
|
+
margin: 0;
|
|
536
|
+
color: #111827;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.fx-formula-modal__close {
|
|
540
|
+
border: 0;
|
|
541
|
+
background: transparent;
|
|
542
|
+
font-size: 24px;
|
|
543
|
+
line-height: 1;
|
|
544
|
+
cursor: pointer;
|
|
545
|
+
color: #6b7280;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.fx-formula-modal__body {
|
|
549
|
+
flex: 0 0 auto;
|
|
550
|
+
height: var(--fx-formula-editor-body-height);
|
|
551
|
+
padding: 0;
|
|
552
|
+
overflow: visible;
|
|
553
|
+
min-height: var(--fx-formula-editor-body-height);
|
|
554
|
+
position: relative;
|
|
555
|
+
z-index: 2;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.fx-formula-editor-host {
|
|
559
|
+
width: 100%;
|
|
560
|
+
height: var(--fx-formula-editor-body-height);
|
|
561
|
+
min-height: var(--fx-formula-editor-body-height);
|
|
562
|
+
overflow: visible;
|
|
563
|
+
position: relative;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.fx-formula-kity-host {
|
|
567
|
+
width: 100%;
|
|
568
|
+
height: var(--fx-formula-editor-body-height);
|
|
569
|
+
min-height: var(--fx-formula-editor-body-height);
|
|
570
|
+
overflow: visible;
|
|
571
|
+
position: relative;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.fx-formula-kity-host .kf-editor {
|
|
575
|
+
height: var(--fx-formula-editor-body-height) !important;
|
|
576
|
+
overflow: visible !important;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.fx-formula-kity-host .kf-editor-toolbar {
|
|
580
|
+
overflow: visible;
|
|
581
|
+
position: relative;
|
|
582
|
+
z-index: 20;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.fx-formula-kity-host .kf-editor-ui-button-mount-point,
|
|
586
|
+
.fx-formula-kity-host .kf-editor-ui-area-mount,
|
|
587
|
+
.fx-formula-kity-host .kf-editor-ui-box,
|
|
588
|
+
.fx-formula-kity-host .kf-editor-ui-list {
|
|
589
|
+
z-index: 1000;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.fx-formula-kity-host .kf-editor-edit-area,
|
|
593
|
+
.fx-formula-kity-host .kf-editor-canvas-container {
|
|
594
|
+
min-height: var(--fx-formula-workspace-height);
|
|
595
|
+
height: var(--fx-formula-workspace-height);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.fx-formula-kity-host .kf-editor-edit-area {
|
|
599
|
+
flex: 0 0 auto;
|
|
600
|
+
overflow: hidden;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.fx-formula-kity-host .kf-editor,
|
|
604
|
+
.fx-formula-kity-host .kf-editor svg text,
|
|
605
|
+
.fx-formula-kity-host .kf-editor-ui-area-item-text,
|
|
606
|
+
.fx-formula-kity-host .kf-editor-ui-box-item-text,
|
|
607
|
+
.fx-formula-kity-host .kf-editor-ui-box-item-val,
|
|
608
|
+
.formulax-math__render {
|
|
609
|
+
font-family: "KF AMS MAIN", "Cambria Math", "Latin Modern Math", "Times New Roman", serif !important;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.fx-formula-kity-host .kf-editor-ui-box-item-content,
|
|
613
|
+
.fx-formula-kity-host .kf-editor-ui-box-item-val {
|
|
614
|
+
min-width: 32px;
|
|
615
|
+
min-height: 32px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.fx-formula-kity-host .kf-editor-ui-box-item-val svg,
|
|
619
|
+
.fx-formula-kity-host .kf-editor-ui-box-item-val img,
|
|
620
|
+
.fx-formula-kity-host .kf-editor-ui-area-item-img,
|
|
621
|
+
.fx-formula-kity-host .kf-editor-ui-area-item-text {
|
|
622
|
+
display: block;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.fx-formula-editor-loading {
|
|
626
|
+
height: var(--fx-formula-editor-body-height);
|
|
627
|
+
padding: 24px;
|
|
628
|
+
color: #4b5563;
|
|
629
|
+
text-align: center;
|
|
630
|
+
display: flex;
|
|
631
|
+
align-items: center;
|
|
632
|
+
justify-content: center;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.fx-formula-editor-error {
|
|
636
|
+
padding: 24px;
|
|
637
|
+
color: #dc2626;
|
|
638
|
+
font-size: 14px;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.fx-formula-editor-error pre {
|
|
642
|
+
white-space: pre-wrap;
|
|
643
|
+
word-break: break-all;
|
|
644
|
+
color: #991b1b;
|
|
645
|
+
background: #fef2f2;
|
|
646
|
+
border: 1px solid #fecaca;
|
|
647
|
+
border-radius: 8px;
|
|
648
|
+
padding: 12px;
|
|
649
|
+
margin-top: 8px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.fx-formula-modal__footer {
|
|
653
|
+
min-height: 64px;
|
|
654
|
+
padding: 12px 20px;
|
|
655
|
+
border-top: 1px solid #e5e7eb;
|
|
656
|
+
display: flex;
|
|
657
|
+
align-items: center;
|
|
658
|
+
justify-content: flex-end;
|
|
659
|
+
gap: 12px;
|
|
660
|
+
flex-shrink: 0;
|
|
661
|
+
position: relative;
|
|
662
|
+
z-index: 1;
|
|
663
|
+
background: #fff;
|
|
664
|
+
border-radius: 0 0 14px 14px;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.fx-formula-modal__button {
|
|
668
|
+
appearance: none;
|
|
669
|
+
border: 1px solid #d1d5db;
|
|
670
|
+
background: #fff;
|
|
671
|
+
color: #111827;
|
|
672
|
+
border-radius: 8px;
|
|
673
|
+
padding: 8px 14px;
|
|
674
|
+
font-size: 14px;
|
|
675
|
+
cursor: pointer;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.fx-formula-modal__button--primary {
|
|
679
|
+
border-color: #2563eb;
|
|
680
|
+
background: #2563eb;
|
|
681
|
+
color: #fff;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.formulax-math {
|
|
685
|
+
display: inline-flex;
|
|
686
|
+
align-items: center;
|
|
687
|
+
vertical-align: middle;
|
|
688
|
+
line-height: 1;
|
|
689
|
+
padding: 0 2px;
|
|
690
|
+
margin: 0 1px;
|
|
691
|
+
border-radius: 3px;
|
|
692
|
+
background: transparent;
|
|
693
|
+
cursor: pointer;
|
|
694
|
+
user-select: none;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.formulax-math:hover {
|
|
698
|
+
outline: 1px solid rgba(37, 99, 235, 0.35);
|
|
699
|
+
background: rgba(37, 99, 235, 0.06);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.formulax-math__svg {
|
|
703
|
+
display: inline-block;
|
|
704
|
+
flex: 0 0 auto;
|
|
705
|
+
max-width: 100%;
|
|
706
|
+
vertical-align: -0.35em;
|
|
707
|
+
pointer-events: none;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.formulax-math__image {
|
|
711
|
+
display: inline-block;
|
|
712
|
+
max-width: 100%;
|
|
713
|
+
height: auto;
|
|
714
|
+
vertical-align: middle;
|
|
715
|
+
pointer-events: none;
|
|
716
|
+
}
|
|
717
|
+
`;
|
|
718
|
+
function ensureFormulaXModalStyles(doc = document) {
|
|
719
|
+
if (doc.getElementById(STYLE_ID)) return;
|
|
720
|
+
const style = doc.createElement("style");
|
|
721
|
+
style.id = STYLE_ID;
|
|
722
|
+
style.textContent = formulaXModalStyles;
|
|
723
|
+
doc.head.appendChild(style);
|
|
724
|
+
}
|
|
725
|
+
function mountFormulaXKityEditor(root, options = {}) {
|
|
726
|
+
let destroyed = false;
|
|
727
|
+
let latestLatex = options.initialLatex ?? "";
|
|
728
|
+
let handle = null;
|
|
729
|
+
const initialLatex = latestLatex.trim() ? latestLatex : EMPTY_FORMULA_PLACEHOLDER;
|
|
730
|
+
root.classList.add("fx-formula-kity-host");
|
|
731
|
+
root.innerHTML = `
|
|
732
|
+
<div class="fx-formula-editor-loading" role="status" aria-live="polite">
|
|
733
|
+
Loading FormulaX editor...
|
|
734
|
+
</div>
|
|
735
|
+
`;
|
|
736
|
+
const readyPromise = (0, import_kity_runtime.mountKityEditor)(root, {
|
|
737
|
+
initialLatex,
|
|
738
|
+
height: options.height ?? "100%",
|
|
739
|
+
autofocus: options.autofocus ?? true,
|
|
740
|
+
assets: options.assets,
|
|
741
|
+
render: {
|
|
742
|
+
fontsize: options.render?.fontsize ?? 40
|
|
743
|
+
}
|
|
744
|
+
}).then((nextHandle) => {
|
|
745
|
+
if (destroyed) {
|
|
746
|
+
nextHandle.destroy();
|
|
747
|
+
throw new Error("FormulaX editor mount cancelled");
|
|
748
|
+
}
|
|
749
|
+
handle = nextHandle;
|
|
750
|
+
return nextHandle;
|
|
751
|
+
}).catch((error) => {
|
|
752
|
+
console.error("[FormulaX] Failed to load FormulaX editor:", error);
|
|
753
|
+
if (!destroyed) {
|
|
754
|
+
root.innerHTML = `
|
|
755
|
+
<div class="fx-formula-editor-error">
|
|
756
|
+
Failed to load FormulaX editor.
|
|
757
|
+
<pre>${escapeHtml(error instanceof Error ? error.message : String(error))}</pre>
|
|
758
|
+
</div>
|
|
759
|
+
`;
|
|
760
|
+
}
|
|
761
|
+
throw error;
|
|
762
|
+
});
|
|
763
|
+
const getCurrentLatex = async () => {
|
|
764
|
+
const readyHandle = handle ?? await readyPromise;
|
|
765
|
+
const latex = await tryReadLatexFromKityHandle(readyHandle);
|
|
766
|
+
if (latex !== null) {
|
|
767
|
+
latestLatex = latex;
|
|
768
|
+
}
|
|
769
|
+
return latestLatex;
|
|
770
|
+
};
|
|
771
|
+
return {
|
|
772
|
+
root,
|
|
773
|
+
getLatex: getCurrentLatex,
|
|
774
|
+
async getState() {
|
|
775
|
+
const latex = await getCurrentLatex();
|
|
776
|
+
try {
|
|
777
|
+
return {
|
|
778
|
+
...(0, import_core2.createEmptyState)(),
|
|
779
|
+
doc: (0, import_core2.parseLatex)(latex)
|
|
780
|
+
};
|
|
781
|
+
} catch {
|
|
782
|
+
return (0, import_core2.createEmptyState)();
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
async getRenderHtml() {
|
|
786
|
+
await readyPromise;
|
|
787
|
+
await waitForFormulaSvgLayout(root);
|
|
788
|
+
return renderCurrentFormulaAsSvgHtml(root);
|
|
789
|
+
},
|
|
790
|
+
destroy() {
|
|
791
|
+
if (destroyed) return;
|
|
792
|
+
destroyed = true;
|
|
793
|
+
void readyPromise.then((readyHandle) => readyHandle.destroy()).catch(() => void 0);
|
|
794
|
+
root.innerHTML = "";
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
async function tryReadLatexFromKityHandle(handle) {
|
|
799
|
+
try {
|
|
800
|
+
let isEmpty = false;
|
|
801
|
+
handle.ready(function ready() {
|
|
802
|
+
const result = this.execCommand("content.is.empty");
|
|
803
|
+
isEmpty = result === true;
|
|
804
|
+
});
|
|
805
|
+
if (isEmpty) {
|
|
806
|
+
return "";
|
|
807
|
+
}
|
|
808
|
+
} catch {
|
|
809
|
+
}
|
|
810
|
+
const candidates = [
|
|
811
|
+
"get.source",
|
|
812
|
+
"getSource",
|
|
813
|
+
"getLatex",
|
|
814
|
+
"get.latex",
|
|
815
|
+
"get.content",
|
|
816
|
+
"getContent"
|
|
817
|
+
];
|
|
818
|
+
for (const command of candidates) {
|
|
819
|
+
try {
|
|
820
|
+
let value = null;
|
|
821
|
+
handle.ready(function ready() {
|
|
822
|
+
value = this.execCommand(command);
|
|
823
|
+
});
|
|
824
|
+
if (typeof value === "string" && value.trim()) {
|
|
825
|
+
return value;
|
|
826
|
+
}
|
|
827
|
+
if (value && typeof value === "object" && "latex" in value) {
|
|
828
|
+
const latex = value.latex;
|
|
829
|
+
if (typeof latex === "string" && latex.trim()) {
|
|
830
|
+
return latex;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
} catch {
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
function renderCurrentFormulaAsSvgHtml(root) {
|
|
839
|
+
const svg = findFormulaSvg(root);
|
|
840
|
+
if (!svg) {
|
|
841
|
+
return "";
|
|
842
|
+
}
|
|
843
|
+
return serializeSvgForInsertion(svg);
|
|
844
|
+
}
|
|
845
|
+
async function waitForFormulaSvgLayout(root) {
|
|
846
|
+
const doc = root.ownerDocument ?? document;
|
|
847
|
+
const view = doc.defaultView ?? window;
|
|
848
|
+
await waitForDocumentFonts(doc);
|
|
849
|
+
let previous = readRenderedFormulaBox(root);
|
|
850
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
851
|
+
await waitForAnimationFrame(view);
|
|
852
|
+
const current = readRenderedFormulaBox(root);
|
|
853
|
+
if (previous && current && areSvgBoxesClose(previous, current)) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
previous = current;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function findFormulaSvg(root) {
|
|
860
|
+
return root.querySelector(
|
|
861
|
+
".kf-editor-edit-area svg, .kf-editor-canvas-container svg, svg"
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
function readRenderedFormulaBox(root) {
|
|
865
|
+
const svg = findFormulaSvg(root);
|
|
866
|
+
if (!svg) {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
return getInlineSvgContent(svg)?.box ?? readSvgBox(svg);
|
|
870
|
+
}
|
|
871
|
+
function areSvgBoxesClose(left, right) {
|
|
872
|
+
return Math.abs(left.x - right.x) < 0.01 && Math.abs(left.y - right.y) < 0.01 && Math.abs(left.width - right.width) < 0.01 && Math.abs(left.height - right.height) < 0.01;
|
|
873
|
+
}
|
|
874
|
+
async function waitForDocumentFonts(doc) {
|
|
875
|
+
if (!doc.fonts?.ready) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
await doc.fonts.ready;
|
|
880
|
+
} catch {
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function waitForAnimationFrame(view) {
|
|
884
|
+
return new Promise((resolve) => {
|
|
885
|
+
view.requestAnimationFrame(() => resolve());
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
function serializeSvgForInsertion(svg) {
|
|
889
|
+
const content = getInlineSvgContent(svg);
|
|
890
|
+
const inlineViewport = content ? createInlineSvgViewport(content.box) : null;
|
|
891
|
+
const clone = content && inlineViewport ? createInlineSvgClone(svg, content, inlineViewport) : svg.cloneNode(true);
|
|
892
|
+
uniquifySvgIds(clone);
|
|
893
|
+
sizeSvgForInlineDisplay(clone, svg, inlineViewport);
|
|
894
|
+
clone.removeAttribute("id");
|
|
895
|
+
clone.removeAttribute("xmlns");
|
|
896
|
+
clone.removeAttribute("xmlns:xlink");
|
|
897
|
+
clone.setAttribute("class", mergeClassNames(clone.getAttribute("class"), "formulax-math__svg"));
|
|
898
|
+
clone.setAttribute("focusable", "false");
|
|
899
|
+
clone.setAttribute("aria-hidden", "true");
|
|
900
|
+
clone.setAttribute("preserveAspectRatio", clone.getAttribute("preserveAspectRatio") || "xMinYMin meet");
|
|
901
|
+
return new XMLSerializer().serializeToString(clone);
|
|
902
|
+
}
|
|
903
|
+
function getInlineSvgContent(svg) {
|
|
904
|
+
const candidates = [
|
|
905
|
+
'[data-root="true"] > g[data-type="kf-editor-exp-content-box"]',
|
|
906
|
+
'g[data-type="kf-editor-exp-content-box"]',
|
|
907
|
+
'g[data-type="kf-container"]',
|
|
908
|
+
"svg > g, g"
|
|
909
|
+
];
|
|
910
|
+
for (const selector of candidates) {
|
|
911
|
+
const content = svg.querySelector(selector);
|
|
912
|
+
const rootSpace = content ? readSvgBoxInRootSpace(content) : null;
|
|
913
|
+
if (content && rootSpace) {
|
|
914
|
+
return {
|
|
915
|
+
root: content,
|
|
916
|
+
box: rootSpace.box,
|
|
917
|
+
matrix: rootSpace.matrix
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
function readSvgBoxInRootSpace(element) {
|
|
924
|
+
const box = readSvgBox(element);
|
|
925
|
+
const matrix = getSvgRootSpaceMatrix(element);
|
|
926
|
+
if (!box || !matrix) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
const points = [
|
|
930
|
+
{ x: box.x, y: box.y },
|
|
931
|
+
{ x: box.x + box.width, y: box.y },
|
|
932
|
+
{ x: box.x, y: box.y + box.height },
|
|
933
|
+
{ x: box.x + box.width, y: box.y + box.height }
|
|
934
|
+
].map((point) => ({
|
|
935
|
+
x: matrix.a * point.x + matrix.c * point.y + matrix.e,
|
|
936
|
+
y: matrix.b * point.x + matrix.d * point.y + matrix.f
|
|
937
|
+
}));
|
|
938
|
+
const xs = points.map((point) => point.x);
|
|
939
|
+
const ys = points.map((point) => point.y);
|
|
940
|
+
const x = Math.min(...xs);
|
|
941
|
+
const y = Math.min(...ys);
|
|
942
|
+
const width = Math.max(...xs) - x;
|
|
943
|
+
const height = Math.max(...ys) - y;
|
|
944
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
box: { x, y, width, height },
|
|
949
|
+
matrix
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function getSvgRootSpaceMatrix(element) {
|
|
953
|
+
const elementMatrix = typeof element.getCTM === "function" ? element.getCTM() : null;
|
|
954
|
+
const rootMatrix = typeof element.ownerSVGElement?.getCTM === "function" ? element.ownerSVGElement.getCTM() : null;
|
|
955
|
+
if (!elementMatrix) {
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
return rootMatrix ? multiplySvgMatrices(invertSvgMatrix(rootMatrix), elementMatrix) : toSvgMatrixLike(elementMatrix);
|
|
959
|
+
}
|
|
960
|
+
function invertSvgMatrix(matrix) {
|
|
961
|
+
const determinant = matrix.a * matrix.d - matrix.b * matrix.c;
|
|
962
|
+
if (!Number.isFinite(determinant) || determinant === 0) {
|
|
963
|
+
return {
|
|
964
|
+
a: 1,
|
|
965
|
+
b: 0,
|
|
966
|
+
c: 0,
|
|
967
|
+
d: 1,
|
|
968
|
+
e: 0,
|
|
969
|
+
f: 0
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
return {
|
|
973
|
+
a: matrix.d / determinant,
|
|
974
|
+
b: -matrix.b / determinant,
|
|
975
|
+
c: -matrix.c / determinant,
|
|
976
|
+
d: matrix.a / determinant,
|
|
977
|
+
e: (matrix.c * matrix.f - matrix.d * matrix.e) / determinant,
|
|
978
|
+
f: (matrix.b * matrix.e - matrix.a * matrix.f) / determinant
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function multiplySvgMatrices(left, right) {
|
|
982
|
+
return {
|
|
983
|
+
a: left.a * right.a + left.c * right.b,
|
|
984
|
+
b: left.b * right.a + left.d * right.b,
|
|
985
|
+
c: left.a * right.c + left.c * right.d,
|
|
986
|
+
d: left.b * right.c + left.d * right.d,
|
|
987
|
+
e: left.a * right.e + left.c * right.f + left.e,
|
|
988
|
+
f: left.b * right.e + left.d * right.f + left.f
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function toSvgMatrixLike(matrix) {
|
|
992
|
+
return {
|
|
993
|
+
a: matrix.a,
|
|
994
|
+
b: matrix.b,
|
|
995
|
+
c: matrix.c,
|
|
996
|
+
d: matrix.d,
|
|
997
|
+
e: matrix.e,
|
|
998
|
+
f: matrix.f
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function readSvgBox(element) {
|
|
1002
|
+
if (typeof element.getBBox !== "function") {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
const box = element.getBBox();
|
|
1007
|
+
if (!Number.isFinite(box.width) || !Number.isFinite(box.height) || box.width <= 0 || box.height <= 0) {
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
return {
|
|
1011
|
+
x: box.x,
|
|
1012
|
+
y: box.y,
|
|
1013
|
+
width: box.width,
|
|
1014
|
+
height: box.height
|
|
1015
|
+
};
|
|
1016
|
+
} catch {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function createInlineSvgViewport(contentBox) {
|
|
1021
|
+
const edgePadding = Math.max(0.5, Math.min(contentBox.width, contentBox.height) * 6e-3);
|
|
1022
|
+
const inset = edgePadding / 2;
|
|
1023
|
+
return {
|
|
1024
|
+
x: contentBox.x - inset,
|
|
1025
|
+
y: contentBox.y - inset,
|
|
1026
|
+
width: contentBox.width + edgePadding,
|
|
1027
|
+
height: contentBox.height + edgePadding
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
function createInlineSvgClone(source, content, viewport) {
|
|
1031
|
+
const clone = source.cloneNode(false);
|
|
1032
|
+
const ownerDocument = source.ownerDocument;
|
|
1033
|
+
copySvgRootAttributes(source, clone);
|
|
1034
|
+
clone.setAttribute(
|
|
1035
|
+
"viewBox",
|
|
1036
|
+
`0 0 ${roundLength(viewport.width)} ${roundLength(viewport.height)}`
|
|
1037
|
+
);
|
|
1038
|
+
Array.from(source.children).forEach((child) => {
|
|
1039
|
+
if (child.tagName.toLowerCase() === "defs") {
|
|
1040
|
+
clone.appendChild(child.cloneNode(true));
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
const wrapper = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1044
|
+
wrapper.setAttribute(
|
|
1045
|
+
"transform",
|
|
1046
|
+
`translate(${roundLength(-viewport.x)} ${roundLength(-viewport.y)})`
|
|
1047
|
+
);
|
|
1048
|
+
const flattened = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1049
|
+
flattened.setAttribute(
|
|
1050
|
+
"transform",
|
|
1051
|
+
`matrix(${roundLength(content.matrix.a)} ${roundLength(content.matrix.b)} ${roundLength(content.matrix.c)} ${roundLength(content.matrix.d)} ${roundLength(content.matrix.e)} ${roundLength(content.matrix.f)})`
|
|
1052
|
+
);
|
|
1053
|
+
flattened.appendChild(content.root.cloneNode(true));
|
|
1054
|
+
wrapper.appendChild(flattened);
|
|
1055
|
+
clone.appendChild(wrapper);
|
|
1056
|
+
return clone;
|
|
1057
|
+
}
|
|
1058
|
+
function copySvgRootAttributes(source, target) {
|
|
1059
|
+
const excluded = /* @__PURE__ */ new Set([
|
|
1060
|
+
"id",
|
|
1061
|
+
"width",
|
|
1062
|
+
"height",
|
|
1063
|
+
"viewBox",
|
|
1064
|
+
"class",
|
|
1065
|
+
"focusable",
|
|
1066
|
+
"aria-hidden",
|
|
1067
|
+
"xmlns",
|
|
1068
|
+
"xmlns:xlink"
|
|
1069
|
+
]);
|
|
1070
|
+
Array.from(source.attributes).forEach((attribute) => {
|
|
1071
|
+
if (excluded.has(attribute.name)) return;
|
|
1072
|
+
target.setAttribute(attribute.name, attribute.value);
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
function sizeSvgForInlineDisplay(clone, source, viewport) {
|
|
1076
|
+
const viewBox = clone.viewBox?.baseVal;
|
|
1077
|
+
const rect = source.getBoundingClientRect();
|
|
1078
|
+
const width = viewport?.width || viewBox?.width || rect.width || Number(clone.getAttribute("width")) || 1;
|
|
1079
|
+
const height = viewport?.height || viewBox?.height || rect.height || Number(clone.getAttribute("height")) || 1;
|
|
1080
|
+
const ratio = Math.max(0.1, width / Math.max(1, height));
|
|
1081
|
+
const inlineHeightEm = 0.875;
|
|
1082
|
+
const inlineWidthEm = Math.min(40, Math.max(0.75, ratio * inlineHeightEm));
|
|
1083
|
+
clone.setAttribute("width", roundLength(width));
|
|
1084
|
+
clone.setAttribute("height", roundLength(height));
|
|
1085
|
+
clone.setAttribute(
|
|
1086
|
+
"style",
|
|
1087
|
+
mergeInlineStyles2(
|
|
1088
|
+
clone.getAttribute("style"),
|
|
1089
|
+
`width:${roundLength(inlineWidthEm)}em`,
|
|
1090
|
+
`height:${inlineHeightEm}em`
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
function roundLength(value) {
|
|
1095
|
+
return String(Math.round(value * 1e3) / 1e3);
|
|
1096
|
+
}
|
|
1097
|
+
function uniquifySvgIds(svg) {
|
|
1098
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1099
|
+
const prefix = `fx-${randomIdPrefix()}-`;
|
|
1100
|
+
const elementsWithId = svg.querySelectorAll("[id]");
|
|
1101
|
+
elementsWithId.forEach((element) => {
|
|
1102
|
+
const id = element.getAttribute("id");
|
|
1103
|
+
if (!id) return;
|
|
1104
|
+
const nextId = `${prefix}${id}`;
|
|
1105
|
+
idMap.set(id, nextId);
|
|
1106
|
+
element.setAttribute("id", nextId);
|
|
1107
|
+
});
|
|
1108
|
+
if (!idMap.size) return;
|
|
1109
|
+
svg.querySelectorAll("*").forEach((element) => {
|
|
1110
|
+
Array.from(element.attributes).forEach((attribute) => {
|
|
1111
|
+
const nextValue = rewriteSvgReferences(attribute.value, idMap);
|
|
1112
|
+
if (nextValue !== attribute.value) {
|
|
1113
|
+
element.setAttribute(attribute.name, nextValue);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
function randomIdPrefix() {
|
|
1119
|
+
return Math.random().toString(36).slice(2, 5).padEnd(3, "0");
|
|
1120
|
+
}
|
|
1121
|
+
function rewriteSvgReferences(value, idMap) {
|
|
1122
|
+
let nextValue = value;
|
|
1123
|
+
idMap.forEach((nextId, id) => {
|
|
1124
|
+
nextValue = nextValue.replaceAll(`#${id}`, `#${nextId}`).replaceAll(`url(${id})`, `url(${nextId})`).replaceAll(`url(#${id})`, `url(#${nextId})`);
|
|
1125
|
+
});
|
|
1126
|
+
return nextValue;
|
|
1127
|
+
}
|
|
1128
|
+
function mergeClassNames(...values) {
|
|
1129
|
+
return values.flatMap((value) => value?.split(/\s+/) ?? []).filter(Boolean).filter((value, index, list) => list.indexOf(value) === index).join(" ");
|
|
1130
|
+
}
|
|
1131
|
+
function mergeInlineStyles2(...values) {
|
|
1132
|
+
return values.flatMap((value) => value?.split(";") ?? []).map((value) => value.trim()).filter(Boolean).join("; ");
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// src/i18n.ts
|
|
1136
|
+
var translations = {
|
|
1137
|
+
en: {
|
|
1138
|
+
equation: "Equation",
|
|
1139
|
+
structures: "Structures",
|
|
1140
|
+
symbols: "Symbols",
|
|
1141
|
+
matrices: "Matrices",
|
|
1142
|
+
templates: "Templates",
|
|
1143
|
+
insert: "Insert",
|
|
1144
|
+
greek: "Greek",
|
|
1145
|
+
operators: "Operators",
|
|
1146
|
+
relations: "Relations",
|
|
1147
|
+
fraction: "Fraction",
|
|
1148
|
+
superscript: "Superscript",
|
|
1149
|
+
subscript: "Subscript",
|
|
1150
|
+
squareRoot: "Square Root",
|
|
1151
|
+
parentheses: "Parentheses",
|
|
1152
|
+
plusMinus: "Plus Minus",
|
|
1153
|
+
multiply: "Multiply",
|
|
1154
|
+
divide: "Divide",
|
|
1155
|
+
dot: "Dot",
|
|
1156
|
+
union: "Union",
|
|
1157
|
+
intersect: "Intersection",
|
|
1158
|
+
lessOrEqual: "Less or Equal",
|
|
1159
|
+
greaterOrEqual: "Greater or Equal",
|
|
1160
|
+
notEqual: "Not Equal",
|
|
1161
|
+
approximate: "Approximate",
|
|
1162
|
+
infinity: "Infinity",
|
|
1163
|
+
arrow: "Arrow",
|
|
1164
|
+
limit: "Limit",
|
|
1165
|
+
sine: "Sine",
|
|
1166
|
+
logarithm: "Logarithm",
|
|
1167
|
+
matrix: "Matrix",
|
|
1168
|
+
summation: "Summation",
|
|
1169
|
+
integral: "Integral",
|
|
1170
|
+
placeholder: "WPS-inspired ribbon layout. Some tiles are placeholders for future SDK features."
|
|
1171
|
+
},
|
|
1172
|
+
zh: {
|
|
1173
|
+
equation: "\u516C\u5F0F",
|
|
1174
|
+
structures: "\u7ED3\u6784",
|
|
1175
|
+
symbols: "\u7B26\u53F7",
|
|
1176
|
+
matrices: "\u77E9\u9635",
|
|
1177
|
+
templates: "\u6A21\u677F",
|
|
1178
|
+
insert: "\u63D2\u5165",
|
|
1179
|
+
greek: "\u5E0C\u814A\u5B57\u6BCD",
|
|
1180
|
+
operators: "\u8FD0\u7B97\u7B26",
|
|
1181
|
+
relations: "\u5173\u7CFB",
|
|
1182
|
+
fraction: "\u5206\u6570",
|
|
1183
|
+
superscript: "\u4E0A\u6807",
|
|
1184
|
+
subscript: "\u4E0B\u6807",
|
|
1185
|
+
squareRoot: "\u5E73\u65B9\u6839",
|
|
1186
|
+
parentheses: "\u62EC\u53F7",
|
|
1187
|
+
plusMinus: "\u52A0\u51CF",
|
|
1188
|
+
multiply: "\u4E58",
|
|
1189
|
+
divide: "\u9664",
|
|
1190
|
+
dot: "\u70B9\u4E58",
|
|
1191
|
+
union: "\u5E76\u96C6",
|
|
1192
|
+
intersect: "\u4EA4\u96C6",
|
|
1193
|
+
lessOrEqual: "\u5C0F\u4E8E\u7B49\u4E8E",
|
|
1194
|
+
greaterOrEqual: "\u5927\u4E8E\u7B49\u4E8E",
|
|
1195
|
+
notEqual: "\u4E0D\u7B49\u4E8E",
|
|
1196
|
+
approximate: "\u7EA6\u7B49\u4E8E",
|
|
1197
|
+
infinity: "\u65E0\u7A77",
|
|
1198
|
+
arrow: "\u7BAD\u5934",
|
|
1199
|
+
limit: "\u6781\u9650",
|
|
1200
|
+
sine: "\u6B63\u5F26",
|
|
1201
|
+
logarithm: "\u5BF9\u6570",
|
|
1202
|
+
matrix: "\u77E9\u9635",
|
|
1203
|
+
summation: "\u6C42\u548C",
|
|
1204
|
+
integral: "\u79EF\u5206",
|
|
1205
|
+
placeholder: "WPS \u98CE\u683C\u7684\u5DE5\u5177\u680F\u5E03\u5C40\u3002\u90E8\u5206\u6309\u94AE\u4ECD\u662F\u672A\u6765 SDK \u529F\u80FD\u7684\u5360\u4F4D\u9879\u3002"
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
function t(locale, key) {
|
|
1209
|
+
return translations[locale][key] ?? translations.en[key];
|
|
1210
|
+
}
|
|
1211
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1212
|
+
0 && (module.exports = {
|
|
1213
|
+
DEFAULT_FORMULA_ATTRIBUTE,
|
|
1214
|
+
DEFAULT_FORMULA_CLASS,
|
|
1215
|
+
FORMULA_FLAG_ATTRIBUTE,
|
|
1216
|
+
FormulaEditor,
|
|
1217
|
+
createFormulaElement,
|
|
1218
|
+
createFormulaMarkup,
|
|
1219
|
+
editorStyles,
|
|
1220
|
+
ensureFormulaXModalStyles,
|
|
1221
|
+
escapeAttribute,
|
|
1222
|
+
escapeHtml,
|
|
1223
|
+
findFormulaElement,
|
|
1224
|
+
formulaXModalStyles,
|
|
1225
|
+
getFormulaLatexFromElement,
|
|
1226
|
+
isFormulaElement,
|
|
1227
|
+
mountFormulaXKityEditor,
|
|
1228
|
+
renderInteractiveHtml,
|
|
1229
|
+
replaceFormulaElement,
|
|
1230
|
+
serializeSvgForInsertion,
|
|
1231
|
+
t,
|
|
1232
|
+
translations
|
|
1233
|
+
});
|
|
1234
|
+
//# sourceMappingURL=index.cjs.map
|