@yurikilian/lex4 1.4.1 → 1.5.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/README.md +6 -4
- package/dist/ast/inline-mapper.d.ts.map +1 -1
- package/dist/ast/types.d.ts +1 -1
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/components/CanvasControls.d.ts.map +1 -1
- package/dist/components/HeaderFooterToggle.d.ts +1 -0
- package/dist/components/HeaderFooterToggle.d.ts.map +1 -1
- package/dist/components/Lex4Editor.d.ts.map +1 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/context/document-context.d.ts +1 -0
- package/dist/context/document-context.d.ts.map +1 -1
- package/dist/context/document-provider.d.ts.map +1 -1
- package/dist/context/toolbar-config.d.ts +18 -0
- package/dist/context/toolbar-config.d.ts.map +1 -0
- package/dist/extensions/variables-extension.d.ts.map +1 -1
- package/dist/i18n/defaults.d.ts.map +1 -1
- package/dist/i18n/pt-BR.d.ts.map +1 -1
- package/dist/i18n/types.d.ts +12 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lex4-editor.cjs +730 -157
- package/dist/lex4-editor.cjs.map +1 -1
- package/dist/lex4-editor.js +714 -141
- package/dist/lex4-editor.js.map +1 -1
- package/dist/lexical/commands/block-commands.d.ts +5 -0
- package/dist/lexical/commands/block-commands.d.ts.map +1 -0
- package/dist/lexical/theme.d.ts.map +1 -1
- package/dist/lexical/utils/import-document-content.d.ts +4 -0
- package/dist/lexical/utils/import-document-content.d.ts.map +1 -0
- package/dist/style.css +51 -23
- package/dist/types/editor-handle.d.ts +2 -0
- package/dist/types/editor-handle.d.ts.map +1 -1
- package/dist/types/editor-props.d.ts +16 -0
- package/dist/types/editor-props.d.ts.map +1 -1
- package/dist/utils/text-style.d.ts +7 -0
- package/dist/utils/text-style.d.ts.map +1 -0
- package/dist/variables/variable-formatting.d.ts +11 -0
- package/dist/variables/variable-formatting.d.ts.map +1 -0
- package/dist/variables/variable-node.d.ts +10 -2
- package/dist/variables/variable-node.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/lex4-editor.js
CHANGED
|
@@ -3,16 +3,18 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
5
5
|
import React, { createContext, useContext, useMemo, useReducer, useState, useRef, useCallback, useEffect, forwardRef, createElement, useImperativeHandle } from "react";
|
|
6
|
-
import { $getRoot, $createRangeSelectionFromDom, $getSelection, $isRangeSelection, $isTextNode, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $
|
|
6
|
+
import { $getRoot, $createRangeSelectionFromDom, $getSelection, $isRangeSelection, $isTextNode, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $createParagraphNode, $applyNodeReplacement, DecoratorNode, KEY_BACKSPACE_COMMAND, COMMAND_PRIORITY_LOW, KEY_DELETE_COMMAND, $isNodeSelection, $getNodeByKey, $selectAll, SELECTION_CHANGE_COMMAND, KEY_TAB_COMMAND, $isElementNode, $isParagraphNode, $setSelection, FOCUS_COMMAND, $splitNode, $getNearestNodeFromDOMNode, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_DOWN_COMMAND, PASTE_COMMAND, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_CRITICAL, $insertNodes, $createLineBreakNode, $createTextNode, createCommand, COMMAND_PRIORITY_EDITOR } from "lexical";
|
|
7
7
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
8
|
-
import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListNode, ListItemNode, $isListNode } from "@lexical/list";
|
|
8
|
+
import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListNode, ListItemNode, $isListNode, $createListItemNode, $createListNode } from "@lexical/list";
|
|
9
|
+
import { $setBlocksType } from "@lexical/selection";
|
|
10
|
+
import { $createHeadingNode, $isHeadingNode, HeadingNode, QuoteNode, $createQuoteNode } from "@lexical/rich-text";
|
|
11
|
+
import { useLexicalNodeSelection } from "@lexical/react/useLexicalNodeSelection";
|
|
9
12
|
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
10
13
|
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
11
14
|
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
|
12
15
|
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
13
16
|
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
14
17
|
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
|
|
15
|
-
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
|
|
16
18
|
function createEmptyPage(id) {
|
|
17
19
|
return {
|
|
18
20
|
id: id ?? crypto.randomUUID(),
|
|
@@ -471,6 +473,15 @@ const DEFAULT_TRANSLATIONS = {
|
|
|
471
473
|
bulletList: "Bullet List",
|
|
472
474
|
indent: "Indent",
|
|
473
475
|
outdent: "Outdent",
|
|
476
|
+
history: "History",
|
|
477
|
+
blockType: "Block type",
|
|
478
|
+
paragraph: "Paragraph",
|
|
479
|
+
heading1: "Heading 1",
|
|
480
|
+
heading2: "Heading 2",
|
|
481
|
+
heading3: "Heading 3",
|
|
482
|
+
heading4: "Heading 4",
|
|
483
|
+
heading5: "Heading 5",
|
|
484
|
+
heading6: "Heading 6",
|
|
474
485
|
openHistory: "Open History",
|
|
475
486
|
closeHistory: "Close History"
|
|
476
487
|
},
|
|
@@ -502,7 +513,9 @@ const DEFAULT_TRANSLATIONS = {
|
|
|
502
513
|
indentedContent: "Indented content",
|
|
503
514
|
outdentedContent: "Outdented content",
|
|
504
515
|
fontChanged: "Font changed to {{value}}",
|
|
505
|
-
fontSizeChanged: "Font size changed to {{value}}pt"
|
|
516
|
+
fontSizeChanged: "Font size changed to {{value}}pt",
|
|
517
|
+
blockTypeChanged: "Block type changed to {{value}}",
|
|
518
|
+
insertedDocumentContent: "Inserted document content"
|
|
506
519
|
}
|
|
507
520
|
},
|
|
508
521
|
variables: {
|
|
@@ -602,6 +615,15 @@ const PT_BR_TRANSLATIONS = {
|
|
|
602
615
|
bulletList: "Lista com Marcadores",
|
|
603
616
|
indent: "Aumentar Recuo",
|
|
604
617
|
outdent: "Diminuir Recuo",
|
|
618
|
+
history: "Histórico",
|
|
619
|
+
blockType: "Tipo de bloco",
|
|
620
|
+
paragraph: "Parágrafo",
|
|
621
|
+
heading1: "Título 1",
|
|
622
|
+
heading2: "Título 2",
|
|
623
|
+
heading3: "Título 3",
|
|
624
|
+
heading4: "Título 4",
|
|
625
|
+
heading5: "Título 5",
|
|
626
|
+
heading6: "Título 6",
|
|
605
627
|
openHistory: "Abrir Histórico",
|
|
606
628
|
closeHistory: "Fechar Histórico"
|
|
607
629
|
},
|
|
@@ -633,7 +655,9 @@ const PT_BR_TRANSLATIONS = {
|
|
|
633
655
|
indentedContent: "Conteúdo recuado",
|
|
634
656
|
outdentedContent: "Recuo reduzido",
|
|
635
657
|
fontChanged: "Fonte alterada para {{value}}",
|
|
636
|
-
fontSizeChanged: "Tamanho da fonte alterado para {{value}}pt"
|
|
658
|
+
fontSizeChanged: "Tamanho da fonte alterado para {{value}}pt",
|
|
659
|
+
blockTypeChanged: "Tipo de bloco alterado para {{value}}",
|
|
660
|
+
insertedDocumentContent: "Conteúdo do documento inserido"
|
|
637
661
|
}
|
|
638
662
|
},
|
|
639
663
|
variables: {
|
|
@@ -1284,6 +1308,7 @@ const DocumentProvider = ({
|
|
|
1284
1308
|
activePageId,
|
|
1285
1309
|
setActivePageId,
|
|
1286
1310
|
activeEditor: activeEditorRef.current,
|
|
1311
|
+
activeCaretPosition: activeCaretPositionRef.current,
|
|
1287
1312
|
consumePendingCaretPosition,
|
|
1288
1313
|
consumePendingFocusAtEnd,
|
|
1289
1314
|
requestFocusAtEnd,
|
|
@@ -1890,6 +1915,36 @@ const HistorySidebar = () => {
|
|
|
1890
1915
|
}
|
|
1891
1916
|
);
|
|
1892
1917
|
};
|
|
1918
|
+
const DEFAULT_TOOLBAR_CONTROL_CONFIG = {
|
|
1919
|
+
visible: true,
|
|
1920
|
+
showLabel: true
|
|
1921
|
+
};
|
|
1922
|
+
const DEFAULT_TOOLBAR_CONFIG = {
|
|
1923
|
+
history: DEFAULT_TOOLBAR_CONTROL_CONFIG,
|
|
1924
|
+
variables: DEFAULT_TOOLBAR_CONTROL_CONFIG,
|
|
1925
|
+
headerFooter: DEFAULT_TOOLBAR_CONTROL_CONFIG
|
|
1926
|
+
};
|
|
1927
|
+
const ToolbarConfigContext = createContext(DEFAULT_TOOLBAR_CONFIG);
|
|
1928
|
+
function resolveControlConfig(config) {
|
|
1929
|
+
return {
|
|
1930
|
+
visible: (config == null ? void 0 : config.visible) ?? true,
|
|
1931
|
+
showLabel: (config == null ? void 0 : config.showLabel) ?? true
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function normalizeToolbarConfig(config) {
|
|
1935
|
+
return {
|
|
1936
|
+
history: resolveControlConfig(config == null ? void 0 : config.history),
|
|
1937
|
+
variables: resolveControlConfig(config == null ? void 0 : config.variables),
|
|
1938
|
+
headerFooter: resolveControlConfig(config == null ? void 0 : config.headerFooter)
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
const ToolbarConfigProvider = ({ toolbar, children }) => {
|
|
1942
|
+
const resolvedConfig = useMemo(() => normalizeToolbarConfig(toolbar), [toolbar]);
|
|
1943
|
+
return /* @__PURE__ */ jsx(ToolbarConfigContext.Provider, { value: resolvedConfig, children });
|
|
1944
|
+
};
|
|
1945
|
+
function useToolbarConfig() {
|
|
1946
|
+
return useContext(ToolbarConfigContext);
|
|
1947
|
+
}
|
|
1893
1948
|
function resolveExtensions(extensions) {
|
|
1894
1949
|
const resolved = {
|
|
1895
1950
|
nodes: [],
|
|
@@ -2022,6 +2077,7 @@ const SUPPORTED_FONT_SIZES = [
|
|
|
2022
2077
|
48,
|
|
2023
2078
|
72
|
|
2024
2079
|
];
|
|
2080
|
+
const DEFAULT_FONT_SIZE = 12;
|
|
2025
2081
|
function applyFontSize(editor, size) {
|
|
2026
2082
|
editor.update(() => {
|
|
2027
2083
|
const selection = $getSelection();
|
|
@@ -2072,9 +2128,366 @@ function indentContent(editor) {
|
|
|
2072
2128
|
function outdentContent(editor) {
|
|
2073
2129
|
editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, void 0);
|
|
2074
2130
|
}
|
|
2131
|
+
function setBlockType(editor, blockType) {
|
|
2132
|
+
editor.update(() => {
|
|
2133
|
+
const selection = $getSelection();
|
|
2134
|
+
if (!$isRangeSelection(selection)) {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
if (blockType === "paragraph") {
|
|
2138
|
+
$setBlocksType(selection, () => $createParagraphNode());
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
$setBlocksType(selection, () => $createHeadingNode(blockType));
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
function getActiveBlockType(editor) {
|
|
2145
|
+
let blockType = "paragraph";
|
|
2146
|
+
editor.getEditorState().read(() => {
|
|
2147
|
+
const selection = $getSelection();
|
|
2148
|
+
if (!$isRangeSelection(selection)) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
const anchorNode = selection.anchor.getNode();
|
|
2152
|
+
const topLevelElement = anchorNode.getTopLevelElementOrThrow();
|
|
2153
|
+
if ($isHeadingNode(topLevelElement)) {
|
|
2154
|
+
blockType = topLevelElement.getTag();
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
return blockType;
|
|
2158
|
+
}
|
|
2159
|
+
function extractStyleValue(style, property) {
|
|
2160
|
+
const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2161
|
+
const match = style.match(new RegExp(`${escapedProperty}:\\s*([^;]+)`));
|
|
2162
|
+
return match ? match[1].trim().replace(/['"]/g, "") : void 0;
|
|
2163
|
+
}
|
|
2164
|
+
function mergeStyleDeclaration(existingStyle, property, value) {
|
|
2165
|
+
const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2166
|
+
const stripped = existingStyle.replace(
|
|
2167
|
+
new RegExp(`${escapedProperty}:\\s*[^;]+;?\\s*`, "g"),
|
|
2168
|
+
""
|
|
2169
|
+
).trim();
|
|
2170
|
+
const declaration = `${property}: ${value}`;
|
|
2171
|
+
return stripped ? `${stripped}; ${declaration}` : declaration;
|
|
2172
|
+
}
|
|
2173
|
+
function extractFontFamilyFromStyle(style) {
|
|
2174
|
+
return extractStyleValue(style, "font-family");
|
|
2175
|
+
}
|
|
2176
|
+
function extractFontSizePtFromStyle(style) {
|
|
2177
|
+
const value = extractStyleValue(style, "font-size");
|
|
2178
|
+
if (!value) {
|
|
2179
|
+
return void 0;
|
|
2180
|
+
}
|
|
2181
|
+
const match = value.match(/^(\d+(?:\.\d+)?)\s*pt$/);
|
|
2182
|
+
return match ? parseFloat(match[1]) : void 0;
|
|
2183
|
+
}
|
|
2184
|
+
function mergeFontFamilyStyle(existingStyle, fontFamily) {
|
|
2185
|
+
return mergeStyleDeclaration(existingStyle, "font-family", fontFamily);
|
|
2186
|
+
}
|
|
2187
|
+
function mergeFontSizeStyle(existingStyle, size) {
|
|
2188
|
+
return mergeStyleDeclaration(existingStyle, "font-size", `${size}pt`);
|
|
2189
|
+
}
|
|
2190
|
+
const EMPTY_CONTEXT = {
|
|
2191
|
+
definitions: [],
|
|
2192
|
+
refreshDefinitions: () => {
|
|
2193
|
+
},
|
|
2194
|
+
getDefinition: () => void 0
|
|
2195
|
+
};
|
|
2196
|
+
const VariableContext = createContext(EMPTY_CONTEXT);
|
|
2197
|
+
const VariableProvider = ({
|
|
2198
|
+
initialDefinitions = [],
|
|
2199
|
+
children
|
|
2200
|
+
}) => {
|
|
2201
|
+
const [definitions, setDefinitions] = useState(initialDefinitions);
|
|
2202
|
+
const refresh = useCallback((newDefinitions) => {
|
|
2203
|
+
setDefinitions(newDefinitions);
|
|
2204
|
+
}, []);
|
|
2205
|
+
const getDefinition = useCallback(
|
|
2206
|
+
(key) => {
|
|
2207
|
+
return definitions.find((d) => d.key === key);
|
|
2208
|
+
},
|
|
2209
|
+
[definitions]
|
|
2210
|
+
);
|
|
2211
|
+
const value = useMemo(
|
|
2212
|
+
() => ({ definitions, refreshDefinitions: refresh, getDefinition }),
|
|
2213
|
+
[definitions, refresh, getDefinition]
|
|
2214
|
+
);
|
|
2215
|
+
return /* @__PURE__ */ jsx(VariableContext.Provider, { value, children });
|
|
2216
|
+
};
|
|
2217
|
+
function useVariables() {
|
|
2218
|
+
return useContext(VariableContext);
|
|
2219
|
+
}
|
|
2220
|
+
class VariableNode extends DecoratorNode {
|
|
2221
|
+
constructor(variableKey, format = 0, style = "", key) {
|
|
2222
|
+
super(key);
|
|
2223
|
+
__publicField(this, "__variableKey");
|
|
2224
|
+
__publicField(this, "__format");
|
|
2225
|
+
__publicField(this, "__style");
|
|
2226
|
+
this.__variableKey = variableKey;
|
|
2227
|
+
this.__format = format;
|
|
2228
|
+
this.__style = style;
|
|
2229
|
+
}
|
|
2230
|
+
static getType() {
|
|
2231
|
+
return "variable-node";
|
|
2232
|
+
}
|
|
2233
|
+
static clone(node) {
|
|
2234
|
+
return new VariableNode(node.__variableKey, node.__format, node.__style, node.__key);
|
|
2235
|
+
}
|
|
2236
|
+
getVariableKey() {
|
|
2237
|
+
return this.getLatest().__variableKey;
|
|
2238
|
+
}
|
|
2239
|
+
getFormat() {
|
|
2240
|
+
return this.getLatest().__format;
|
|
2241
|
+
}
|
|
2242
|
+
setFormat(format) {
|
|
2243
|
+
const writable = this.getWritable();
|
|
2244
|
+
writable.__format = format;
|
|
2245
|
+
return writable;
|
|
2246
|
+
}
|
|
2247
|
+
getStyle() {
|
|
2248
|
+
return this.getLatest().__style;
|
|
2249
|
+
}
|
|
2250
|
+
setStyle(style) {
|
|
2251
|
+
const writable = this.getWritable();
|
|
2252
|
+
writable.__style = style;
|
|
2253
|
+
return writable;
|
|
2254
|
+
}
|
|
2255
|
+
// -- Serialization --
|
|
2256
|
+
static importJSON(serializedNode) {
|
|
2257
|
+
return $createVariableNode(
|
|
2258
|
+
serializedNode.variableKey,
|
|
2259
|
+
serializedNode.format ?? 0,
|
|
2260
|
+
serializedNode.style ?? ""
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
exportJSON() {
|
|
2264
|
+
return {
|
|
2265
|
+
type: "variable-node",
|
|
2266
|
+
version: 1,
|
|
2267
|
+
variableKey: this.__variableKey,
|
|
2268
|
+
format: this.__format,
|
|
2269
|
+
style: this.__style
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
// -- DOM --
|
|
2273
|
+
createDOM() {
|
|
2274
|
+
const span = document.createElement("span");
|
|
2275
|
+
span.className = "lex4-variable";
|
|
2276
|
+
span.setAttribute("data-variable-key", this.__variableKey);
|
|
2277
|
+
span.setAttribute("data-testid", `variable-${this.__variableKey}`);
|
|
2278
|
+
span.contentEditable = "false";
|
|
2279
|
+
return span;
|
|
2280
|
+
}
|
|
2281
|
+
updateDOM() {
|
|
2282
|
+
return false;
|
|
2283
|
+
}
|
|
2284
|
+
exportDOM() {
|
|
2285
|
+
const span = document.createElement("span");
|
|
2286
|
+
span.setAttribute("data-variable-key", this.__variableKey);
|
|
2287
|
+
span.textContent = `{{${this.__variableKey}}}`;
|
|
2288
|
+
return { element: span };
|
|
2289
|
+
}
|
|
2290
|
+
static importDOM() {
|
|
2291
|
+
return null;
|
|
2292
|
+
}
|
|
2293
|
+
// -- Behavior --
|
|
2294
|
+
isInline() {
|
|
2295
|
+
return true;
|
|
2296
|
+
}
|
|
2297
|
+
isKeyboardSelectable() {
|
|
2298
|
+
return true;
|
|
2299
|
+
}
|
|
2300
|
+
getTextContent() {
|
|
2301
|
+
return `{{${this.__variableKey}}}`;
|
|
2302
|
+
}
|
|
2303
|
+
// -- Rendering --
|
|
2304
|
+
decorate() {
|
|
2305
|
+
return /* @__PURE__ */ jsx(
|
|
2306
|
+
VariableChip,
|
|
2307
|
+
{
|
|
2308
|
+
nodeKey: this.__key,
|
|
2309
|
+
variableKey: this.__variableKey,
|
|
2310
|
+
format: this.__format,
|
|
2311
|
+
styleValue: this.__style
|
|
2312
|
+
}
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
function VariableChip({
|
|
2317
|
+
nodeKey,
|
|
2318
|
+
variableKey,
|
|
2319
|
+
format,
|
|
2320
|
+
styleValue
|
|
2321
|
+
}) {
|
|
2322
|
+
const { getDefinition } = useVariables();
|
|
2323
|
+
const [editor] = useLexicalComposerContext();
|
|
2324
|
+
const [isSelected, setSelected, clearOtherSelections] = useLexicalNodeSelection(nodeKey);
|
|
2325
|
+
const def = getDefinition(variableKey);
|
|
2326
|
+
const label = (def == null ? void 0 : def.label) ?? variableKey;
|
|
2327
|
+
const group = def == null ? void 0 : def.group;
|
|
2328
|
+
const style = useMemo(() => {
|
|
2329
|
+
const fontFamily = extractFontFamilyFromStyle(styleValue);
|
|
2330
|
+
const fontSize = extractFontSizePtFromStyle(styleValue);
|
|
2331
|
+
return {
|
|
2332
|
+
...fontFamily ? { fontFamily } : {},
|
|
2333
|
+
...fontSize ? { fontSize: `${fontSize}pt` } : {}
|
|
2334
|
+
};
|
|
2335
|
+
}, [styleValue]);
|
|
2336
|
+
const className = [
|
|
2337
|
+
"lex4-variable-chip",
|
|
2338
|
+
isSelected && "lex4-variable-chip-selected",
|
|
2339
|
+
format & 1 ? "lex4-text-bold" : "",
|
|
2340
|
+
format & 2 ? "lex4-text-italic" : "",
|
|
2341
|
+
format & 8 ? "lex4-text-underline" : "",
|
|
2342
|
+
format & 4 ? "lex4-text-strikethrough" : ""
|
|
2343
|
+
].filter(Boolean).join(" ");
|
|
2344
|
+
const handleClick = useCallback((event) => {
|
|
2345
|
+
event.preventDefault();
|
|
2346
|
+
if (!event.shiftKey) {
|
|
2347
|
+
clearOtherSelections();
|
|
2348
|
+
}
|
|
2349
|
+
setSelected(!isSelected);
|
|
2350
|
+
}, [clearOtherSelections, isSelected, setSelected]);
|
|
2351
|
+
useEffect(() => {
|
|
2352
|
+
const removeSelectedNodes = () => {
|
|
2353
|
+
editor.update(() => {
|
|
2354
|
+
const selection = $getSelection();
|
|
2355
|
+
if (!$isNodeSelection(selection)) {
|
|
2356
|
+
const node = $getNodeByKey(nodeKey);
|
|
2357
|
+
if ($isVariableNode(node)) {
|
|
2358
|
+
node.remove();
|
|
2359
|
+
}
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
for (const node of selection.getNodes()) {
|
|
2363
|
+
if ($isVariableNode(node)) {
|
|
2364
|
+
node.remove();
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
};
|
|
2369
|
+
const unregisterBackspace = editor.registerCommand(
|
|
2370
|
+
KEY_BACKSPACE_COMMAND,
|
|
2371
|
+
() => {
|
|
2372
|
+
if (!isSelected) {
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
removeSelectedNodes();
|
|
2376
|
+
return true;
|
|
2377
|
+
},
|
|
2378
|
+
COMMAND_PRIORITY_LOW
|
|
2379
|
+
);
|
|
2380
|
+
const unregisterDelete = editor.registerCommand(
|
|
2381
|
+
KEY_DELETE_COMMAND,
|
|
2382
|
+
() => {
|
|
2383
|
+
if (!isSelected) {
|
|
2384
|
+
return false;
|
|
2385
|
+
}
|
|
2386
|
+
removeSelectedNodes();
|
|
2387
|
+
return true;
|
|
2388
|
+
},
|
|
2389
|
+
COMMAND_PRIORITY_LOW
|
|
2390
|
+
);
|
|
2391
|
+
return () => {
|
|
2392
|
+
unregisterBackspace();
|
|
2393
|
+
unregisterDelete();
|
|
2394
|
+
};
|
|
2395
|
+
}, [editor, isSelected, nodeKey]);
|
|
2396
|
+
return /* @__PURE__ */ jsx(
|
|
2397
|
+
"span",
|
|
2398
|
+
{
|
|
2399
|
+
className,
|
|
2400
|
+
"data-testid": `variable-chip-${variableKey}`,
|
|
2401
|
+
"data-variable-group": group,
|
|
2402
|
+
title: variableKey,
|
|
2403
|
+
style,
|
|
2404
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
2405
|
+
onClick: handleClick,
|
|
2406
|
+
children: label
|
|
2407
|
+
}
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
function $createVariableNode(variableKey, format = 0, style = "") {
|
|
2411
|
+
return $applyNodeReplacement(new VariableNode(variableKey, format, style));
|
|
2412
|
+
}
|
|
2413
|
+
function $isVariableNode(node) {
|
|
2414
|
+
return node instanceof VariableNode;
|
|
2415
|
+
}
|
|
2416
|
+
const FORMAT_MASKS = {
|
|
2417
|
+
bold: 1,
|
|
2418
|
+
italic: 2,
|
|
2419
|
+
strikethrough: 4,
|
|
2420
|
+
underline: 8
|
|
2421
|
+
};
|
|
2422
|
+
function withSelectedVariableNodes(editor, updater) {
|
|
2423
|
+
let updated = false;
|
|
2424
|
+
editor.update(() => {
|
|
2425
|
+
const selection = $getSelection();
|
|
2426
|
+
if (!$isNodeSelection(selection)) {
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
const nodes = selection.getNodes().filter($isVariableNode);
|
|
2430
|
+
if (nodes.length === 0) {
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
updater(nodes);
|
|
2434
|
+
updated = true;
|
|
2435
|
+
});
|
|
2436
|
+
return updated;
|
|
2437
|
+
}
|
|
2438
|
+
function getSelectedVariableNodes(editor) {
|
|
2439
|
+
let nodes = [];
|
|
2440
|
+
editor.getEditorState().read(() => {
|
|
2441
|
+
const selection = $getSelection();
|
|
2442
|
+
if (!$isNodeSelection(selection)) {
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
nodes = selection.getNodes().filter($isVariableNode);
|
|
2446
|
+
});
|
|
2447
|
+
return nodes;
|
|
2448
|
+
}
|
|
2449
|
+
function toggleSelectedVariableFormat(editor, format) {
|
|
2450
|
+
const mask = FORMAT_MASKS[format];
|
|
2451
|
+
if (!mask) {
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2454
|
+
return withSelectedVariableNodes(editor, (nodes) => {
|
|
2455
|
+
const shouldEnable = nodes.some((node) => (node.getFormat() & mask) === 0);
|
|
2456
|
+
for (const node of nodes) {
|
|
2457
|
+
const nextFormat = shouldEnable ? node.getFormat() | mask : node.getFormat() & ~mask;
|
|
2458
|
+
node.setFormat(nextFormat);
|
|
2459
|
+
}
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
function applyFontFamilyToSelectedVariables(editor, fontFamily) {
|
|
2463
|
+
return withSelectedVariableNodes(editor, (nodes) => {
|
|
2464
|
+
for (const node of nodes) {
|
|
2465
|
+
node.setStyle(mergeFontFamilyStyle(node.getStyle(), fontFamily));
|
|
2466
|
+
}
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
function applyFontSizeToSelectedVariables(editor, size) {
|
|
2470
|
+
return withSelectedVariableNodes(editor, (nodes) => {
|
|
2471
|
+
for (const node of nodes) {
|
|
2472
|
+
node.setStyle(mergeFontSizeStyle(node.getStyle(), size));
|
|
2473
|
+
}
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
function readSelectedVariableFormatting(editor) {
|
|
2477
|
+
const firstNode = getSelectedVariableNodes(editor)[0];
|
|
2478
|
+
if (!firstNode) {
|
|
2479
|
+
return {};
|
|
2480
|
+
}
|
|
2481
|
+
const style = firstNode.getStyle();
|
|
2482
|
+
return {
|
|
2483
|
+
fontFamily: extractFontFamilyFromStyle(style),
|
|
2484
|
+
fontSize: extractFontSizePtFromStyle(style)
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2075
2487
|
const HeaderFooterToggle = ({
|
|
2076
2488
|
enabled,
|
|
2077
|
-
onToggle
|
|
2489
|
+
onToggle,
|
|
2490
|
+
showLabel = true
|
|
2078
2491
|
}) => {
|
|
2079
2492
|
const t = useTranslations();
|
|
2080
2493
|
return /* @__PURE__ */ jsxs(
|
|
@@ -2084,18 +2497,20 @@ const HeaderFooterToggle = ({
|
|
|
2084
2497
|
"data-testid": "header-footer-toggle",
|
|
2085
2498
|
children: [
|
|
2086
2499
|
/* @__PURE__ */ jsx(FileText, { size: 14, className: "lex4-hf-toggle-icon" }),
|
|
2087
|
-
/* @__PURE__ */ jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
|
|
2500
|
+
showLabel && /* @__PURE__ */ jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
|
|
2088
2501
|
/* @__PURE__ */ jsx(
|
|
2089
2502
|
"button",
|
|
2090
2503
|
{
|
|
2091
2504
|
type: "button",
|
|
2092
2505
|
role: "switch",
|
|
2093
2506
|
"aria-checked": enabled,
|
|
2507
|
+
"aria-label": t.headerFooter.label,
|
|
2094
2508
|
onMouseDown: (e) => e.preventDefault(),
|
|
2095
2509
|
onClick: () => onToggle(!enabled),
|
|
2096
2510
|
className: "lex4-hf-switch",
|
|
2097
2511
|
style: { backgroundColor: enabled ? "var(--color-primary)" : "var(--color-muted)" },
|
|
2098
2512
|
"data-testid": "header-footer-switch",
|
|
2513
|
+
title: t.headerFooter.label,
|
|
2099
2514
|
children: /* @__PURE__ */ jsx("span", { className: "lex4-hf-switch-knob" })
|
|
2100
2515
|
}
|
|
2101
2516
|
)
|
|
@@ -2259,6 +2674,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
|
|
|
2259
2674
|
const MenuDivider = () => /* @__PURE__ */ jsx("div", { className: "lex4-settings-divider" });
|
|
2260
2675
|
const CanvasControls = () => {
|
|
2261
2676
|
const { document: document2, dispatch, activePageId, runHistoryAction } = useDocument();
|
|
2677
|
+
const toolbarConfig = useToolbarConfig();
|
|
2262
2678
|
const t = useTranslations();
|
|
2263
2679
|
const runCanvasAction = useCallback((label, callback) => {
|
|
2264
2680
|
runHistoryAction(
|
|
@@ -2325,12 +2741,16 @@ const CanvasControls = () => {
|
|
|
2325
2741
|
dispatch({ type: "SET_PAGE_COUNTER_MODE", mode });
|
|
2326
2742
|
});
|
|
2327
2743
|
}, [dispatch, runCanvasAction, t.history.actions.pageCounterSet]);
|
|
2744
|
+
if (!toolbarConfig.headerFooter.visible) {
|
|
2745
|
+
return null;
|
|
2746
|
+
}
|
|
2328
2747
|
return /* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-group lex4-toolbar-group-gap", "data-testid": "header-footer-controls", children: [
|
|
2329
2748
|
/* @__PURE__ */ jsx(
|
|
2330
2749
|
HeaderFooterToggle,
|
|
2331
2750
|
{
|
|
2332
2751
|
enabled: document2.headerFooterEnabled,
|
|
2333
|
-
onToggle: handleToggle
|
|
2752
|
+
onToggle: handleToggle,
|
|
2753
|
+
showLabel: toolbarConfig.headerFooter.showLabel
|
|
2334
2754
|
}
|
|
2335
2755
|
),
|
|
2336
2756
|
document2.headerFooterEnabled && /* @__PURE__ */ jsx(
|
|
@@ -2363,7 +2783,17 @@ const Toolbar = () => {
|
|
|
2363
2783
|
undo
|
|
2364
2784
|
} = useDocument();
|
|
2365
2785
|
const { toolbarItems, toolbarEndItems } = useExtensions();
|
|
2786
|
+
const toolbarConfig = useToolbarConfig();
|
|
2366
2787
|
const t = useTranslations();
|
|
2788
|
+
const [activeBlockType, setActiveBlockType] = useState("paragraph");
|
|
2789
|
+
const [activeFontFamily, setActiveFontFamily] = useState("Calibri");
|
|
2790
|
+
const [activeFontSize, setActiveFontSize] = useState(DEFAULT_FONT_SIZE);
|
|
2791
|
+
const normalizeFontFamily = useCallback((fontFamily) => {
|
|
2792
|
+
if (fontFamily && SUPPORTED_FONTS.includes(fontFamily)) {
|
|
2793
|
+
return fontFamily;
|
|
2794
|
+
}
|
|
2795
|
+
return "Calibri";
|
|
2796
|
+
}, []);
|
|
2367
2797
|
const withBodySelection = useCallback(
|
|
2368
2798
|
(editor, action) => {
|
|
2369
2799
|
editor.update(() => {
|
|
@@ -2399,27 +2829,91 @@ const Toolbar = () => {
|
|
|
2399
2829
|
},
|
|
2400
2830
|
[runHistoryAction]
|
|
2401
2831
|
);
|
|
2832
|
+
useEffect(() => {
|
|
2833
|
+
if (!activeEditor) {
|
|
2834
|
+
setActiveBlockType("paragraph");
|
|
2835
|
+
setActiveFontFamily("Calibri");
|
|
2836
|
+
setActiveFontSize(DEFAULT_FONT_SIZE);
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
const updateSelectionState = () => {
|
|
2840
|
+
const selectedVariables = getSelectedVariableNodes(activeEditor);
|
|
2841
|
+
if (selectedVariables.length > 0) {
|
|
2842
|
+
const formatting = readSelectedVariableFormatting(activeEditor);
|
|
2843
|
+
setActiveBlockType("paragraph");
|
|
2844
|
+
setActiveFontFamily(normalizeFontFamily(formatting.fontFamily));
|
|
2845
|
+
setActiveFontSize(formatting.fontSize ?? DEFAULT_FONT_SIZE);
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
setActiveBlockType(getActiveBlockType(activeEditor));
|
|
2849
|
+
let nextFontFamily = "Calibri";
|
|
2850
|
+
let nextFontSize = DEFAULT_FONT_SIZE;
|
|
2851
|
+
activeEditor.getEditorState().read(() => {
|
|
2852
|
+
const selection = $getSelection();
|
|
2853
|
+
if (!$isRangeSelection(selection)) {
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
const textNode = selection.getNodes().find($isTextNode);
|
|
2857
|
+
if (!textNode) {
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
const style = textNode.getStyle();
|
|
2861
|
+
nextFontFamily = normalizeFontFamily(extractFontFamilyFromStyle(style));
|
|
2862
|
+
nextFontSize = extractFontSizePtFromStyle(style) ?? DEFAULT_FONT_SIZE;
|
|
2863
|
+
});
|
|
2864
|
+
setActiveFontFamily(nextFontFamily);
|
|
2865
|
+
setActiveFontSize(nextFontSize);
|
|
2866
|
+
};
|
|
2867
|
+
updateSelectionState();
|
|
2868
|
+
const unregisterSelectionChange = activeEditor.registerCommand(
|
|
2869
|
+
SELECTION_CHANGE_COMMAND,
|
|
2870
|
+
() => {
|
|
2871
|
+
updateSelectionState();
|
|
2872
|
+
return false;
|
|
2873
|
+
},
|
|
2874
|
+
COMMAND_PRIORITY_LOW
|
|
2875
|
+
);
|
|
2876
|
+
const unregisterUpdateListener = activeEditor.registerUpdateListener(() => {
|
|
2877
|
+
updateSelectionState();
|
|
2878
|
+
});
|
|
2879
|
+
return () => {
|
|
2880
|
+
unregisterSelectionChange();
|
|
2881
|
+
unregisterUpdateListener();
|
|
2882
|
+
};
|
|
2883
|
+
}, [activeEditor, normalizeFontFamily]);
|
|
2402
2884
|
const handleBold = useCallback(() => {
|
|
2403
2885
|
debug("toolbar", `bold (globalSelection=${globalSelectionActive}, editors=${editorRegistry.all().length}, hasEditor=${!!activeEditor})`);
|
|
2404
2886
|
runToolbarAction(t.history.actions.boldApplied, () => {
|
|
2887
|
+
if (activeEditor && toggleSelectedVariableFormat(activeEditor, "bold")) {
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2405
2890
|
applyToBodyEditors(toggleBold);
|
|
2406
2891
|
});
|
|
2407
2892
|
}, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction, t.history.actions.boldApplied]);
|
|
2408
2893
|
const handleItalic = useCallback(() => {
|
|
2409
2894
|
debug("toolbar", `italic (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
|
|
2410
2895
|
runToolbarAction(t.history.actions.italicApplied, () => {
|
|
2896
|
+
if (activeEditor && toggleSelectedVariableFormat(activeEditor, "italic")) {
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2411
2899
|
applyToBodyEditors(toggleItalic);
|
|
2412
2900
|
});
|
|
2413
2901
|
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.italicApplied]);
|
|
2414
2902
|
const handleUnderline = useCallback(() => {
|
|
2415
2903
|
debug("toolbar", `underline (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
|
|
2416
2904
|
runToolbarAction(t.history.actions.underlineApplied, () => {
|
|
2905
|
+
if (activeEditor && toggleSelectedVariableFormat(activeEditor, "underline")) {
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2417
2908
|
applyToBodyEditors(toggleUnderline);
|
|
2418
2909
|
});
|
|
2419
2910
|
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.underlineApplied]);
|
|
2420
2911
|
const handleStrikethrough = useCallback(() => {
|
|
2421
2912
|
debug("toolbar", `strikethrough (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
|
|
2422
2913
|
runToolbarAction(t.history.actions.strikethroughApplied, () => {
|
|
2914
|
+
if (activeEditor && toggleSelectedVariableFormat(activeEditor, "strikethrough")) {
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2423
2917
|
applyToBodyEditors(toggleStrikethrough);
|
|
2424
2918
|
});
|
|
2425
2919
|
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.strikethroughApplied]);
|
|
@@ -2465,20 +2959,59 @@ const Toolbar = () => {
|
|
|
2465
2959
|
}, [applyToBodyEditors, runToolbarAction, t.history.actions.outdentedContent]);
|
|
2466
2960
|
const handleFontChange = useCallback(
|
|
2467
2961
|
(e) => {
|
|
2468
|
-
|
|
2469
|
-
|
|
2962
|
+
const fontFamily = e.target.value;
|
|
2963
|
+
runToolbarAction(interpolate(t.history.actions.fontChanged, { value: fontFamily }), () => {
|
|
2964
|
+
if (activeEditor && applyFontFamilyToSelectedVariables(activeEditor, fontFamily)) {
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
applyToBodyEditors((editor) => applyFontFamily(editor, fontFamily));
|
|
2470
2968
|
});
|
|
2471
2969
|
},
|
|
2472
|
-
[applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
|
|
2970
|
+
[activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
|
|
2473
2971
|
);
|
|
2474
2972
|
const handleFontSizeChange = useCallback(
|
|
2475
2973
|
(e) => {
|
|
2476
2974
|
const size = parseInt(e.target.value, 10);
|
|
2477
2975
|
runToolbarAction(interpolate(t.history.actions.fontSizeChanged, { value: String(size) }), () => {
|
|
2976
|
+
if (activeEditor && applyFontSizeToSelectedVariables(activeEditor, size)) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2478
2979
|
applyToBodyEditors((editor) => applyFontSize(editor, size));
|
|
2479
2980
|
});
|
|
2480
2981
|
},
|
|
2481
|
-
[applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
|
|
2982
|
+
[activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
|
|
2983
|
+
);
|
|
2984
|
+
const handleBlockTypeChange = useCallback(
|
|
2985
|
+
(e) => {
|
|
2986
|
+
const blockType = e.target.value;
|
|
2987
|
+
const labelMap = {
|
|
2988
|
+
paragraph: t.toolbar.paragraph,
|
|
2989
|
+
h1: t.toolbar.heading1,
|
|
2990
|
+
h2: t.toolbar.heading2,
|
|
2991
|
+
h3: t.toolbar.heading3,
|
|
2992
|
+
h4: t.toolbar.heading4,
|
|
2993
|
+
h5: t.toolbar.heading5,
|
|
2994
|
+
h6: t.toolbar.heading6
|
|
2995
|
+
};
|
|
2996
|
+
runToolbarAction(
|
|
2997
|
+
interpolate(t.history.actions.blockTypeChanged, { value: labelMap[blockType] }),
|
|
2998
|
+
() => {
|
|
2999
|
+
applyToBodyEditors((editor) => setBlockType(editor, blockType));
|
|
3000
|
+
}
|
|
3001
|
+
);
|
|
3002
|
+
},
|
|
3003
|
+
[
|
|
3004
|
+
applyToBodyEditors,
|
|
3005
|
+
runToolbarAction,
|
|
3006
|
+
t.history.actions.blockTypeChanged,
|
|
3007
|
+
t.toolbar.heading1,
|
|
3008
|
+
t.toolbar.heading2,
|
|
3009
|
+
t.toolbar.heading3,
|
|
3010
|
+
t.toolbar.heading4,
|
|
3011
|
+
t.toolbar.heading5,
|
|
3012
|
+
t.toolbar.heading6,
|
|
3013
|
+
t.toolbar.paragraph
|
|
3014
|
+
]
|
|
2482
3015
|
);
|
|
2483
3016
|
const handleToggleHistory = useCallback(() => {
|
|
2484
3017
|
setHistorySidebarOpen(!historySidebarOpen);
|
|
@@ -2512,6 +3045,26 @@ const Toolbar = () => {
|
|
|
2512
3045
|
)
|
|
2513
3046
|
] }),
|
|
2514
3047
|
/* @__PURE__ */ jsx(Divider, {}),
|
|
3048
|
+
/* @__PURE__ */ jsx("div", { className: "lex4-toolbar-group-gap lex4-toolbar-group-block", children: /* @__PURE__ */ jsxs(
|
|
3049
|
+
"select",
|
|
3050
|
+
{
|
|
3051
|
+
className: "lex4-toolbar-select lex4-toolbar-select-block",
|
|
3052
|
+
"data-testid": "block-type-selector",
|
|
3053
|
+
"aria-label": t.toolbar.blockType,
|
|
3054
|
+
value: activeBlockType,
|
|
3055
|
+
onChange: handleBlockTypeChange,
|
|
3056
|
+
children: [
|
|
3057
|
+
/* @__PURE__ */ jsx("option", { value: "paragraph", children: t.toolbar.paragraph }),
|
|
3058
|
+
/* @__PURE__ */ jsx("option", { value: "h1", children: t.toolbar.heading1 }),
|
|
3059
|
+
/* @__PURE__ */ jsx("option", { value: "h2", children: t.toolbar.heading2 }),
|
|
3060
|
+
/* @__PURE__ */ jsx("option", { value: "h3", children: t.toolbar.heading3 }),
|
|
3061
|
+
/* @__PURE__ */ jsx("option", { value: "h4", children: t.toolbar.heading4 }),
|
|
3062
|
+
/* @__PURE__ */ jsx("option", { value: "h5", children: t.toolbar.heading5 }),
|
|
3063
|
+
/* @__PURE__ */ jsx("option", { value: "h6", children: t.toolbar.heading6 })
|
|
3064
|
+
]
|
|
3065
|
+
}
|
|
3066
|
+
) }),
|
|
3067
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
2515
3068
|
/* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-group-gap", children: [
|
|
2516
3069
|
/* @__PURE__ */ jsx(Type, { size: 14, className: "lex4-toolbar-icon" }),
|
|
2517
3070
|
/* @__PURE__ */ jsx(
|
|
@@ -2519,7 +3072,7 @@ const Toolbar = () => {
|
|
|
2519
3072
|
{
|
|
2520
3073
|
className: "lex4-toolbar-select",
|
|
2521
3074
|
"data-testid": "font-selector",
|
|
2522
|
-
|
|
3075
|
+
value: activeFontFamily,
|
|
2523
3076
|
onChange: handleFontChange,
|
|
2524
3077
|
children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
|
|
2525
3078
|
}
|
|
@@ -2532,7 +3085,7 @@ const Toolbar = () => {
|
|
|
2532
3085
|
{
|
|
2533
3086
|
className: "lex4-toolbar-select lex4-toolbar-select-narrow",
|
|
2534
3087
|
"data-testid": "font-size-selector",
|
|
2535
|
-
|
|
3088
|
+
value: String(activeFontSize),
|
|
2536
3089
|
onChange: handleFontSizeChange,
|
|
2537
3090
|
children: SUPPORTED_FONT_SIZES.map((size) => /* @__PURE__ */ jsx("option", { value: size, children: size }, size))
|
|
2538
3091
|
}
|
|
@@ -2567,7 +3120,7 @@ const Toolbar = () => {
|
|
|
2567
3120
|
/* @__PURE__ */ jsx(CanvasControls, {}),
|
|
2568
3121
|
/* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-end", children: [
|
|
2569
3122
|
toolbarEndItems.map((EndItem, idx) => /* @__PURE__ */ jsx(EndItem, {}, idx)),
|
|
2570
|
-
/* @__PURE__ */ jsxs(
|
|
3123
|
+
toolbarConfig.history.visible && /* @__PURE__ */ jsxs(
|
|
2571
3124
|
"button",
|
|
2572
3125
|
{
|
|
2573
3126
|
type: "button",
|
|
@@ -2579,7 +3132,7 @@ const Toolbar = () => {
|
|
|
2579
3132
|
"aria-label": historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
|
|
2580
3133
|
children: [
|
|
2581
3134
|
/* @__PURE__ */ jsx(History, { size: 14 }),
|
|
2582
|
-
|
|
3135
|
+
toolbarConfig.history.showLabel && t.toolbar.history
|
|
2583
3136
|
]
|
|
2584
3137
|
}
|
|
2585
3138
|
)
|
|
@@ -2923,7 +3476,8 @@ const lexicalTheme = {
|
|
|
2923
3476
|
h2: "lex4-heading lex4-heading-h2",
|
|
2924
3477
|
h3: "lex4-heading lex4-heading-h3",
|
|
2925
3478
|
h4: "lex4-heading lex4-heading-h4",
|
|
2926
|
-
h5: "lex4-heading lex4-heading-h5"
|
|
3479
|
+
h5: "lex4-heading lex4-heading-h5",
|
|
3480
|
+
h6: "lex4-heading lex4-heading-h6"
|
|
2927
3481
|
},
|
|
2928
3482
|
text: {
|
|
2929
3483
|
bold: "lex4-text-bold",
|
|
@@ -4460,12 +5014,10 @@ function decodeFormatBitmask(format) {
|
|
|
4460
5014
|
return marks;
|
|
4461
5015
|
}
|
|
4462
5016
|
function extractFontFamily(style) {
|
|
4463
|
-
|
|
4464
|
-
return match ? match[1].trim().replace(/['"]/g, "") : void 0;
|
|
5017
|
+
return extractFontFamilyFromStyle(style);
|
|
4465
5018
|
}
|
|
4466
5019
|
function extractFontSizePt(style) {
|
|
4467
|
-
|
|
4468
|
-
return match ? parseFloat(match[1]) : void 0;
|
|
5020
|
+
return extractFontSizePtFromStyle(style);
|
|
4469
5021
|
}
|
|
4470
5022
|
function buildTextMarks(format, style) {
|
|
4471
5023
|
const formatMarks = decodeFormatBitmask(format);
|
|
@@ -4499,9 +5051,11 @@ function mapTextNode(node) {
|
|
|
4499
5051
|
};
|
|
4500
5052
|
}
|
|
4501
5053
|
function mapVariableNode(node) {
|
|
5054
|
+
const marks = buildTextMarks(node.format ?? 0, node.style);
|
|
4502
5055
|
return {
|
|
4503
5056
|
type: "variable",
|
|
4504
|
-
key: node.variableKey
|
|
5057
|
+
key: node.variableKey,
|
|
5058
|
+
...marks ? { marks } : {}
|
|
4505
5059
|
};
|
|
4506
5060
|
}
|
|
4507
5061
|
function mapLineBreak() {
|
|
@@ -4565,7 +5119,7 @@ function mapParagraph(node) {
|
|
|
4565
5119
|
function mapHeading(node) {
|
|
4566
5120
|
var _a;
|
|
4567
5121
|
const alignment = decodeAlignment(node.format);
|
|
4568
|
-
const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(
|
|
5122
|
+
const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h([1-6])$/);
|
|
4569
5123
|
const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
|
|
4570
5124
|
return {
|
|
4571
5125
|
type: "heading",
|
|
@@ -4690,6 +5244,105 @@ function buildSavePayload(ast, options) {
|
|
|
4690
5244
|
function serializeDocumentJson(ast) {
|
|
4691
5245
|
return JSON.stringify(ast, null, 2);
|
|
4692
5246
|
}
|
|
5247
|
+
function appendChildren(parent, children = []) {
|
|
5248
|
+
const nodes = children.map(buildLexicalNode).filter((node) => node !== null);
|
|
5249
|
+
if (nodes.length > 0) {
|
|
5250
|
+
parent.append(...nodes);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
function applyElementFormatting(node, serializedNode) {
|
|
5254
|
+
if (typeof serializedNode.format === "string" && ["left", "center", "right", "justify"].includes(serializedNode.format) && node.setFormat) {
|
|
5255
|
+
node.setFormat(serializedNode.format);
|
|
5256
|
+
}
|
|
5257
|
+
if (typeof serializedNode.indent === "number" && serializedNode.indent > 0 && node.setIndent) {
|
|
5258
|
+
node.setIndent(serializedNode.indent);
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
function buildLexicalNode(serializedNode) {
|
|
5262
|
+
switch (serializedNode.type) {
|
|
5263
|
+
case "paragraph": {
|
|
5264
|
+
const node = $createParagraphNode();
|
|
5265
|
+
applyElementFormatting(node, serializedNode);
|
|
5266
|
+
appendChildren(node, serializedNode.children);
|
|
5267
|
+
return node;
|
|
5268
|
+
}
|
|
5269
|
+
case "heading": {
|
|
5270
|
+
const tag = typeof serializedNode.tag === "string" && /^h[1-6]$/.test(serializedNode.tag) ? serializedNode.tag : "h1";
|
|
5271
|
+
const node = $createHeadingNode(tag);
|
|
5272
|
+
applyElementFormatting(node, serializedNode);
|
|
5273
|
+
appendChildren(node, serializedNode.children);
|
|
5274
|
+
return node;
|
|
5275
|
+
}
|
|
5276
|
+
case "quote": {
|
|
5277
|
+
const node = $createQuoteNode();
|
|
5278
|
+
applyElementFormatting(node, serializedNode);
|
|
5279
|
+
appendChildren(node, serializedNode.children);
|
|
5280
|
+
return node;
|
|
5281
|
+
}
|
|
5282
|
+
case "list": {
|
|
5283
|
+
const listType = serializedNode.listType === "number" ? "number" : "bullet";
|
|
5284
|
+
const node = $createListNode(listType, typeof serializedNode.start === "number" ? serializedNode.start : 1);
|
|
5285
|
+
appendChildren(node, serializedNode.children);
|
|
5286
|
+
return node;
|
|
5287
|
+
}
|
|
5288
|
+
case "listitem": {
|
|
5289
|
+
const node = $createListItemNode();
|
|
5290
|
+
appendChildren(node, serializedNode.children);
|
|
5291
|
+
return node;
|
|
5292
|
+
}
|
|
5293
|
+
case "text": {
|
|
5294
|
+
const node = $createTextNode(serializedNode.text ?? "");
|
|
5295
|
+
if (typeof serializedNode.format === "number" && serializedNode.format > 0) {
|
|
5296
|
+
node.setFormat(serializedNode.format);
|
|
5297
|
+
}
|
|
5298
|
+
if (typeof serializedNode.style === "string" && serializedNode.style.trim() !== "") {
|
|
5299
|
+
node.setStyle(serializedNode.style);
|
|
5300
|
+
}
|
|
5301
|
+
return node;
|
|
5302
|
+
}
|
|
5303
|
+
case "linebreak":
|
|
5304
|
+
return $createLineBreakNode();
|
|
5305
|
+
case "variable-node":
|
|
5306
|
+
return $createVariableNode(
|
|
5307
|
+
serializedNode.variableKey ?? "",
|
|
5308
|
+
typeof serializedNode.format === "number" ? serializedNode.format : 0,
|
|
5309
|
+
typeof serializedNode.style === "string" ? serializedNode.style : ""
|
|
5310
|
+
);
|
|
5311
|
+
default:
|
|
5312
|
+
return null;
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
function getBodyChildren(pageState) {
|
|
5316
|
+
const root = pageState == null ? void 0 : pageState.root;
|
|
5317
|
+
return (root == null ? void 0 : root.children) ?? [];
|
|
5318
|
+
}
|
|
5319
|
+
function insertDocumentContent(editor, document2) {
|
|
5320
|
+
let inserted = false;
|
|
5321
|
+
editor.update(() => {
|
|
5322
|
+
const nodes = document2.pages.flatMap((page) => getBodyChildren(page.bodyState)).map(buildLexicalNode).filter((node) => node !== null);
|
|
5323
|
+
if (nodes.length === 0) {
|
|
5324
|
+
return;
|
|
5325
|
+
}
|
|
5326
|
+
const selection = $getSelection();
|
|
5327
|
+
if ($isRangeSelection(selection)) {
|
|
5328
|
+
if (selection.isCollapsed()) {
|
|
5329
|
+
selection.insertParagraph();
|
|
5330
|
+
}
|
|
5331
|
+
const nextSelection = $getSelection();
|
|
5332
|
+
if ($isRangeSelection(nextSelection) || $isNodeSelection(nextSelection)) {
|
|
5333
|
+
nextSelection.insertNodes(nodes);
|
|
5334
|
+
} else {
|
|
5335
|
+
$insertNodes(nodes);
|
|
5336
|
+
}
|
|
5337
|
+
} else if ($isNodeSelection(selection)) {
|
|
5338
|
+
selection.insertNodes(nodes);
|
|
5339
|
+
} else {
|
|
5340
|
+
$insertNodes(nodes);
|
|
5341
|
+
}
|
|
5342
|
+
inserted = true;
|
|
5343
|
+
});
|
|
5344
|
+
return inserted;
|
|
5345
|
+
}
|
|
4693
5346
|
function selectEntireDocument(rootElement, selectionBuffer) {
|
|
4694
5347
|
if (!rootElement || !selectionBuffer) {
|
|
4695
5348
|
return;
|
|
@@ -4896,10 +5549,13 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
|
|
|
4896
5549
|
const {
|
|
4897
5550
|
document: doc,
|
|
4898
5551
|
activeEditor,
|
|
5552
|
+
activeCaretPosition,
|
|
4899
5553
|
historySidebarOpen,
|
|
5554
|
+
runHistoryAction,
|
|
4900
5555
|
setHistorySidebarOpen
|
|
4901
5556
|
} = useDocument();
|
|
4902
5557
|
const { handleFactories } = useExtensions();
|
|
5558
|
+
const t = useTranslations();
|
|
4903
5559
|
const getDocument = useCallback(() => doc, [doc]);
|
|
4904
5560
|
const getActiveEditor = useCallback(() => activeEditor, [activeEditor]);
|
|
4905
5561
|
const extensionCtx = useExtensionContext(getDocument, getActiveEditor);
|
|
@@ -4910,6 +5566,23 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
|
|
|
4910
5566
|
},
|
|
4911
5567
|
toggleHistorySidebar: () => {
|
|
4912
5568
|
setHistorySidebarOpen(!historySidebarOpen);
|
|
5569
|
+
},
|
|
5570
|
+
insertDocumentContent: (documentToInsert) => {
|
|
5571
|
+
if (!activeEditor || (activeCaretPosition == null ? void 0 : activeCaretPosition.region) !== "body") {
|
|
5572
|
+
return false;
|
|
5573
|
+
}
|
|
5574
|
+
let inserted = false;
|
|
5575
|
+
runHistoryAction(
|
|
5576
|
+
{
|
|
5577
|
+
label: t.history.actions.insertedDocumentContent,
|
|
5578
|
+
source: "toolbar",
|
|
5579
|
+
region: "document"
|
|
5580
|
+
},
|
|
5581
|
+
() => {
|
|
5582
|
+
inserted = insertDocumentContent(activeEditor, documentToInsert);
|
|
5583
|
+
}
|
|
5584
|
+
);
|
|
5585
|
+
return inserted;
|
|
4913
5586
|
}
|
|
4914
5587
|
};
|
|
4915
5588
|
for (const factory of handleFactories) {
|
|
@@ -4917,7 +5590,16 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
|
|
|
4917
5590
|
Object.assign(handle, methods);
|
|
4918
5591
|
}
|
|
4919
5592
|
return handle;
|
|
4920
|
-
}, [
|
|
5593
|
+
}, [
|
|
5594
|
+
activeCaretPosition == null ? void 0 : activeCaretPosition.region,
|
|
5595
|
+
activeEditor,
|
|
5596
|
+
extensionCtx,
|
|
5597
|
+
handleFactories,
|
|
5598
|
+
historySidebarOpen,
|
|
5599
|
+
runHistoryAction,
|
|
5600
|
+
setHistorySidebarOpen,
|
|
5601
|
+
t.history.actions.insertedDocumentContent
|
|
5602
|
+
]);
|
|
4921
5603
|
return /* @__PURE__ */ jsx(
|
|
4922
5604
|
EditorChrome,
|
|
4923
5605
|
{
|
|
@@ -4935,9 +5617,10 @@ const Lex4Editor = forwardRef(({
|
|
|
4935
5617
|
onSave,
|
|
4936
5618
|
extensions,
|
|
4937
5619
|
translations,
|
|
5620
|
+
toolbar,
|
|
4938
5621
|
className
|
|
4939
5622
|
}, ref) => {
|
|
4940
|
-
return /* @__PURE__ */ jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsx(
|
|
5623
|
+
return /* @__PURE__ */ jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsx(ToolbarConfigProvider, { toolbar, children: /* @__PURE__ */ jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsx(
|
|
4941
5624
|
DocumentProvider,
|
|
4942
5625
|
{
|
|
4943
5626
|
initialDocument,
|
|
@@ -4952,7 +5635,7 @@ const Lex4Editor = forwardRef(({
|
|
|
4952
5635
|
}
|
|
4953
5636
|
)
|
|
4954
5637
|
}
|
|
4955
|
-
) }) }) });
|
|
5638
|
+
) }) }) }) });
|
|
4956
5639
|
});
|
|
4957
5640
|
Lex4Editor.displayName = "Lex4Editor";
|
|
4958
5641
|
function useOverflowDetection(bodyHeight, onOverflow, debounceMs = 100) {
|
|
@@ -5046,120 +5729,6 @@ function astExtension() {
|
|
|
5046
5729
|
}
|
|
5047
5730
|
};
|
|
5048
5731
|
}
|
|
5049
|
-
const EMPTY_CONTEXT = {
|
|
5050
|
-
definitions: [],
|
|
5051
|
-
refreshDefinitions: () => {
|
|
5052
|
-
},
|
|
5053
|
-
getDefinition: () => void 0
|
|
5054
|
-
};
|
|
5055
|
-
const VariableContext = createContext(EMPTY_CONTEXT);
|
|
5056
|
-
const VariableProvider = ({
|
|
5057
|
-
initialDefinitions = [],
|
|
5058
|
-
children
|
|
5059
|
-
}) => {
|
|
5060
|
-
const [definitions, setDefinitions] = useState(initialDefinitions);
|
|
5061
|
-
const refresh = useCallback((newDefinitions) => {
|
|
5062
|
-
setDefinitions(newDefinitions);
|
|
5063
|
-
}, []);
|
|
5064
|
-
const getDefinition = useCallback(
|
|
5065
|
-
(key) => {
|
|
5066
|
-
return definitions.find((d) => d.key === key);
|
|
5067
|
-
},
|
|
5068
|
-
[definitions]
|
|
5069
|
-
);
|
|
5070
|
-
const value = useMemo(
|
|
5071
|
-
() => ({ definitions, refreshDefinitions: refresh, getDefinition }),
|
|
5072
|
-
[definitions, refresh, getDefinition]
|
|
5073
|
-
);
|
|
5074
|
-
return /* @__PURE__ */ jsx(VariableContext.Provider, { value, children });
|
|
5075
|
-
};
|
|
5076
|
-
function useVariables() {
|
|
5077
|
-
return useContext(VariableContext);
|
|
5078
|
-
}
|
|
5079
|
-
class VariableNode extends DecoratorNode {
|
|
5080
|
-
constructor(variableKey, key) {
|
|
5081
|
-
super(key);
|
|
5082
|
-
__publicField(this, "__variableKey");
|
|
5083
|
-
this.__variableKey = variableKey;
|
|
5084
|
-
}
|
|
5085
|
-
static getType() {
|
|
5086
|
-
return "variable-node";
|
|
5087
|
-
}
|
|
5088
|
-
static clone(node) {
|
|
5089
|
-
return new VariableNode(node.__variableKey, node.__key);
|
|
5090
|
-
}
|
|
5091
|
-
getVariableKey() {
|
|
5092
|
-
return this.__variableKey;
|
|
5093
|
-
}
|
|
5094
|
-
// -- Serialization --
|
|
5095
|
-
static importJSON(serializedNode) {
|
|
5096
|
-
return $createVariableNode(serializedNode.variableKey);
|
|
5097
|
-
}
|
|
5098
|
-
exportJSON() {
|
|
5099
|
-
return {
|
|
5100
|
-
type: "variable-node",
|
|
5101
|
-
version: 1,
|
|
5102
|
-
variableKey: this.__variableKey
|
|
5103
|
-
};
|
|
5104
|
-
}
|
|
5105
|
-
// -- DOM --
|
|
5106
|
-
createDOM() {
|
|
5107
|
-
const span = document.createElement("span");
|
|
5108
|
-
span.className = "lex4-variable";
|
|
5109
|
-
span.setAttribute("data-variable-key", this.__variableKey);
|
|
5110
|
-
span.setAttribute("data-testid", `variable-${this.__variableKey}`);
|
|
5111
|
-
span.contentEditable = "false";
|
|
5112
|
-
return span;
|
|
5113
|
-
}
|
|
5114
|
-
updateDOM() {
|
|
5115
|
-
return false;
|
|
5116
|
-
}
|
|
5117
|
-
exportDOM() {
|
|
5118
|
-
const span = document.createElement("span");
|
|
5119
|
-
span.setAttribute("data-variable-key", this.__variableKey);
|
|
5120
|
-
span.textContent = `{{${this.__variableKey}}}`;
|
|
5121
|
-
return { element: span };
|
|
5122
|
-
}
|
|
5123
|
-
static importDOM() {
|
|
5124
|
-
return null;
|
|
5125
|
-
}
|
|
5126
|
-
// -- Behavior --
|
|
5127
|
-
isInline() {
|
|
5128
|
-
return true;
|
|
5129
|
-
}
|
|
5130
|
-
isKeyboardSelectable() {
|
|
5131
|
-
return true;
|
|
5132
|
-
}
|
|
5133
|
-
getTextContent() {
|
|
5134
|
-
return `{{${this.__variableKey}}}`;
|
|
5135
|
-
}
|
|
5136
|
-
// -- Rendering --
|
|
5137
|
-
decorate() {
|
|
5138
|
-
return /* @__PURE__ */ jsx(VariableChip, { variableKey: this.__variableKey });
|
|
5139
|
-
}
|
|
5140
|
-
}
|
|
5141
|
-
function VariableChip({ variableKey }) {
|
|
5142
|
-
const { getDefinition } = useVariables();
|
|
5143
|
-
const def = getDefinition(variableKey);
|
|
5144
|
-
const label = (def == null ? void 0 : def.label) ?? variableKey;
|
|
5145
|
-
const group = def == null ? void 0 : def.group;
|
|
5146
|
-
return /* @__PURE__ */ jsx(
|
|
5147
|
-
"span",
|
|
5148
|
-
{
|
|
5149
|
-
className: "lex4-variable-chip",
|
|
5150
|
-
"data-testid": `variable-chip-${variableKey}`,
|
|
5151
|
-
"data-variable-group": group,
|
|
5152
|
-
title: variableKey,
|
|
5153
|
-
children: label
|
|
5154
|
-
}
|
|
5155
|
-
);
|
|
5156
|
-
}
|
|
5157
|
-
function $createVariableNode(variableKey) {
|
|
5158
|
-
return $applyNodeReplacement(new VariableNode(variableKey));
|
|
5159
|
-
}
|
|
5160
|
-
function $isVariableNode(node) {
|
|
5161
|
-
return node instanceof VariableNode;
|
|
5162
|
-
}
|
|
5163
5732
|
const INSERT_VARIABLE_COMMAND = createCommand("INSERT_VARIABLE");
|
|
5164
5733
|
const VariablePlugin = () => {
|
|
5165
5734
|
const [editor] = useLexicalComposerContext();
|
|
@@ -5420,7 +5989,11 @@ const VariablePanelStateProvider = ({ children }) => {
|
|
|
5420
5989
|
};
|
|
5421
5990
|
const VariableToolbarToggle = () => {
|
|
5422
5991
|
const { panelOpen, setPanelOpen } = useVariablePanelState();
|
|
5992
|
+
const toolbarConfig = useToolbarConfig();
|
|
5423
5993
|
const t = useTranslations();
|
|
5994
|
+
if (!toolbarConfig.variables.visible) {
|
|
5995
|
+
return null;
|
|
5996
|
+
}
|
|
5424
5997
|
return /* @__PURE__ */ jsxs(
|
|
5425
5998
|
"button",
|
|
5426
5999
|
{
|
|
@@ -5433,7 +6006,7 @@ const VariableToolbarToggle = () => {
|
|
|
5433
6006
|
"aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
|
|
5434
6007
|
children: [
|
|
5435
6008
|
/* @__PURE__ */ jsx(Braces, { size: 14 }),
|
|
5436
|
-
t.variables.title
|
|
6009
|
+
toolbarConfig.variables.showLabel && t.variables.title
|
|
5437
6010
|
]
|
|
5438
6011
|
}
|
|
5439
6012
|
);
|