@yurikilian/lex4 0.1.1 → 0.2.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/README.md +243 -27
- package/dist/ast/block-mapper.d.ts +24 -0
- package/dist/ast/block-mapper.d.ts.map +1 -0
- package/dist/ast/content-mapper.d.ts +16 -0
- package/dist/ast/content-mapper.d.ts.map +1 -0
- package/dist/ast/document-serializer.d.ts +11 -0
- package/dist/ast/document-serializer.d.ts.map +1 -0
- package/dist/ast/index.d.ts +9 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/inline-mapper.d.ts +41 -0
- package/dist/ast/inline-mapper.d.ts.map +1 -0
- package/dist/ast/payload-builder.d.ts +18 -0
- package/dist/ast/payload-builder.d.ts.map +1 -0
- package/dist/ast/types.d.ts +112 -0
- package/dist/ast/types.d.ts.map +1 -0
- package/dist/components/EditorSidebar.d.ts +1 -0
- package/dist/components/EditorSidebar.d.ts.map +1 -1
- package/dist/components/HistorySidebar.d.ts.map +1 -1
- package/dist/components/Lex4Editor.d.ts +9 -1
- package/dist/components/Lex4Editor.d.ts.map +1 -1
- package/dist/components/PageBody.d.ts +1 -2
- package/dist/components/PageBody.d.ts.map +1 -1
- package/dist/components/PageFooter.d.ts.map +1 -1
- package/dist/components/PageHeader.d.ts.map +1 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/VariablePanel.d.ts +15 -0
- package/dist/components/VariablePanel.d.ts.map +1 -0
- package/dist/components/VariablePicker.d.ts +14 -0
- package/dist/components/VariablePicker.d.ts.map +1 -0
- package/dist/extensions/ast-extension.d.ts +16 -0
- package/dist/extensions/ast-extension.d.ts.map +1 -0
- package/dist/extensions/extension-context.d.ts +29 -0
- package/dist/extensions/extension-context.d.ts.map +1 -0
- package/dist/extensions/index.d.ts +6 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/types.d.ts +74 -0
- package/dist/extensions/types.d.ts.map +1 -0
- package/dist/extensions/variables-extension.d.ts +31 -0
- package/dist/extensions/variables-extension.d.ts.map +1 -0
- package/dist/i18n/context.d.ts +28 -0
- package/dist/i18n/context.d.ts.map +1 -0
- package/dist/i18n/defaults.d.ts +3 -0
- package/dist/i18n/defaults.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/types.d.ts +82 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lex4-editor.cjs +1113 -101
- package/dist/lex4-editor.cjs.map +1 -1
- package/dist/lex4-editor.js +1116 -104
- package/dist/lex4-editor.js.map +1 -1
- package/dist/lexical/editor-setup.d.ts +5 -1
- package/dist/lexical/editor-setup.d.ts.map +1 -1
- package/dist/lexical/plugins/font-plugin.d.ts +1 -1
- package/dist/lexical/plugins/font-plugin.d.ts.map +1 -1
- package/dist/lexical/plugins/font-size-plugin.d.ts +19 -0
- package/dist/lexical/plugins/font-size-plugin.d.ts.map +1 -0
- package/dist/lexical/plugins/index.d.ts +2 -0
- package/dist/lexical/plugins/index.d.ts.map +1 -1
- package/dist/style.css +532 -160
- package/dist/types/editor-handle.d.ts +14 -0
- package/dist/types/editor-handle.d.ts.map +1 -0
- package/dist/types/editor-props.d.ts +23 -0
- package/dist/types/editor-props.d.ts.map +1 -1
- package/dist/variables/index.d.ts +5 -0
- package/dist/variables/index.d.ts.map +1 -0
- package/dist/variables/types.d.ts +26 -0
- package/dist/variables/types.d.ts.map +1 -0
- package/dist/variables/variable-commands.d.ts +11 -0
- package/dist/variables/variable-commands.d.ts.map +1 -0
- package/dist/variables/variable-context.d.ts +21 -0
- package/dist/variables/variable-context.d.ts.map +1 -0
- package/dist/variables/variable-node.d.ts +34 -0
- package/dist/variables/variable-node.d.ts.map +1 -0
- package/dist/variables/variable-plugin.d.ts +8 -0
- package/dist/variables/variable-plugin.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/lex4-editor.cjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
2
5
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
6
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
7
|
const React = require("react");
|
|
@@ -1133,33 +1136,53 @@ const createLucideIcon = (iconName, iconNode) => {
|
|
|
1133
1136
|
* This source code is licensed under the ISC license.
|
|
1134
1137
|
* See the LICENSE file in the root directory of this source tree.
|
|
1135
1138
|
*/
|
|
1136
|
-
const __iconNode$
|
|
1139
|
+
const __iconNode$m = [
|
|
1137
1140
|
[
|
|
1138
1141
|
"path",
|
|
1139
1142
|
{ d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
|
|
1140
1143
|
]
|
|
1141
1144
|
];
|
|
1142
|
-
const Bold = createLucideIcon("bold", __iconNode$
|
|
1145
|
+
const Bold = createLucideIcon("bold", __iconNode$m);
|
|
1143
1146
|
/**
|
|
1144
1147
|
* @license lucide-react v1.8.0 - ISC
|
|
1145
1148
|
*
|
|
1146
1149
|
* This source code is licensed under the ISC license.
|
|
1147
1150
|
* See the LICENSE file in the root directory of this source tree.
|
|
1148
1151
|
*/
|
|
1149
|
-
const __iconNode$
|
|
1152
|
+
const __iconNode$l = [
|
|
1153
|
+
[
|
|
1154
|
+
"path",
|
|
1155
|
+
{ d: "M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1", key: "ezmyqa" }
|
|
1156
|
+
],
|
|
1157
|
+
[
|
|
1158
|
+
"path",
|
|
1159
|
+
{
|
|
1160
|
+
d: "M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1",
|
|
1161
|
+
key: "e1hn23"
|
|
1162
|
+
}
|
|
1163
|
+
]
|
|
1164
|
+
];
|
|
1165
|
+
const Braces = createLucideIcon("braces", __iconNode$l);
|
|
1166
|
+
/**
|
|
1167
|
+
* @license lucide-react v1.8.0 - ISC
|
|
1168
|
+
*
|
|
1169
|
+
* This source code is licensed under the ISC license.
|
|
1170
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1171
|
+
*/
|
|
1172
|
+
const __iconNode$k = [
|
|
1150
1173
|
["line", { x1: "15", x2: "15", y1: "12", y2: "18", key: "1p7wdc" }],
|
|
1151
1174
|
["line", { x1: "12", x2: "18", y1: "15", y2: "15", key: "1nscbv" }],
|
|
1152
1175
|
["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
|
|
1153
1176
|
["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
|
|
1154
1177
|
];
|
|
1155
|
-
const CopyPlus = createLucideIcon("copy-plus", __iconNode$
|
|
1178
|
+
const CopyPlus = createLucideIcon("copy-plus", __iconNode$k);
|
|
1156
1179
|
/**
|
|
1157
1180
|
* @license lucide-react v1.8.0 - ISC
|
|
1158
1181
|
*
|
|
1159
1182
|
* This source code is licensed under the ISC license.
|
|
1160
1183
|
* See the LICENSE file in the root directory of this source tree.
|
|
1161
1184
|
*/
|
|
1162
|
-
const __iconNode$
|
|
1185
|
+
const __iconNode$j = [
|
|
1163
1186
|
[
|
|
1164
1187
|
"path",
|
|
1165
1188
|
{
|
|
@@ -1169,65 +1192,65 @@ const __iconNode$h = [
|
|
|
1169
1192
|
],
|
|
1170
1193
|
["path", { d: "m5.082 11.09 8.828 8.828", key: "1wx5vj" }]
|
|
1171
1194
|
];
|
|
1172
|
-
const Eraser = createLucideIcon("eraser", __iconNode$
|
|
1195
|
+
const Eraser = createLucideIcon("eraser", __iconNode$j);
|
|
1173
1196
|
/**
|
|
1174
1197
|
* @license lucide-react v1.8.0 - ISC
|
|
1175
1198
|
*
|
|
1176
1199
|
* This source code is licensed under the ISC license.
|
|
1177
1200
|
* See the LICENSE file in the root directory of this source tree.
|
|
1178
1201
|
*/
|
|
1179
|
-
const __iconNode$
|
|
1202
|
+
const __iconNode$i = [
|
|
1180
1203
|
["line", { x1: "4", x2: "20", y1: "9", y2: "9", key: "4lhtct" }],
|
|
1181
1204
|
["line", { x1: "4", x2: "20", y1: "15", y2: "15", key: "vyu0kd" }],
|
|
1182
1205
|
["line", { x1: "10", x2: "8", y1: "3", y2: "21", key: "1ggp8o" }],
|
|
1183
1206
|
["line", { x1: "16", x2: "14", y1: "3", y2: "21", key: "weycgp" }]
|
|
1184
1207
|
];
|
|
1185
|
-
const Hash = createLucideIcon("hash", __iconNode$
|
|
1208
|
+
const Hash = createLucideIcon("hash", __iconNode$i);
|
|
1186
1209
|
/**
|
|
1187
1210
|
* @license lucide-react v1.8.0 - ISC
|
|
1188
1211
|
*
|
|
1189
1212
|
* This source code is licensed under the ISC license.
|
|
1190
1213
|
* See the LICENSE file in the root directory of this source tree.
|
|
1191
1214
|
*/
|
|
1192
|
-
const __iconNode$
|
|
1215
|
+
const __iconNode$h = [
|
|
1193
1216
|
["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
|
|
1194
1217
|
["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
|
|
1195
1218
|
["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
|
|
1196
1219
|
];
|
|
1197
|
-
const Italic = createLucideIcon("italic", __iconNode$
|
|
1220
|
+
const Italic = createLucideIcon("italic", __iconNode$h);
|
|
1198
1221
|
/**
|
|
1199
1222
|
* @license lucide-react v1.8.0 - ISC
|
|
1200
1223
|
*
|
|
1201
1224
|
* This source code is licensed under the ISC license.
|
|
1202
1225
|
* See the LICENSE file in the root directory of this source tree.
|
|
1203
1226
|
*/
|
|
1204
|
-
const __iconNode$
|
|
1227
|
+
const __iconNode$g = [
|
|
1205
1228
|
["path", { d: "M21 5H11", key: "us1j55" }],
|
|
1206
1229
|
["path", { d: "M21 12H11", key: "wd7e0v" }],
|
|
1207
1230
|
["path", { d: "M21 19H11", key: "saa85w" }],
|
|
1208
1231
|
["path", { d: "m7 8-4 4 4 4", key: "o5hrat" }]
|
|
1209
1232
|
];
|
|
1210
|
-
const ListIndentDecrease = createLucideIcon("list-indent-decrease", __iconNode$
|
|
1233
|
+
const ListIndentDecrease = createLucideIcon("list-indent-decrease", __iconNode$g);
|
|
1211
1234
|
/**
|
|
1212
1235
|
* @license lucide-react v1.8.0 - ISC
|
|
1213
1236
|
*
|
|
1214
1237
|
* This source code is licensed under the ISC license.
|
|
1215
1238
|
* See the LICENSE file in the root directory of this source tree.
|
|
1216
1239
|
*/
|
|
1217
|
-
const __iconNode$
|
|
1240
|
+
const __iconNode$f = [
|
|
1218
1241
|
["path", { d: "M21 5H11", key: "us1j55" }],
|
|
1219
1242
|
["path", { d: "M21 12H11", key: "wd7e0v" }],
|
|
1220
1243
|
["path", { d: "M21 19H11", key: "saa85w" }],
|
|
1221
1244
|
["path", { d: "m3 8 4 4-4 4", key: "1a3j6y" }]
|
|
1222
1245
|
];
|
|
1223
|
-
const ListIndentIncrease = createLucideIcon("list-indent-increase", __iconNode$
|
|
1246
|
+
const ListIndentIncrease = createLucideIcon("list-indent-increase", __iconNode$f);
|
|
1224
1247
|
/**
|
|
1225
1248
|
* @license lucide-react v1.8.0 - ISC
|
|
1226
1249
|
*
|
|
1227
1250
|
* This source code is licensed under the ISC license.
|
|
1228
1251
|
* See the LICENSE file in the root directory of this source tree.
|
|
1229
1252
|
*/
|
|
1230
|
-
const __iconNode$
|
|
1253
|
+
const __iconNode$e = [
|
|
1231
1254
|
["path", { d: "M11 5h10", key: "1cz7ny" }],
|
|
1232
1255
|
["path", { d: "M11 12h10", key: "1438ji" }],
|
|
1233
1256
|
["path", { d: "M11 19h10", key: "11t30w" }],
|
|
@@ -1235,14 +1258,14 @@ const __iconNode$c = [
|
|
|
1235
1258
|
["path", { d: "M4 9h2", key: "r1h2o0" }],
|
|
1236
1259
|
["path", { d: "M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02", key: "xtkcd5" }]
|
|
1237
1260
|
];
|
|
1238
|
-
const ListOrdered = createLucideIcon("list-ordered", __iconNode$
|
|
1261
|
+
const ListOrdered = createLucideIcon("list-ordered", __iconNode$e);
|
|
1239
1262
|
/**
|
|
1240
1263
|
* @license lucide-react v1.8.0 - ISC
|
|
1241
1264
|
*
|
|
1242
1265
|
* This source code is licensed under the ISC license.
|
|
1243
1266
|
* See the LICENSE file in the root directory of this source tree.
|
|
1244
1267
|
*/
|
|
1245
|
-
const __iconNode$
|
|
1268
|
+
const __iconNode$d = [
|
|
1246
1269
|
["path", { d: "M3 5h.01", key: "18ugdj" }],
|
|
1247
1270
|
["path", { d: "M3 12h.01", key: "nlz23k" }],
|
|
1248
1271
|
["path", { d: "M3 19h.01", key: "noohij" }],
|
|
@@ -1250,29 +1273,53 @@ const __iconNode$b = [
|
|
|
1250
1273
|
["path", { d: "M8 12h13", key: "1za7za" }],
|
|
1251
1274
|
["path", { d: "M8 19h13", key: "m83p4d" }]
|
|
1252
1275
|
];
|
|
1253
|
-
const List = createLucideIcon("list", __iconNode$
|
|
1276
|
+
const List = createLucideIcon("list", __iconNode$d);
|
|
1254
1277
|
/**
|
|
1255
1278
|
* @license lucide-react v1.8.0 - ISC
|
|
1256
1279
|
*
|
|
1257
1280
|
* This source code is licensed under the ISC license.
|
|
1258
1281
|
* See the LICENSE file in the root directory of this source tree.
|
|
1259
1282
|
*/
|
|
1260
|
-
const __iconNode$
|
|
1283
|
+
const __iconNode$c = [
|
|
1261
1284
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
|
|
1262
1285
|
["path", { d: "M15 3v18", key: "14nvp0" }]
|
|
1263
1286
|
];
|
|
1264
|
-
const PanelRight = createLucideIcon("panel-right", __iconNode$
|
|
1287
|
+
const PanelRight = createLucideIcon("panel-right", __iconNode$c);
|
|
1265
1288
|
/**
|
|
1266
1289
|
* @license lucide-react v1.8.0 - ISC
|
|
1267
1290
|
*
|
|
1268
1291
|
* This source code is licensed under the ISC license.
|
|
1269
1292
|
* See the LICENSE file in the root directory of this source tree.
|
|
1270
1293
|
*/
|
|
1271
|
-
const __iconNode$
|
|
1294
|
+
const __iconNode$b = [
|
|
1272
1295
|
["path", { d: "m15 14 5-5-5-5", key: "12vg1m" }],
|
|
1273
1296
|
["path", { d: "M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13", key: "6uklza" }]
|
|
1274
1297
|
];
|
|
1275
|
-
const Redo2 = createLucideIcon("redo-2", __iconNode$
|
|
1298
|
+
const Redo2 = createLucideIcon("redo-2", __iconNode$b);
|
|
1299
|
+
/**
|
|
1300
|
+
* @license lucide-react v1.8.0 - ISC
|
|
1301
|
+
*
|
|
1302
|
+
* This source code is licensed under the ISC license.
|
|
1303
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1304
|
+
*/
|
|
1305
|
+
const __iconNode$a = [
|
|
1306
|
+
["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
|
|
1307
|
+
["path", { d: "M21 3v5h-5", key: "1q7to0" }],
|
|
1308
|
+
["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
|
|
1309
|
+
["path", { d: "M8 16H3v5", key: "1cv678" }]
|
|
1310
|
+
];
|
|
1311
|
+
const RefreshCw = createLucideIcon("refresh-cw", __iconNode$a);
|
|
1312
|
+
/**
|
|
1313
|
+
* @license lucide-react v1.8.0 - ISC
|
|
1314
|
+
*
|
|
1315
|
+
* This source code is licensed under the ISC license.
|
|
1316
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1317
|
+
*/
|
|
1318
|
+
const __iconNode$9 = [
|
|
1319
|
+
["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
|
|
1320
|
+
["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
|
|
1321
|
+
];
|
|
1322
|
+
const Search = createLucideIcon("search", __iconNode$9);
|
|
1276
1323
|
/**
|
|
1277
1324
|
* @license lucide-react v1.8.0 - ISC
|
|
1278
1325
|
*
|
|
@@ -1380,14 +1427,122 @@ const __iconNode = [
|
|
|
1380
1427
|
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
1381
1428
|
];
|
|
1382
1429
|
const X = createLucideIcon("x", __iconNode);
|
|
1430
|
+
const DEFAULT_TRANSLATIONS = {
|
|
1431
|
+
toolbar: {
|
|
1432
|
+
undo: "Undo",
|
|
1433
|
+
redo: "Redo",
|
|
1434
|
+
bold: "Bold (Ctrl+B)",
|
|
1435
|
+
italic: "Italic (Ctrl+I)",
|
|
1436
|
+
underline: "Underline (Ctrl+U)",
|
|
1437
|
+
strikethrough: "Strikethrough",
|
|
1438
|
+
alignLeft: "Align Left",
|
|
1439
|
+
alignCenter: "Align Center",
|
|
1440
|
+
alignRight: "Align Right",
|
|
1441
|
+
justify: "Justify",
|
|
1442
|
+
numberedList: "Numbered List",
|
|
1443
|
+
bulletList: "Bullet List",
|
|
1444
|
+
indent: "Indent",
|
|
1445
|
+
outdent: "Outdent",
|
|
1446
|
+
openHistory: "Open History",
|
|
1447
|
+
closeHistory: "Close History"
|
|
1448
|
+
},
|
|
1449
|
+
history: {
|
|
1450
|
+
title: "History",
|
|
1451
|
+
subtitle: "Word-style session history (last 100 actions)",
|
|
1452
|
+
empty: "No history yet.",
|
|
1453
|
+
clearHistory: "Clear History",
|
|
1454
|
+
actions: {
|
|
1455
|
+
enabledHeadersFooters: "Enabled headers and footers",
|
|
1456
|
+
disabledHeadersFooters: "Disabled headers and footers",
|
|
1457
|
+
copiedHeaderToAll: "Copied header to all pages",
|
|
1458
|
+
copiedFooterToAll: "Copied footer to all pages",
|
|
1459
|
+
clearedHeader: "Cleared header",
|
|
1460
|
+
clearedFooter: "Cleared footer",
|
|
1461
|
+
clearedAllHeaders: "Cleared all headers",
|
|
1462
|
+
clearedAllFooters: "Cleared all footers",
|
|
1463
|
+
pageCounterSet: "Page counter set to {{value}}",
|
|
1464
|
+
boldApplied: "Bold applied",
|
|
1465
|
+
italicApplied: "Italic applied",
|
|
1466
|
+
underlineApplied: "Underline applied",
|
|
1467
|
+
strikethroughApplied: "Strikethrough applied",
|
|
1468
|
+
alignedLeft: "Aligned left",
|
|
1469
|
+
alignedCenter: "Aligned center",
|
|
1470
|
+
alignedRight: "Aligned right",
|
|
1471
|
+
justifiedText: "Justified text",
|
|
1472
|
+
insertedNumberedList: "Inserted numbered list",
|
|
1473
|
+
insertedBulletList: "Inserted bullet list",
|
|
1474
|
+
indentedContent: "Indented content",
|
|
1475
|
+
outdentedContent: "Outdented content",
|
|
1476
|
+
fontChanged: "Font changed to {{value}}",
|
|
1477
|
+
fontSizeChanged: "Font size changed to {{value}}pt"
|
|
1478
|
+
}
|
|
1479
|
+
},
|
|
1480
|
+
variables: {
|
|
1481
|
+
title: "Variables",
|
|
1482
|
+
available: "{{count}} available",
|
|
1483
|
+
refreshVariables: "Refresh variables",
|
|
1484
|
+
searchPlaceholder: "Search variables...",
|
|
1485
|
+
noVariablesFound: "No variables found",
|
|
1486
|
+
insertVariable: "Insert variable {{key}}",
|
|
1487
|
+
openPanel: "Open Variables",
|
|
1488
|
+
closePanel: "Close Variables"
|
|
1489
|
+
},
|
|
1490
|
+
header: {
|
|
1491
|
+
placeholder: "Header"
|
|
1492
|
+
},
|
|
1493
|
+
footer: {
|
|
1494
|
+
placeholder: "Footer"
|
|
1495
|
+
},
|
|
1496
|
+
sidebar: {
|
|
1497
|
+
close: "Close sidebar"
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
function deepMerge(target, source) {
|
|
1501
|
+
if (typeof target !== "object" || target === null) {
|
|
1502
|
+
return source ?? target;
|
|
1503
|
+
}
|
|
1504
|
+
const result = { ...target };
|
|
1505
|
+
for (const key of Object.keys(source)) {
|
|
1506
|
+
const sourceVal = source[key];
|
|
1507
|
+
const targetVal = target[key];
|
|
1508
|
+
if (sourceVal !== void 0 && typeof sourceVal === "object" && sourceVal !== null && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null) {
|
|
1509
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
1510
|
+
} else if (sourceVal !== void 0) {
|
|
1511
|
+
result[key] = sourceVal;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return result;
|
|
1515
|
+
}
|
|
1516
|
+
const TranslationsContext = React.createContext(DEFAULT_TRANSLATIONS);
|
|
1517
|
+
function useTranslations() {
|
|
1518
|
+
return React.useContext(TranslationsContext);
|
|
1519
|
+
}
|
|
1520
|
+
function interpolate(template, params) {
|
|
1521
|
+
return template.replace(
|
|
1522
|
+
/\{\{(\w+)\}\}/g,
|
|
1523
|
+
(_, key) => String(params[key] ?? `{{${key}}}`)
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
const TranslationsProvider = ({
|
|
1527
|
+
translations,
|
|
1528
|
+
children
|
|
1529
|
+
}) => {
|
|
1530
|
+
const merged = React.useMemo(
|
|
1531
|
+
() => translations ? deepMerge(DEFAULT_TRANSLATIONS, translations) : DEFAULT_TRANSLATIONS,
|
|
1532
|
+
[translations]
|
|
1533
|
+
);
|
|
1534
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TranslationsContext.Provider, { value: merged, children });
|
|
1535
|
+
};
|
|
1383
1536
|
const EditorSidebar = ({
|
|
1384
1537
|
title,
|
|
1385
1538
|
subtitle,
|
|
1386
1539
|
open,
|
|
1387
1540
|
onClose,
|
|
1388
1541
|
headerActions,
|
|
1542
|
+
testId = "editor-sidebar",
|
|
1389
1543
|
children
|
|
1390
1544
|
}) => {
|
|
1545
|
+
const t = useTranslations();
|
|
1391
1546
|
if (!open) {
|
|
1392
1547
|
return null;
|
|
1393
1548
|
}
|
|
@@ -1395,7 +1550,7 @@ const EditorSidebar = ({
|
|
|
1395
1550
|
"aside",
|
|
1396
1551
|
{
|
|
1397
1552
|
className: "flex h-full w-[320px] shrink-0 flex-col border-l border-gray-200 bg-white",
|
|
1398
|
-
"data-testid":
|
|
1553
|
+
"data-testid": testId,
|
|
1399
1554
|
children: [
|
|
1400
1555
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between border-b border-gray-200 px-3 py-2.5", children: [
|
|
1401
1556
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
@@ -1411,7 +1566,7 @@ const EditorSidebar = ({
|
|
|
1411
1566
|
className: "flex h-6 w-6 items-center justify-center rounded text-gray-400\n transition-colors hover:bg-gray-100 hover:text-gray-600",
|
|
1412
1567
|
"data-testid": "close-editor-sidebar",
|
|
1413
1568
|
onClick: onClose,
|
|
1414
|
-
"aria-label":
|
|
1569
|
+
"aria-label": t.sidebar.close,
|
|
1415
1570
|
children: /* @__PURE__ */ jsxRuntime.jsx(X, { size: 14 })
|
|
1416
1571
|
}
|
|
1417
1572
|
)
|
|
@@ -1439,13 +1594,14 @@ const HistorySidebar = () => {
|
|
|
1439
1594
|
jumpToHistoryEntry: jumpToHistoryEntry2,
|
|
1440
1595
|
setHistorySidebarOpen
|
|
1441
1596
|
} = useDocument();
|
|
1597
|
+
const t = useTranslations();
|
|
1442
1598
|
const visibleEntries = historyEntries.slice().reverse();
|
|
1443
1599
|
const headerActions = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1444
1600
|
"button",
|
|
1445
1601
|
{
|
|
1446
1602
|
type: "button",
|
|
1447
|
-
title:
|
|
1448
|
-
"aria-label":
|
|
1603
|
+
title: t.history.clearHistory,
|
|
1604
|
+
"aria-label": t.history.clearHistory,
|
|
1449
1605
|
onMouseDown: (e) => e.preventDefault(),
|
|
1450
1606
|
onClick: () => clearHistory("manual"),
|
|
1451
1607
|
className: "flex h-6 w-6 items-center justify-center rounded text-gray-400\n transition-colors hover:bg-gray-100 hover:text-gray-600",
|
|
@@ -1456,17 +1612,18 @@ const HistorySidebar = () => {
|
|
|
1456
1612
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1457
1613
|
EditorSidebar,
|
|
1458
1614
|
{
|
|
1459
|
-
title:
|
|
1460
|
-
subtitle:
|
|
1615
|
+
title: t.history.title,
|
|
1616
|
+
subtitle: t.history.subtitle,
|
|
1461
1617
|
open: historySidebarOpen,
|
|
1462
1618
|
onClose: () => setHistorySidebarOpen(false),
|
|
1463
1619
|
headerActions,
|
|
1620
|
+
testId: "history-sidebar",
|
|
1464
1621
|
children: visibleEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1465
1622
|
"div",
|
|
1466
1623
|
{
|
|
1467
1624
|
className: "px-3 py-4 text-xs text-gray-500",
|
|
1468
1625
|
"data-testid": "history-empty",
|
|
1469
|
-
children:
|
|
1626
|
+
children: t.history.empty
|
|
1470
1627
|
}
|
|
1471
1628
|
) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "divide-y divide-gray-100", "data-testid": "history-entry-list", children: visibleEntries.map((entry, reversedIndex) => {
|
|
1472
1629
|
const actualIndex = historyEntries.length - reversedIndex - 1;
|
|
@@ -1657,7 +1814,81 @@ const ActionIconButton = ({ title, icon, onClick, disabled, testId }) => /* @__P
|
|
|
1657
1814
|
}
|
|
1658
1815
|
);
|
|
1659
1816
|
const ActionDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-4 w-px bg-gray-300/60" });
|
|
1817
|
+
function resolveExtensions(extensions) {
|
|
1818
|
+
const resolved = {
|
|
1819
|
+
nodes: [],
|
|
1820
|
+
bodyPlugins: [],
|
|
1821
|
+
toolbarItems: [],
|
|
1822
|
+
sidePanels: [],
|
|
1823
|
+
providers: [],
|
|
1824
|
+
themeOverrides: {},
|
|
1825
|
+
handleFactories: []
|
|
1826
|
+
};
|
|
1827
|
+
for (const ext of extensions) {
|
|
1828
|
+
if (ext.nodes) resolved.nodes.push(...ext.nodes);
|
|
1829
|
+
if (ext.bodyPlugins) resolved.bodyPlugins.push(...ext.bodyPlugins);
|
|
1830
|
+
if (ext.toolbarItems) resolved.toolbarItems.push(...ext.toolbarItems);
|
|
1831
|
+
if (ext.sidePanel) resolved.sidePanels.push(ext.sidePanel);
|
|
1832
|
+
if (ext.provider) resolved.providers.push(ext.provider);
|
|
1833
|
+
if (ext.themeOverrides) {
|
|
1834
|
+
resolved.themeOverrides = { ...resolved.themeOverrides, ...ext.themeOverrides };
|
|
1835
|
+
}
|
|
1836
|
+
if (ext.handleMethods) resolved.handleFactories.push(ext.handleMethods);
|
|
1837
|
+
}
|
|
1838
|
+
return resolved;
|
|
1839
|
+
}
|
|
1840
|
+
const EMPTY_RESOLVED = {
|
|
1841
|
+
nodes: [],
|
|
1842
|
+
bodyPlugins: [],
|
|
1843
|
+
toolbarItems: [],
|
|
1844
|
+
sidePanels: [],
|
|
1845
|
+
providers: [],
|
|
1846
|
+
themeOverrides: {},
|
|
1847
|
+
handleFactories: []
|
|
1848
|
+
};
|
|
1849
|
+
const ExtensionResolvedContext = React.createContext(EMPTY_RESOLVED);
|
|
1850
|
+
const ExtensionProvider = ({ extensions, children }) => {
|
|
1851
|
+
const resolved = React.useMemo(
|
|
1852
|
+
() => extensions && extensions.length > 0 ? resolveExtensions(extensions) : EMPTY_RESOLVED,
|
|
1853
|
+
[extensions]
|
|
1854
|
+
);
|
|
1855
|
+
let wrapped = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1856
|
+
for (let i = resolved.providers.length - 1; i >= 0; i--) {
|
|
1857
|
+
const Provider = resolved.providers[i];
|
|
1858
|
+
wrapped = /* @__PURE__ */ jsxRuntime.jsx(Provider, { children: wrapped });
|
|
1859
|
+
}
|
|
1860
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ExtensionResolvedContext.Provider, { value: resolved, children: wrapped });
|
|
1861
|
+
};
|
|
1862
|
+
function useExtensions() {
|
|
1863
|
+
return React.useContext(ExtensionResolvedContext);
|
|
1864
|
+
}
|
|
1865
|
+
const ExtensionStateContext = React.createContext(null);
|
|
1866
|
+
const ExtensionStateProvider = ({ children }) => {
|
|
1867
|
+
const stateRef = React.useRef({});
|
|
1868
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ExtensionStateContext.Provider, { value: stateRef, children });
|
|
1869
|
+
};
|
|
1870
|
+
function useExtensionState() {
|
|
1871
|
+
const stateRef = React.useContext(ExtensionStateContext);
|
|
1872
|
+
return {
|
|
1873
|
+
get: (key) => stateRef == null ? void 0 : stateRef.current[key],
|
|
1874
|
+
set: (key, value) => {
|
|
1875
|
+
if (stateRef) stateRef.current[key] = value;
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
function useExtensionContext(getDocument, getActiveEditor) {
|
|
1880
|
+
const stateRef = React.useContext(ExtensionStateContext);
|
|
1881
|
+
return React.useMemo(() => ({
|
|
1882
|
+
getDocument,
|
|
1883
|
+
getActiveEditor,
|
|
1884
|
+
getExtensionState: (key) => stateRef == null ? void 0 : stateRef.current[key],
|
|
1885
|
+
setExtensionState: (key, value) => {
|
|
1886
|
+
if (stateRef) stateRef.current[key] = value;
|
|
1887
|
+
}
|
|
1888
|
+
}), [getDocument, getActiveEditor, stateRef]);
|
|
1889
|
+
}
|
|
1660
1890
|
const SUPPORTED_FONTS = [
|
|
1891
|
+
"Inter",
|
|
1661
1892
|
"Times New Roman",
|
|
1662
1893
|
"Arial",
|
|
1663
1894
|
"Calibri",
|
|
@@ -1676,6 +1907,42 @@ function applyFontFamily(editor, fontFamily) {
|
|
|
1676
1907
|
}
|
|
1677
1908
|
});
|
|
1678
1909
|
}
|
|
1910
|
+
const SUPPORTED_FONT_SIZES = [
|
|
1911
|
+
8,
|
|
1912
|
+
9,
|
|
1913
|
+
10,
|
|
1914
|
+
11,
|
|
1915
|
+
12,
|
|
1916
|
+
14,
|
|
1917
|
+
16,
|
|
1918
|
+
18,
|
|
1919
|
+
20,
|
|
1920
|
+
24,
|
|
1921
|
+
28,
|
|
1922
|
+
32,
|
|
1923
|
+
36,
|
|
1924
|
+
48,
|
|
1925
|
+
72
|
|
1926
|
+
];
|
|
1927
|
+
function applyFontSize(editor, size) {
|
|
1928
|
+
editor.update(() => {
|
|
1929
|
+
const selection = lexical.$getSelection();
|
|
1930
|
+
if (!lexical.$isRangeSelection(selection)) return;
|
|
1931
|
+
const nodes = selection.getNodes();
|
|
1932
|
+
for (const node of nodes) {
|
|
1933
|
+
if (lexical.$isTextNode(node)) {
|
|
1934
|
+
const existing = node.getStyle();
|
|
1935
|
+
const updated = mergeFontSize(existing, size);
|
|
1936
|
+
node.setStyle(updated);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
function mergeFontSize(existingStyle, size) {
|
|
1942
|
+
const stripped = existingStyle.replace(/font-size:\s*[^;]+;?\s*/g, "").trim();
|
|
1943
|
+
const sizeDecl = `font-size: ${size}pt`;
|
|
1944
|
+
return stripped ? `${stripped}; ${sizeDecl}` : sizeDecl;
|
|
1945
|
+
}
|
|
1679
1946
|
function toggleFormat(editor, format) {
|
|
1680
1947
|
editor.dispatchCommand(lexical.FORMAT_TEXT_COMMAND, format);
|
|
1681
1948
|
}
|
|
@@ -1723,6 +1990,8 @@ const Toolbar = () => {
|
|
|
1723
1990
|
setHistorySidebarOpen,
|
|
1724
1991
|
undo
|
|
1725
1992
|
} = useDocument();
|
|
1993
|
+
const { toolbarItems } = useExtensions();
|
|
1994
|
+
const t = useTranslations();
|
|
1726
1995
|
const withBodySelection = React.useCallback(
|
|
1727
1996
|
(editor, action) => {
|
|
1728
1997
|
editor.update(() => {
|
|
@@ -1760,7 +2029,7 @@ const Toolbar = () => {
|
|
|
1760
2029
|
);
|
|
1761
2030
|
const handleToggle = (enabled) => {
|
|
1762
2031
|
runToolbarAction(
|
|
1763
|
-
enabled ?
|
|
2032
|
+
enabled ? t.history.actions.enabledHeadersFooters : t.history.actions.disabledHeadersFooters,
|
|
1764
2033
|
() => {
|
|
1765
2034
|
dispatch({ type: "SET_HEADER_FOOTER_ENABLED", enabled });
|
|
1766
2035
|
}
|
|
@@ -1768,114 +2037,123 @@ const Toolbar = () => {
|
|
|
1768
2037
|
};
|
|
1769
2038
|
const handleCopyHeaderToAll = () => {
|
|
1770
2039
|
if (activePageId) {
|
|
1771
|
-
runToolbarAction(
|
|
2040
|
+
runToolbarAction(t.history.actions.copiedHeaderToAll, () => {
|
|
1772
2041
|
dispatch({ type: "COPY_HEADER_TO_ALL", sourcePageId: activePageId });
|
|
1773
2042
|
});
|
|
1774
2043
|
}
|
|
1775
2044
|
};
|
|
1776
2045
|
const handleCopyFooterToAll = () => {
|
|
1777
2046
|
if (activePageId) {
|
|
1778
|
-
runToolbarAction(
|
|
2047
|
+
runToolbarAction(t.history.actions.copiedFooterToAll, () => {
|
|
1779
2048
|
dispatch({ type: "COPY_FOOTER_TO_ALL", sourcePageId: activePageId });
|
|
1780
2049
|
});
|
|
1781
2050
|
}
|
|
1782
2051
|
};
|
|
1783
2052
|
const handleClearHeader = () => {
|
|
1784
2053
|
if (activePageId) {
|
|
1785
|
-
runToolbarAction(
|
|
2054
|
+
runToolbarAction(t.history.actions.clearedHeader, () => {
|
|
1786
2055
|
dispatch({ type: "CLEAR_HEADER", pageId: activePageId });
|
|
1787
2056
|
});
|
|
1788
2057
|
}
|
|
1789
2058
|
};
|
|
1790
2059
|
const handleClearFooter = () => {
|
|
1791
2060
|
if (activePageId) {
|
|
1792
|
-
runToolbarAction(
|
|
2061
|
+
runToolbarAction(t.history.actions.clearedFooter, () => {
|
|
1793
2062
|
dispatch({ type: "CLEAR_FOOTER", pageId: activePageId });
|
|
1794
2063
|
});
|
|
1795
2064
|
}
|
|
1796
2065
|
};
|
|
1797
|
-
const handleClearAllHeaders = () => runToolbarAction(
|
|
2066
|
+
const handleClearAllHeaders = () => runToolbarAction(t.history.actions.clearedAllHeaders, () => {
|
|
1798
2067
|
dispatch({ type: "CLEAR_ALL_HEADERS" });
|
|
1799
2068
|
});
|
|
1800
|
-
const handleClearAllFooters = () => runToolbarAction(
|
|
2069
|
+
const handleClearAllFooters = () => runToolbarAction(t.history.actions.clearedAllFooters, () => {
|
|
1801
2070
|
dispatch({ type: "CLEAR_ALL_FOOTERS" });
|
|
1802
2071
|
});
|
|
1803
2072
|
const handlePageCounterModeChange = React.useCallback((mode) => {
|
|
1804
|
-
runToolbarAction(
|
|
2073
|
+
runToolbarAction(interpolate(t.history.actions.pageCounterSet, { value: mode }), () => {
|
|
1805
2074
|
dispatch({ type: "SET_PAGE_COUNTER_MODE", mode });
|
|
1806
2075
|
});
|
|
1807
|
-
}, [dispatch, runToolbarAction]);
|
|
2076
|
+
}, [dispatch, runToolbarAction, t.history.actions.pageCounterSet]);
|
|
1808
2077
|
const handleBold = React.useCallback(() => {
|
|
1809
2078
|
debug("toolbar", `bold (globalSelection=${globalSelectionActive}, editors=${editorRegistry.all().length}, hasEditor=${!!activeEditor})`);
|
|
1810
|
-
runToolbarAction(
|
|
2079
|
+
runToolbarAction(t.history.actions.boldApplied, () => {
|
|
1811
2080
|
applyToBodyEditors(toggleBold);
|
|
1812
2081
|
});
|
|
1813
|
-
}, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction]);
|
|
2082
|
+
}, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction, t.history.actions.boldApplied]);
|
|
1814
2083
|
const handleItalic = React.useCallback(() => {
|
|
1815
2084
|
debug("toolbar", `italic (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
|
|
1816
|
-
runToolbarAction(
|
|
2085
|
+
runToolbarAction(t.history.actions.italicApplied, () => {
|
|
1817
2086
|
applyToBodyEditors(toggleItalic);
|
|
1818
2087
|
});
|
|
1819
|
-
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction]);
|
|
2088
|
+
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.italicApplied]);
|
|
1820
2089
|
const handleUnderline = React.useCallback(() => {
|
|
1821
2090
|
debug("toolbar", `underline (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
|
|
1822
|
-
runToolbarAction(
|
|
2091
|
+
runToolbarAction(t.history.actions.underlineApplied, () => {
|
|
1823
2092
|
applyToBodyEditors(toggleUnderline);
|
|
1824
2093
|
});
|
|
1825
|
-
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction]);
|
|
2094
|
+
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.underlineApplied]);
|
|
1826
2095
|
const handleStrikethrough = React.useCallback(() => {
|
|
1827
2096
|
debug("toolbar", `strikethrough (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
|
|
1828
|
-
runToolbarAction(
|
|
2097
|
+
runToolbarAction(t.history.actions.strikethroughApplied, () => {
|
|
1829
2098
|
applyToBodyEditors(toggleStrikethrough);
|
|
1830
2099
|
});
|
|
1831
|
-
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction]);
|
|
2100
|
+
}, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.strikethroughApplied]);
|
|
1832
2101
|
const handleAlignLeft = React.useCallback(() => {
|
|
1833
|
-
runToolbarAction(
|
|
2102
|
+
runToolbarAction(t.history.actions.alignedLeft, () => {
|
|
1834
2103
|
applyToBodyEditors((editor) => setAlignment(editor, "left"));
|
|
1835
2104
|
});
|
|
1836
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2105
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.alignedLeft]);
|
|
1837
2106
|
const handleAlignCenter = React.useCallback(() => {
|
|
1838
|
-
runToolbarAction(
|
|
2107
|
+
runToolbarAction(t.history.actions.alignedCenter, () => {
|
|
1839
2108
|
applyToBodyEditors((editor) => setAlignment(editor, "center"));
|
|
1840
2109
|
});
|
|
1841
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2110
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.alignedCenter]);
|
|
1842
2111
|
const handleAlignRight = React.useCallback(() => {
|
|
1843
|
-
runToolbarAction(
|
|
2112
|
+
runToolbarAction(t.history.actions.alignedRight, () => {
|
|
1844
2113
|
applyToBodyEditors((editor) => setAlignment(editor, "right"));
|
|
1845
2114
|
});
|
|
1846
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2115
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.alignedRight]);
|
|
1847
2116
|
const handleAlignJustify = React.useCallback(() => {
|
|
1848
|
-
runToolbarAction(
|
|
2117
|
+
runToolbarAction(t.history.actions.justifiedText, () => {
|
|
1849
2118
|
applyToBodyEditors((editor) => setAlignment(editor, "justify"));
|
|
1850
2119
|
});
|
|
1851
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2120
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.justifiedText]);
|
|
1852
2121
|
const handleListNumber = React.useCallback(() => {
|
|
1853
|
-
runToolbarAction(
|
|
2122
|
+
runToolbarAction(t.history.actions.insertedNumberedList, () => {
|
|
1854
2123
|
applyToBodyEditors((editor) => insertList(editor, "number"));
|
|
1855
2124
|
});
|
|
1856
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2125
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.insertedNumberedList]);
|
|
1857
2126
|
const handleListBullet = React.useCallback(() => {
|
|
1858
|
-
runToolbarAction(
|
|
2127
|
+
runToolbarAction(t.history.actions.insertedBulletList, () => {
|
|
1859
2128
|
applyToBodyEditors((editor) => insertList(editor, "bullet"));
|
|
1860
2129
|
});
|
|
1861
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2130
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.insertedBulletList]);
|
|
1862
2131
|
const handleIndent = React.useCallback(() => {
|
|
1863
|
-
runToolbarAction(
|
|
2132
|
+
runToolbarAction(t.history.actions.indentedContent, () => {
|
|
1864
2133
|
applyToBodyEditors(indentContent);
|
|
1865
2134
|
});
|
|
1866
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2135
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.indentedContent]);
|
|
1867
2136
|
const handleOutdent = React.useCallback(() => {
|
|
1868
|
-
runToolbarAction(
|
|
2137
|
+
runToolbarAction(t.history.actions.outdentedContent, () => {
|
|
1869
2138
|
applyToBodyEditors(outdentContent);
|
|
1870
2139
|
});
|
|
1871
|
-
}, [applyToBodyEditors, runToolbarAction]);
|
|
2140
|
+
}, [applyToBodyEditors, runToolbarAction, t.history.actions.outdentedContent]);
|
|
1872
2141
|
const handleFontChange = React.useCallback(
|
|
1873
2142
|
(e) => {
|
|
1874
|
-
runToolbarAction(
|
|
2143
|
+
runToolbarAction(interpolate(t.history.actions.fontChanged, { value: e.target.value }), () => {
|
|
1875
2144
|
applyToBodyEditors((editor) => applyFontFamily(editor, e.target.value));
|
|
1876
2145
|
});
|
|
1877
2146
|
},
|
|
1878
|
-
[applyToBodyEditors, runToolbarAction]
|
|
2147
|
+
[applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
|
|
2148
|
+
);
|
|
2149
|
+
const handleFontSizeChange = React.useCallback(
|
|
2150
|
+
(e) => {
|
|
2151
|
+
const size = parseInt(e.target.value, 10);
|
|
2152
|
+
runToolbarAction(interpolate(t.history.actions.fontSizeChanged, { value: String(size) }), () => {
|
|
2153
|
+
applyToBodyEditors((editor) => applyFontSize(editor, size));
|
|
2154
|
+
});
|
|
2155
|
+
},
|
|
2156
|
+
[applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
|
|
1879
2157
|
);
|
|
1880
2158
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1881
2159
|
"div",
|
|
@@ -1887,7 +2165,7 @@ const Toolbar = () => {
|
|
|
1887
2165
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1888
2166
|
ToolbarIconButton,
|
|
1889
2167
|
{
|
|
1890
|
-
title:
|
|
2168
|
+
title: t.toolbar.undo,
|
|
1891
2169
|
testId: "btn-undo",
|
|
1892
2170
|
disabled: !canUndo,
|
|
1893
2171
|
onClick: undo,
|
|
@@ -1897,7 +2175,7 @@ const Toolbar = () => {
|
|
|
1897
2175
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1898
2176
|
ToolbarIconButton,
|
|
1899
2177
|
{
|
|
1900
|
-
title:
|
|
2178
|
+
title: t.toolbar.redo,
|
|
1901
2179
|
testId: "btn-redo",
|
|
1902
2180
|
disabled: !canRedo,
|
|
1903
2181
|
onClick: redo,
|
|
@@ -1911,31 +2189,41 @@ const Toolbar = () => {
|
|
|
1911
2189
|
{
|
|
1912
2190
|
className: "h-7 rounded border border-gray-200 bg-white px-2 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
|
|
1913
2191
|
"data-testid": "font-selector",
|
|
1914
|
-
defaultValue: "
|
|
2192
|
+
defaultValue: "Inter",
|
|
1915
2193
|
onChange: handleFontChange,
|
|
1916
2194
|
children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
|
|
1917
2195
|
}
|
|
1918
2196
|
),
|
|
2197
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2198
|
+
"select",
|
|
2199
|
+
{
|
|
2200
|
+
className: "h-7 w-16 rounded border border-gray-200 bg-white px-1 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
|
|
2201
|
+
"data-testid": "font-size-selector",
|
|
2202
|
+
defaultValue: "12",
|
|
2203
|
+
onChange: handleFontSizeChange,
|
|
2204
|
+
children: SUPPORTED_FONT_SIZES.map((size) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: size, children: size }, size))
|
|
2205
|
+
}
|
|
2206
|
+
),
|
|
1919
2207
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
1920
2208
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "format-group", children: [
|
|
1921
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1922
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1923
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1924
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
2209
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bold, testId: "btn-bold", onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
|
|
2210
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.italic, testId: "btn-italic", onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
|
|
2211
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.underline, testId: "btn-underline", onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
|
|
2212
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.strikethrough, testId: "btn-strike", onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
|
|
1925
2213
|
] }),
|
|
1926
2214
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
1927
2215
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "align-group", children: [
|
|
1928
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1929
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1930
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1931
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
2216
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignLeft, testId: "btn-align-left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
|
|
2217
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignCenter, testId: "btn-align-center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
|
|
2218
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignRight, testId: "btn-align-right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
|
|
2219
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.justify, testId: "btn-align-justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
|
|
1932
2220
|
] }),
|
|
1933
2221
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
1934
2222
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "list-group", children: [
|
|
1935
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1936
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1937
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
1938
|
-
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title:
|
|
2223
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.numberedList, testId: "btn-list-number", onClick: handleListNumber, children: /* @__PURE__ */ jsxRuntime.jsx(ListOrdered, { size: 15 }) }),
|
|
2224
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bulletList, testId: "btn-list-bullet", onClick: handleListBullet, children: /* @__PURE__ */ jsxRuntime.jsx(List, { size: 15 }) }),
|
|
2225
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.indent, testId: "btn-indent", onClick: handleIndent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentIncrease, { size: 15 }) }),
|
|
2226
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.outdent, testId: "btn-outdent", onClick: handleOutdent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentDecrease, { size: 15 }) })
|
|
1939
2227
|
] }),
|
|
1940
2228
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
1941
2229
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1962,10 +2250,14 @@ const Toolbar = () => {
|
|
|
1962
2250
|
}
|
|
1963
2251
|
)
|
|
1964
2252
|
] }),
|
|
2253
|
+
toolbarItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2254
|
+
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
2255
|
+
toolbarItems.map((ToolbarItem, idx) => /* @__PURE__ */ jsxRuntime.jsx(ToolbarItem, {}, idx))
|
|
2256
|
+
] }),
|
|
1965
2257
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1966
2258
|
ToolbarIconButton,
|
|
1967
2259
|
{
|
|
1968
|
-
title: historySidebarOpen ?
|
|
2260
|
+
title: historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
|
|
1969
2261
|
testId: "toggle-history-sidebar",
|
|
1970
2262
|
active: historySidebarOpen,
|
|
1971
2263
|
onClick: () => setHistorySidebarOpen(!historySidebarOpen),
|
|
@@ -2268,12 +2560,13 @@ const lexicalTheme = {
|
|
|
2268
2560
|
},
|
|
2269
2561
|
quote: "border-l-4 border-gray-300 pl-4 italic text-gray-600"
|
|
2270
2562
|
};
|
|
2271
|
-
|
|
2563
|
+
const DEFAULT_NODES = [richText.HeadingNode, richText.QuoteNode, list.ListNode, list.ListItemNode];
|
|
2564
|
+
function createEditorConfig(mode, pageId, extraNodes = [], themeOverrides = {}) {
|
|
2272
2565
|
const namespace = pageId ? `lex4-${mode}-${pageId}` : `lex4-${mode}`;
|
|
2273
2566
|
return {
|
|
2274
2567
|
namespace,
|
|
2275
|
-
theme: lexicalTheme,
|
|
2276
|
-
nodes: [
|
|
2568
|
+
theme: { ...lexicalTheme, ...themeOverrides },
|
|
2569
|
+
nodes: [...DEFAULT_NODES, ...extraNodes],
|
|
2277
2570
|
onError: (error) => {
|
|
2278
2571
|
console.error(`[Lex4 ${mode} editor error]`, error);
|
|
2279
2572
|
}
|
|
@@ -2851,11 +3144,12 @@ const PageBody = ({
|
|
|
2851
3144
|
onMoveToNextPage,
|
|
2852
3145
|
readOnly = false
|
|
2853
3146
|
}) => {
|
|
3147
|
+
const { nodes, bodyPlugins, themeOverrides } = useExtensions();
|
|
2854
3148
|
const config = React.useMemo(
|
|
2855
3149
|
() => {
|
|
2856
3150
|
var _a, _b;
|
|
2857
3151
|
const baseConfig = {
|
|
2858
|
-
...createEditorConfig("body", pageId),
|
|
3152
|
+
...createEditorConfig("body", pageId, nodes, themeOverrides),
|
|
2859
3153
|
editable: !readOnly
|
|
2860
3154
|
};
|
|
2861
3155
|
if (initialBodyState) {
|
|
@@ -2869,7 +3163,7 @@ const PageBody = ({
|
|
|
2869
3163
|
return baseConfig;
|
|
2870
3164
|
},
|
|
2871
3165
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally only use initialBodyState at mount time
|
|
2872
|
-
[pageId, readOnly]
|
|
3166
|
+
[pageId, readOnly, nodes, themeOverrides]
|
|
2873
3167
|
);
|
|
2874
3168
|
const handleChange = React.useCallback(
|
|
2875
3169
|
(editorState) => {
|
|
@@ -2925,6 +3219,7 @@ const PageBody = ({
|
|
|
2925
3219
|
}
|
|
2926
3220
|
),
|
|
2927
3221
|
/* @__PURE__ */ jsxRuntime.jsx(OverflowPlugin, { onOverflow: handleOverflow }),
|
|
3222
|
+
bodyPlugins.map((Plugin, idx) => /* @__PURE__ */ jsxRuntime.jsx(Plugin, {}, idx)),
|
|
2928
3223
|
/* @__PURE__ */ jsxRuntime.jsx(LexicalOnChangePlugin.OnChangePlugin, { onChange: handleChange, ignoreSelectionChange: true })
|
|
2929
3224
|
] })
|
|
2930
3225
|
}
|
|
@@ -2994,16 +3289,18 @@ const PageHeader = ({
|
|
|
2994
3289
|
onHeaderChange
|
|
2995
3290
|
}) => {
|
|
2996
3291
|
const hasPageCounter = !!pageCounterLabel;
|
|
3292
|
+
const { nodes, themeOverrides } = useExtensions();
|
|
3293
|
+
const t = useTranslations();
|
|
2997
3294
|
const config = React.useMemo(
|
|
2998
3295
|
() => {
|
|
2999
|
-
const baseConfig = createEditorConfig("header", pageId);
|
|
3296
|
+
const baseConfig = createEditorConfig("header", pageId, nodes, themeOverrides);
|
|
3000
3297
|
if (initialHeaderState) {
|
|
3001
3298
|
return { ...baseConfig, editorState: JSON.stringify(initialHeaderState) };
|
|
3002
3299
|
}
|
|
3003
3300
|
return baseConfig;
|
|
3004
3301
|
},
|
|
3005
3302
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- syncVersion forces remount via key, only used at init
|
|
3006
|
-
[pageId]
|
|
3303
|
+
[pageId, nodes, themeOverrides]
|
|
3007
3304
|
);
|
|
3008
3305
|
const contentRef = React.useRef(null);
|
|
3009
3306
|
const handleChange = React.useCallback(
|
|
@@ -3029,7 +3326,7 @@ const PageHeader = ({
|
|
|
3029
3326
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3030
3327
|
"div",
|
|
3031
3328
|
{
|
|
3032
|
-
className: "lex4-page-header border-b border-dashed border-
|
|
3329
|
+
className: "lex4-page-header bg-blue-50/60 border-t-2 border-t-blue-200 border-b border-dashed border-blue-100 relative flex-shrink-0",
|
|
3033
3330
|
style: { maxHeight: MAX_HEADER_HEIGHT_PX, overflow: "clip" },
|
|
3034
3331
|
"data-testid": `page-header-${pageId}`,
|
|
3035
3332
|
children: [
|
|
@@ -3044,7 +3341,7 @@ const PageHeader = ({
|
|
|
3044
3341
|
className: `outline-none p-2 text-sm text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
|
|
3045
3342
|
}
|
|
3046
3343
|
),
|
|
3047
|
-
placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 text-sm ${hasPageCounter ? "pr-24" : ""}`, children:
|
|
3344
|
+
placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 text-sm ${hasPageCounter ? "pr-24" : ""}`, children: t.header.placeholder }),
|
|
3048
3345
|
ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
|
|
3049
3346
|
}
|
|
3050
3347
|
),
|
|
@@ -3072,16 +3369,18 @@ const PageFooter = ({
|
|
|
3072
3369
|
onFooterChange
|
|
3073
3370
|
}) => {
|
|
3074
3371
|
const hasPageCounter = !!pageCounterLabel;
|
|
3372
|
+
const { nodes, themeOverrides } = useExtensions();
|
|
3373
|
+
const t = useTranslations();
|
|
3075
3374
|
const config = React.useMemo(
|
|
3076
3375
|
() => {
|
|
3077
|
-
const baseConfig = createEditorConfig("footer", pageId);
|
|
3376
|
+
const baseConfig = createEditorConfig("footer", pageId, nodes, themeOverrides);
|
|
3078
3377
|
if (initialFooterState) {
|
|
3079
3378
|
return { ...baseConfig, editorState: JSON.stringify(initialFooterState) };
|
|
3080
3379
|
}
|
|
3081
3380
|
return baseConfig;
|
|
3082
3381
|
},
|
|
3083
3382
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- syncVersion forces remount via key, only used at init
|
|
3084
|
-
[pageId]
|
|
3383
|
+
[pageId, nodes, themeOverrides]
|
|
3085
3384
|
);
|
|
3086
3385
|
const contentRef = React.useRef(null);
|
|
3087
3386
|
const handleChange = React.useCallback(
|
|
@@ -3107,7 +3406,7 @@ const PageFooter = ({
|
|
|
3107
3406
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3108
3407
|
"div",
|
|
3109
3408
|
{
|
|
3110
|
-
className: "lex4-page-footer border-t border-dashed border-
|
|
3409
|
+
className: "lex4-page-footer bg-blue-50/60 border-b-2 border-b-blue-200 border-t border-dashed border-blue-100 relative flex-shrink-0",
|
|
3111
3410
|
style: { maxHeight: MAX_FOOTER_HEIGHT_PX, overflow: "clip" },
|
|
3112
3411
|
"data-testid": `page-footer-${pageId}`,
|
|
3113
3412
|
children: [
|
|
@@ -3122,7 +3421,7 @@ const PageFooter = ({
|
|
|
3122
3421
|
className: `outline-none p-2 text-sm text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
|
|
3123
3422
|
}
|
|
3124
3423
|
),
|
|
3125
|
-
placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 text-sm ${hasPageCounter ? "pr-24" : ""}`, children:
|
|
3424
|
+
placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 text-sm ${hasPageCounter ? "pr-24" : ""}`, children: t.footer.placeholder }),
|
|
3126
3425
|
ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
|
|
3127
3426
|
}
|
|
3128
3427
|
),
|
|
@@ -3188,7 +3487,7 @@ const PageView = React.memo(({
|
|
|
3188
3487
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3189
3488
|
"div",
|
|
3190
3489
|
{
|
|
3191
|
-
className: "lex4-page bg-white shadow-
|
|
3490
|
+
className: "lex4-page bg-white shadow-xl flex flex-col",
|
|
3192
3491
|
style: {
|
|
3193
3492
|
width: A4_WIDTH_PX,
|
|
3194
3493
|
height: A4_HEIGHT_PX,
|
|
@@ -3524,6 +3823,7 @@ const EditorChrome = ({
|
|
|
3524
3823
|
undo,
|
|
3525
3824
|
redo
|
|
3526
3825
|
} = useDocument();
|
|
3826
|
+
const { sidePanels } = useExtensions();
|
|
3527
3827
|
const rootRef = React.useRef(null);
|
|
3528
3828
|
const selectionBufferRef = React.useRef(null);
|
|
3529
3829
|
const clearGlobalSelection = React.useCallback(() => {
|
|
@@ -3665,35 +3965,63 @@ const EditorChrome = ({
|
|
|
3665
3965
|
}
|
|
3666
3966
|
),
|
|
3667
3967
|
/* @__PURE__ */ jsxRuntime.jsx(Toolbar, {}),
|
|
3668
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden bg-gray-
|
|
3968
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden bg-gray-700", children: [
|
|
3669
3969
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(DocumentView, {}) }),
|
|
3970
|
+
sidePanels.map((Panel, idx) => /* @__PURE__ */ jsxRuntime.jsx(Panel, {}, idx)),
|
|
3670
3971
|
/* @__PURE__ */ jsxRuntime.jsx(HistorySidebar, {})
|
|
3671
3972
|
] })
|
|
3672
3973
|
]
|
|
3673
3974
|
}
|
|
3674
3975
|
);
|
|
3675
3976
|
};
|
|
3676
|
-
const
|
|
3977
|
+
const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, className }, ref) => {
|
|
3978
|
+
const { document: doc, activeEditor } = useDocument();
|
|
3979
|
+
const { handleFactories } = useExtensions();
|
|
3980
|
+
const getDocument = React.useCallback(() => doc, [doc]);
|
|
3981
|
+
const getActiveEditor = React.useCallback(() => activeEditor, [activeEditor]);
|
|
3982
|
+
const extensionCtx = useExtensionContext(getDocument, getActiveEditor);
|
|
3983
|
+
React.useImperativeHandle(ref, () => {
|
|
3984
|
+
const handle = {};
|
|
3985
|
+
for (const factory of handleFactories) {
|
|
3986
|
+
const methods = factory(extensionCtx);
|
|
3987
|
+
Object.assign(handle, methods);
|
|
3988
|
+
}
|
|
3989
|
+
return handle;
|
|
3990
|
+
}, [extensionCtx, handleFactories]);
|
|
3991
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3992
|
+
EditorChrome,
|
|
3993
|
+
{
|
|
3994
|
+
captureHistoryShortcutsOnWindow,
|
|
3995
|
+
className
|
|
3996
|
+
}
|
|
3997
|
+
);
|
|
3998
|
+
});
|
|
3999
|
+
EditorWithHandle.displayName = "EditorWithHandle";
|
|
4000
|
+
const Lex4Editor = React.forwardRef(({
|
|
3677
4001
|
captureHistoryShortcutsOnWindow = true,
|
|
3678
4002
|
initialDocument,
|
|
3679
4003
|
onDocumentChange,
|
|
4004
|
+
extensions,
|
|
4005
|
+
translations,
|
|
3680
4006
|
className
|
|
3681
|
-
}) => {
|
|
3682
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4007
|
+
}, ref) => {
|
|
4008
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3683
4009
|
DocumentProvider,
|
|
3684
4010
|
{
|
|
3685
4011
|
initialDocument,
|
|
3686
4012
|
onDocumentChange,
|
|
3687
4013
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3688
|
-
|
|
4014
|
+
EditorWithHandle,
|
|
3689
4015
|
{
|
|
4016
|
+
ref,
|
|
3690
4017
|
captureHistoryShortcutsOnWindow,
|
|
3691
4018
|
className
|
|
3692
4019
|
}
|
|
3693
4020
|
)
|
|
3694
4021
|
}
|
|
3695
|
-
);
|
|
3696
|
-
};
|
|
4022
|
+
) }) }) });
|
|
4023
|
+
});
|
|
4024
|
+
Lex4Editor.displayName = "Lex4Editor";
|
|
3697
4025
|
function useOverflowDetection(bodyHeight, onOverflow, debounceMs = 100) {
|
|
3698
4026
|
const timerRef = React.useRef(null);
|
|
3699
4027
|
const observerRef = React.useRef(null);
|
|
@@ -3759,16 +4087,700 @@ function useHeaderFooter(maxHeight, onHeightChange) {
|
|
|
3759
4087
|
}, []);
|
|
3760
4088
|
return { attachRef };
|
|
3761
4089
|
}
|
|
4090
|
+
const AST_VERSION = "1.0.0";
|
|
4091
|
+
const IS_BOLD = 1;
|
|
4092
|
+
const IS_ITALIC = 2;
|
|
4093
|
+
const IS_STRIKETHROUGH = 4;
|
|
4094
|
+
const IS_UNDERLINE = 8;
|
|
4095
|
+
function decodeFormatBitmask(format) {
|
|
4096
|
+
const marks = {};
|
|
4097
|
+
if (format & IS_BOLD) marks.bold = true;
|
|
4098
|
+
if (format & IS_ITALIC) marks.italic = true;
|
|
4099
|
+
if (format & IS_UNDERLINE) marks.underline = true;
|
|
4100
|
+
if (format & IS_STRIKETHROUGH) marks.strikethrough = true;
|
|
4101
|
+
return marks;
|
|
4102
|
+
}
|
|
4103
|
+
function extractFontFamily(style) {
|
|
4104
|
+
const match = style.match(/font-family:\s*([^;]+)/);
|
|
4105
|
+
return match ? match[1].trim().replace(/['"]/g, "") : void 0;
|
|
4106
|
+
}
|
|
4107
|
+
function extractFontSizePt(style) {
|
|
4108
|
+
const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
|
|
4109
|
+
return match ? parseFloat(match[1]) : void 0;
|
|
4110
|
+
}
|
|
4111
|
+
function buildTextMarks(format, style) {
|
|
4112
|
+
const formatMarks = decodeFormatBitmask(format);
|
|
4113
|
+
const fontFamily = style ? extractFontFamily(style) : void 0;
|
|
4114
|
+
const fontSize = style ? extractFontSizePt(style) : void 0;
|
|
4115
|
+
const marks = {
|
|
4116
|
+
...formatMarks,
|
|
4117
|
+
...fontFamily ? { fontFamily } : {},
|
|
4118
|
+
...fontSize ? { fontSize } : {}
|
|
4119
|
+
};
|
|
4120
|
+
return Object.keys(marks).length > 0 ? marks : void 0;
|
|
4121
|
+
}
|
|
4122
|
+
function mapInlineNode(node) {
|
|
4123
|
+
switch (node.type) {
|
|
4124
|
+
case "text":
|
|
4125
|
+
return mapTextNode(node);
|
|
4126
|
+
case "variable-node":
|
|
4127
|
+
return mapVariableNode(node);
|
|
4128
|
+
case "linebreak":
|
|
4129
|
+
return mapLineBreak();
|
|
4130
|
+
default:
|
|
4131
|
+
return { type: "text", text: "" };
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
function mapTextNode(node) {
|
|
4135
|
+
const marks = buildTextMarks(node.format, node.style);
|
|
4136
|
+
return {
|
|
4137
|
+
type: "text",
|
|
4138
|
+
text: node.text,
|
|
4139
|
+
...marks ? { marks } : {}
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
function mapVariableNode(node) {
|
|
4143
|
+
return {
|
|
4144
|
+
type: "variable",
|
|
4145
|
+
key: node.variableKey
|
|
4146
|
+
};
|
|
4147
|
+
}
|
|
4148
|
+
function mapLineBreak() {
|
|
4149
|
+
return { type: "linebreak" };
|
|
4150
|
+
}
|
|
4151
|
+
function mapInlineNodes(nodes) {
|
|
4152
|
+
return nodes.map(mapInlineNode);
|
|
4153
|
+
}
|
|
4154
|
+
const ALIGN_LEFT = 1;
|
|
4155
|
+
const ALIGN_CENTER = 2;
|
|
4156
|
+
const ALIGN_RIGHT = 3;
|
|
4157
|
+
const ALIGN_JUSTIFY = 4;
|
|
4158
|
+
function decodeAlignment(format) {
|
|
4159
|
+
if (typeof format === "string") {
|
|
4160
|
+
if (["left", "center", "right", "justify"].includes(format)) {
|
|
4161
|
+
return format;
|
|
4162
|
+
}
|
|
4163
|
+
return void 0;
|
|
4164
|
+
}
|
|
4165
|
+
if (typeof format !== "number" || format === 0) return void 0;
|
|
4166
|
+
switch (format) {
|
|
4167
|
+
case ALIGN_LEFT:
|
|
4168
|
+
return "left";
|
|
4169
|
+
case ALIGN_CENTER:
|
|
4170
|
+
return "center";
|
|
4171
|
+
case ALIGN_RIGHT:
|
|
4172
|
+
return "right";
|
|
4173
|
+
case ALIGN_JUSTIFY:
|
|
4174
|
+
return "justify";
|
|
4175
|
+
default:
|
|
4176
|
+
return void 0;
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
function mapBlockNode(node) {
|
|
4180
|
+
switch (node.type) {
|
|
4181
|
+
case "paragraph":
|
|
4182
|
+
return mapParagraph(node);
|
|
4183
|
+
case "heading":
|
|
4184
|
+
return mapHeading(node);
|
|
4185
|
+
case "list":
|
|
4186
|
+
return mapList(node);
|
|
4187
|
+
case "quote":
|
|
4188
|
+
return mapBlockQuote(node);
|
|
4189
|
+
default:
|
|
4190
|
+
return {
|
|
4191
|
+
type: "paragraph",
|
|
4192
|
+
children: mapInlineChildren(node)
|
|
4193
|
+
};
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
function mapParagraph(node) {
|
|
4197
|
+
const alignment = decodeAlignment(node.format);
|
|
4198
|
+
const indent = node.indent && node.indent > 0 ? node.indent : void 0;
|
|
4199
|
+
return {
|
|
4200
|
+
type: "paragraph",
|
|
4201
|
+
...alignment ? { alignment } : {},
|
|
4202
|
+
...indent ? { indent } : {},
|
|
4203
|
+
children: mapInlineChildren(node)
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
function mapHeading(node) {
|
|
4207
|
+
var _a;
|
|
4208
|
+
const alignment = decodeAlignment(node.format);
|
|
4209
|
+
const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
|
|
4210
|
+
const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
|
|
4211
|
+
return {
|
|
4212
|
+
type: "heading",
|
|
4213
|
+
level,
|
|
4214
|
+
...alignment ? { alignment } : {},
|
|
4215
|
+
children: mapInlineChildren(node)
|
|
4216
|
+
};
|
|
4217
|
+
}
|
|
4218
|
+
function mapList(node) {
|
|
4219
|
+
const listType = node.listType === "number" ? "ordered" : "unordered";
|
|
4220
|
+
const items = (node.children ?? []).filter((c) => c.type === "listitem").map(mapListItem);
|
|
4221
|
+
return {
|
|
4222
|
+
type: "list",
|
|
4223
|
+
listType,
|
|
4224
|
+
items
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
function mapListItem(node) {
|
|
4228
|
+
const inlineChildren = [];
|
|
4229
|
+
let nestedList;
|
|
4230
|
+
for (const child of node.children ?? []) {
|
|
4231
|
+
if (child.type === "list") {
|
|
4232
|
+
nestedList = mapList(child);
|
|
4233
|
+
} else {
|
|
4234
|
+
const mapped = mapInlineNodes([child]);
|
|
4235
|
+
inlineChildren.push(...mapped);
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
return {
|
|
4239
|
+
type: "list-item",
|
|
4240
|
+
children: inlineChildren,
|
|
4241
|
+
...nestedList ? { nestedList } : {}
|
|
4242
|
+
};
|
|
4243
|
+
}
|
|
4244
|
+
function mapBlockQuote(node) {
|
|
4245
|
+
return {
|
|
4246
|
+
type: "blockquote",
|
|
4247
|
+
children: mapInlineChildren(node)
|
|
4248
|
+
};
|
|
4249
|
+
}
|
|
4250
|
+
function mapInlineChildren(node) {
|
|
4251
|
+
if (!node.children || node.children.length === 0) return [];
|
|
4252
|
+
return mapInlineNodes(node.children);
|
|
4253
|
+
}
|
|
4254
|
+
function mapBlockNodes(nodes) {
|
|
4255
|
+
return nodes.map(mapBlockNode);
|
|
4256
|
+
}
|
|
4257
|
+
function mapEditorStateToContent(state) {
|
|
4258
|
+
if (!state || !state.root || !state.root.children) {
|
|
4259
|
+
return null;
|
|
4260
|
+
}
|
|
4261
|
+
const blocks = mapBlockNodes(state.root.children);
|
|
4262
|
+
return { blocks };
|
|
4263
|
+
}
|
|
4264
|
+
function mapEditorStateToBlocks(state) {
|
|
4265
|
+
if (!state || !state.root || !state.root.children) {
|
|
4266
|
+
return [];
|
|
4267
|
+
}
|
|
4268
|
+
return mapBlockNodes(state.root.children);
|
|
4269
|
+
}
|
|
4270
|
+
const MARGIN_MM = Math.round(PAGE_MARGIN_PX / PX_PER_MM * 10) / 10;
|
|
4271
|
+
function serializeDocument(document2, variableDefinitions = []) {
|
|
4272
|
+
const pages = document2.pages.map(
|
|
4273
|
+
(page, index) => serializePage(page, index)
|
|
4274
|
+
);
|
|
4275
|
+
const metadata = buildMetadata(variableDefinitions);
|
|
4276
|
+
return {
|
|
4277
|
+
version: AST_VERSION,
|
|
4278
|
+
page: {
|
|
4279
|
+
format: "A4",
|
|
4280
|
+
widthMm: 210,
|
|
4281
|
+
heightMm: 297,
|
|
4282
|
+
margins: {
|
|
4283
|
+
topMm: MARGIN_MM,
|
|
4284
|
+
rightMm: MARGIN_MM,
|
|
4285
|
+
bottomMm: MARGIN_MM,
|
|
4286
|
+
leftMm: MARGIN_MM
|
|
4287
|
+
}
|
|
4288
|
+
},
|
|
4289
|
+
headerFooter: {
|
|
4290
|
+
enabled: document2.headerFooterEnabled,
|
|
4291
|
+
pageCounterMode: document2.pageCounterMode,
|
|
4292
|
+
defaultHeader: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].headerState) : null,
|
|
4293
|
+
defaultFooter: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].footerState) : null
|
|
4294
|
+
},
|
|
4295
|
+
pages,
|
|
4296
|
+
metadata
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4299
|
+
function serializePage(page, pageIndex) {
|
|
4300
|
+
return {
|
|
4301
|
+
pageIndex,
|
|
4302
|
+
body: mapEditorStateToBlocks(page.bodyState),
|
|
4303
|
+
header: mapEditorStateToContent(page.headerState),
|
|
4304
|
+
footer: mapEditorStateToContent(page.footerState)
|
|
4305
|
+
};
|
|
4306
|
+
}
|
|
4307
|
+
function buildMetadata(variableDefinitions) {
|
|
4308
|
+
const variables = {};
|
|
4309
|
+
for (const def of variableDefinitions) {
|
|
4310
|
+
variables[def.key] = {
|
|
4311
|
+
key: def.key,
|
|
4312
|
+
label: def.label,
|
|
4313
|
+
...def.description ? { description: def.description } : {},
|
|
4314
|
+
...def.valueType ? { valueType: def.valueType } : {},
|
|
4315
|
+
...def.group ? { group: def.group } : {}
|
|
4316
|
+
};
|
|
4317
|
+
}
|
|
4318
|
+
return { variables };
|
|
4319
|
+
}
|
|
4320
|
+
function buildSavePayload(ast, options) {
|
|
4321
|
+
return {
|
|
4322
|
+
document: ast,
|
|
4323
|
+
...(options == null ? void 0 : options.exportTarget) ? { exportTarget: options.exportTarget } : {},
|
|
4324
|
+
...(options == null ? void 0 : options.documentId) ? { documentId: options.documentId } : {},
|
|
4325
|
+
...(options == null ? void 0 : options.metadata) ? { metadata: options.metadata } : {}
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
function serializeDocumentJson(ast) {
|
|
4329
|
+
return JSON.stringify(ast, null, 2);
|
|
4330
|
+
}
|
|
4331
|
+
function astExtension() {
|
|
4332
|
+
return {
|
|
4333
|
+
name: "ast",
|
|
4334
|
+
handleMethods: (ctx) => {
|
|
4335
|
+
const getDefinitions = () => {
|
|
4336
|
+
return ctx.getExtensionState("variableDefinitions") ?? [];
|
|
4337
|
+
};
|
|
4338
|
+
return {
|
|
4339
|
+
getDocumentAst: () => {
|
|
4340
|
+
const doc = ctx.getDocument();
|
|
4341
|
+
return serializeDocument(doc, getDefinitions());
|
|
4342
|
+
},
|
|
4343
|
+
getDocumentJson: () => {
|
|
4344
|
+
const doc = ctx.getDocument();
|
|
4345
|
+
const ast = serializeDocument(doc, getDefinitions());
|
|
4346
|
+
return serializeDocumentJson(ast);
|
|
4347
|
+
},
|
|
4348
|
+
buildSavePayload: (options) => {
|
|
4349
|
+
const doc = ctx.getDocument();
|
|
4350
|
+
const ast = serializeDocument(doc, getDefinitions());
|
|
4351
|
+
return buildSavePayload(ast, options);
|
|
4352
|
+
}
|
|
4353
|
+
};
|
|
4354
|
+
}
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
class VariableNode extends lexical.DecoratorNode {
|
|
4358
|
+
constructor(variableKey, key) {
|
|
4359
|
+
super(key);
|
|
4360
|
+
__publicField(this, "__variableKey");
|
|
4361
|
+
this.__variableKey = variableKey;
|
|
4362
|
+
}
|
|
4363
|
+
static getType() {
|
|
4364
|
+
return "variable-node";
|
|
4365
|
+
}
|
|
4366
|
+
static clone(node) {
|
|
4367
|
+
return new VariableNode(node.__variableKey, node.__key);
|
|
4368
|
+
}
|
|
4369
|
+
getVariableKey() {
|
|
4370
|
+
return this.__variableKey;
|
|
4371
|
+
}
|
|
4372
|
+
// -- Serialization --
|
|
4373
|
+
static importJSON(serializedNode) {
|
|
4374
|
+
return $createVariableNode(serializedNode.variableKey);
|
|
4375
|
+
}
|
|
4376
|
+
exportJSON() {
|
|
4377
|
+
return {
|
|
4378
|
+
type: "variable-node",
|
|
4379
|
+
version: 1,
|
|
4380
|
+
variableKey: this.__variableKey
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
// -- DOM --
|
|
4384
|
+
createDOM() {
|
|
4385
|
+
const span = document.createElement("span");
|
|
4386
|
+
span.className = "lex4-variable";
|
|
4387
|
+
span.setAttribute("data-variable-key", this.__variableKey);
|
|
4388
|
+
span.setAttribute("data-testid", `variable-${this.__variableKey}`);
|
|
4389
|
+
span.contentEditable = "false";
|
|
4390
|
+
return span;
|
|
4391
|
+
}
|
|
4392
|
+
updateDOM() {
|
|
4393
|
+
return false;
|
|
4394
|
+
}
|
|
4395
|
+
exportDOM() {
|
|
4396
|
+
const span = document.createElement("span");
|
|
4397
|
+
span.setAttribute("data-variable-key", this.__variableKey);
|
|
4398
|
+
span.textContent = `{{${this.__variableKey}}}`;
|
|
4399
|
+
return { element: span };
|
|
4400
|
+
}
|
|
4401
|
+
static importDOM() {
|
|
4402
|
+
return null;
|
|
4403
|
+
}
|
|
4404
|
+
// -- Behavior --
|
|
4405
|
+
isInline() {
|
|
4406
|
+
return true;
|
|
4407
|
+
}
|
|
4408
|
+
isKeyboardSelectable() {
|
|
4409
|
+
return true;
|
|
4410
|
+
}
|
|
4411
|
+
getTextContent() {
|
|
4412
|
+
return `{{${this.__variableKey}}}`;
|
|
4413
|
+
}
|
|
4414
|
+
// -- Rendering --
|
|
4415
|
+
decorate() {
|
|
4416
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4417
|
+
"span",
|
|
4418
|
+
{
|
|
4419
|
+
className: "lex4-variable-chip inline-flex items-center rounded bg-white px-1.5 py-0.5\n text-xs font-medium text-blue-700 border border-blue-300 select-none\n cursor-default whitespace-nowrap mx-0.5",
|
|
4420
|
+
"data-testid": `variable-chip-${this.__variableKey}`,
|
|
4421
|
+
title: this.__variableKey,
|
|
4422
|
+
children: `{{${this.__variableKey}}}`
|
|
4423
|
+
}
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
function $createVariableNode(variableKey) {
|
|
4428
|
+
return lexical.$applyNodeReplacement(new VariableNode(variableKey));
|
|
4429
|
+
}
|
|
4430
|
+
function $isVariableNode(node) {
|
|
4431
|
+
return node instanceof VariableNode;
|
|
4432
|
+
}
|
|
4433
|
+
const INSERT_VARIABLE_COMMAND = lexical.createCommand("INSERT_VARIABLE");
|
|
4434
|
+
const VariablePlugin = () => {
|
|
4435
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
4436
|
+
React.useEffect(() => {
|
|
4437
|
+
return editor.registerCommand(
|
|
4438
|
+
INSERT_VARIABLE_COMMAND,
|
|
4439
|
+
(variableKey) => {
|
|
4440
|
+
editor.update(() => {
|
|
4441
|
+
const selection = lexical.$getSelection();
|
|
4442
|
+
if (!lexical.$isRangeSelection(selection)) return;
|
|
4443
|
+
const variableNode = $createVariableNode(variableKey);
|
|
4444
|
+
lexical.$insertNodes([variableNode]);
|
|
4445
|
+
});
|
|
4446
|
+
return true;
|
|
4447
|
+
},
|
|
4448
|
+
lexical.COMMAND_PRIORITY_EDITOR
|
|
4449
|
+
);
|
|
4450
|
+
}, [editor]);
|
|
4451
|
+
return null;
|
|
4452
|
+
};
|
|
4453
|
+
const EMPTY_CONTEXT = {
|
|
4454
|
+
definitions: [],
|
|
4455
|
+
refreshDefinitions: () => {
|
|
4456
|
+
},
|
|
4457
|
+
getDefinition: () => void 0
|
|
4458
|
+
};
|
|
4459
|
+
const VariableContext = React.createContext(EMPTY_CONTEXT);
|
|
4460
|
+
const VariableProvider = ({
|
|
4461
|
+
initialDefinitions = [],
|
|
4462
|
+
children
|
|
4463
|
+
}) => {
|
|
4464
|
+
const [definitions, setDefinitions] = React.useState(initialDefinitions);
|
|
4465
|
+
const refresh = React.useCallback((newDefinitions) => {
|
|
4466
|
+
setDefinitions(newDefinitions);
|
|
4467
|
+
}, []);
|
|
4468
|
+
const getDefinition = React.useCallback(
|
|
4469
|
+
(key) => {
|
|
4470
|
+
return definitions.find((d) => d.key === key);
|
|
4471
|
+
},
|
|
4472
|
+
[definitions]
|
|
4473
|
+
);
|
|
4474
|
+
const value = React.useMemo(
|
|
4475
|
+
() => ({ definitions, refreshDefinitions: refresh, getDefinition }),
|
|
4476
|
+
[definitions, refresh, getDefinition]
|
|
4477
|
+
);
|
|
4478
|
+
return /* @__PURE__ */ jsxRuntime.jsx(VariableContext.Provider, { value, children });
|
|
4479
|
+
};
|
|
4480
|
+
function useVariables() {
|
|
4481
|
+
return React.useContext(VariableContext);
|
|
4482
|
+
}
|
|
4483
|
+
const VariablePicker = ({ onInsert, disabled = false }) => {
|
|
4484
|
+
const { definitions } = useVariables();
|
|
4485
|
+
const [open, setOpen] = React.useState(false);
|
|
4486
|
+
const [filter, setFilter] = React.useState("");
|
|
4487
|
+
const containerRef = React.useRef(null);
|
|
4488
|
+
const filtered = React.useMemo(() => {
|
|
4489
|
+
if (!filter) return definitions;
|
|
4490
|
+
const lower = filter.toLowerCase();
|
|
4491
|
+
return definitions.filter(
|
|
4492
|
+
(d) => d.key.toLowerCase().includes(lower) || d.label.toLowerCase().includes(lower)
|
|
4493
|
+
);
|
|
4494
|
+
}, [definitions, filter]);
|
|
4495
|
+
const grouped = React.useMemo(() => {
|
|
4496
|
+
const groups = {};
|
|
4497
|
+
for (const def of filtered) {
|
|
4498
|
+
const g = def.group ?? "Other";
|
|
4499
|
+
if (!groups[g]) groups[g] = [];
|
|
4500
|
+
groups[g].push(def);
|
|
4501
|
+
}
|
|
4502
|
+
return groups;
|
|
4503
|
+
}, [filtered]);
|
|
4504
|
+
const handleInsert = React.useCallback(
|
|
4505
|
+
(key) => {
|
|
4506
|
+
onInsert(key);
|
|
4507
|
+
setOpen(false);
|
|
4508
|
+
setFilter("");
|
|
4509
|
+
},
|
|
4510
|
+
[onInsert]
|
|
4511
|
+
);
|
|
4512
|
+
React.useEffect(() => {
|
|
4513
|
+
if (!open) return;
|
|
4514
|
+
const handler = (e) => {
|
|
4515
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
4516
|
+
setOpen(false);
|
|
4517
|
+
setFilter("");
|
|
4518
|
+
}
|
|
4519
|
+
};
|
|
4520
|
+
document.addEventListener("mousedown", handler);
|
|
4521
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
4522
|
+
}, [open]);
|
|
4523
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative inline-block", children: [
|
|
4524
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4525
|
+
"button",
|
|
4526
|
+
{
|
|
4527
|
+
type: "button",
|
|
4528
|
+
className: "h-7 rounded border border-gray-200 bg-white px-2 text-xs font-medium text-gray-700\n hover:bg-gray-50 focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400\n disabled:opacity-50 disabled:cursor-not-allowed",
|
|
4529
|
+
"data-testid": "variable-picker-button",
|
|
4530
|
+
disabled: disabled || definitions.length === 0,
|
|
4531
|
+
onClick: () => setOpen(!open),
|
|
4532
|
+
title: "Insert variable",
|
|
4533
|
+
children: "{x}"
|
|
4534
|
+
}
|
|
4535
|
+
),
|
|
4536
|
+
open && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4537
|
+
"div",
|
|
4538
|
+
{
|
|
4539
|
+
className: "absolute left-0 top-full z-50 mt-1 w-64 rounded-md border border-gray-200\n bg-white shadow-lg",
|
|
4540
|
+
"data-testid": "variable-picker-dropdown",
|
|
4541
|
+
children: [
|
|
4542
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-gray-100 p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4543
|
+
"input",
|
|
4544
|
+
{
|
|
4545
|
+
type: "text",
|
|
4546
|
+
className: "w-full rounded border border-gray-200 px-2 py-1 text-xs\n focus:border-blue-400 focus:outline-none",
|
|
4547
|
+
placeholder: "Search variables...",
|
|
4548
|
+
"data-testid": "variable-picker-search",
|
|
4549
|
+
value: filter,
|
|
4550
|
+
onChange: (e) => setFilter(e.target.value),
|
|
4551
|
+
autoFocus: true
|
|
4552
|
+
}
|
|
4553
|
+
) }),
|
|
4554
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-48 overflow-y-auto p-1", children: [
|
|
4555
|
+
Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-xs text-gray-400", children: "No variables found" }),
|
|
4556
|
+
Object.entries(grouped).map(([group, defs]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4557
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-[10px] font-semibold uppercase text-gray-400", children: group }),
|
|
4558
|
+
defs.map((def) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4559
|
+
"button",
|
|
4560
|
+
{
|
|
4561
|
+
type: "button",
|
|
4562
|
+
className: "flex w-full items-center gap-2 rounded px-2 py-1 text-left text-xs\n hover:bg-blue-50",
|
|
4563
|
+
"data-testid": `variable-option-${def.key}`,
|
|
4564
|
+
onClick: () => handleInsert(def.key),
|
|
4565
|
+
children: [
|
|
4566
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-blue-700", children: `{{${def.key}}}` }),
|
|
4567
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: def.label })
|
|
4568
|
+
]
|
|
4569
|
+
},
|
|
4570
|
+
def.key
|
|
4571
|
+
))
|
|
4572
|
+
] }, group))
|
|
4573
|
+
] })
|
|
4574
|
+
]
|
|
4575
|
+
}
|
|
4576
|
+
)
|
|
4577
|
+
] });
|
|
4578
|
+
};
|
|
4579
|
+
const VariablePanel = ({ open, onClose }) => {
|
|
4580
|
+
const { definitions } = useVariables();
|
|
4581
|
+
const { activeEditor, runHistoryAction } = useDocument();
|
|
4582
|
+
const t = useTranslations();
|
|
4583
|
+
const [filter, setFilter] = React.useState("");
|
|
4584
|
+
const filtered = React.useMemo(() => {
|
|
4585
|
+
if (!filter) return definitions;
|
|
4586
|
+
const lower = filter.toLowerCase();
|
|
4587
|
+
return definitions.filter(
|
|
4588
|
+
(d) => d.key.toLowerCase().includes(lower) || d.label.toLowerCase().includes(lower)
|
|
4589
|
+
);
|
|
4590
|
+
}, [definitions, filter]);
|
|
4591
|
+
const grouped = React.useMemo(() => {
|
|
4592
|
+
const groups = {};
|
|
4593
|
+
for (const def of filtered) {
|
|
4594
|
+
const g = def.group ?? "Other";
|
|
4595
|
+
if (!groups[g]) groups[g] = [];
|
|
4596
|
+
groups[g].push(def);
|
|
4597
|
+
}
|
|
4598
|
+
return groups;
|
|
4599
|
+
}, [filtered]);
|
|
4600
|
+
const handleInsert = React.useCallback(
|
|
4601
|
+
(key) => {
|
|
4602
|
+
if (!activeEditor) return;
|
|
4603
|
+
runHistoryAction(
|
|
4604
|
+
{ label: interpolate(t.variables.insertVariable, { key }), source: "toolbar", region: "document" },
|
|
4605
|
+
() => {
|
|
4606
|
+
activeEditor.dispatchCommand(INSERT_VARIABLE_COMMAND, key);
|
|
4607
|
+
}
|
|
4608
|
+
);
|
|
4609
|
+
},
|
|
4610
|
+
[activeEditor, runHistoryAction]
|
|
4611
|
+
);
|
|
4612
|
+
if (definitions.length === 0) return null;
|
|
4613
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4614
|
+
EditorSidebar,
|
|
4615
|
+
{
|
|
4616
|
+
title: t.variables.title,
|
|
4617
|
+
subtitle: interpolate(t.variables.available, { count: String(definitions.length) }),
|
|
4618
|
+
open,
|
|
4619
|
+
onClose,
|
|
4620
|
+
testId: "variable-panel",
|
|
4621
|
+
headerActions: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4622
|
+
"button",
|
|
4623
|
+
{
|
|
4624
|
+
type: "button",
|
|
4625
|
+
className: "flex h-6 w-6 items-center justify-center rounded text-gray-400\n transition-colors hover:bg-gray-100 hover:text-gray-600",
|
|
4626
|
+
title: t.variables.refreshVariables,
|
|
4627
|
+
"data-testid": "btn-refresh-variables",
|
|
4628
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(RefreshCw, { size: 12 })
|
|
4629
|
+
}
|
|
4630
|
+
),
|
|
4631
|
+
children: [
|
|
4632
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
4633
|
+
/* @__PURE__ */ jsxRuntime.jsx(Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" }),
|
|
4634
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4635
|
+
"input",
|
|
4636
|
+
{
|
|
4637
|
+
type: "text",
|
|
4638
|
+
className: "w-full rounded-lg border border-gray-200 bg-gray-50 py-1.5 pl-8 pr-3 text-xs\n placeholder-gray-400 focus:border-blue-400 focus:bg-white focus:outline-none\n focus:ring-1 focus:ring-blue-400",
|
|
4639
|
+
placeholder: t.variables.searchPlaceholder,
|
|
4640
|
+
"data-testid": "variable-panel-search",
|
|
4641
|
+
value: filter,
|
|
4642
|
+
onChange: (e) => setFilter(e.target.value)
|
|
4643
|
+
}
|
|
4644
|
+
)
|
|
4645
|
+
] }) }),
|
|
4646
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pb-3", children: [
|
|
4647
|
+
Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4 text-center text-xs text-gray-400", children: t.variables.noVariablesFound }),
|
|
4648
|
+
Object.entries(grouped).map(([group, defs]) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: defs.map((def) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4649
|
+
"button",
|
|
4650
|
+
{
|
|
4651
|
+
type: "button",
|
|
4652
|
+
className: "flex items-center justify-between gap-2 rounded-lg px-2.5 py-1.5 text-left text-xs\n transition-colors hover:bg-blue-50 group",
|
|
4653
|
+
"data-testid": `variable-panel-${def.key}`,
|
|
4654
|
+
onClick: () => handleInsert(def.key),
|
|
4655
|
+
disabled: !activeEditor,
|
|
4656
|
+
children: [
|
|
4657
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4658
|
+
"span",
|
|
4659
|
+
{
|
|
4660
|
+
className: "inline-flex items-center rounded-full border border-blue-300 bg-white\n px-2 py-0.5 text-[11px] font-medium text-blue-700\n group-hover:border-blue-400 group-hover:bg-blue-50",
|
|
4661
|
+
children: def.label
|
|
4662
|
+
}
|
|
4663
|
+
),
|
|
4664
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-gray-400", children: group })
|
|
4665
|
+
]
|
|
4666
|
+
},
|
|
4667
|
+
def.key
|
|
4668
|
+
)) }) }, group))
|
|
4669
|
+
] })
|
|
4670
|
+
]
|
|
4671
|
+
}
|
|
4672
|
+
);
|
|
4673
|
+
};
|
|
4674
|
+
const VariablePanelContext = React.createContext({
|
|
4675
|
+
panelOpen: false,
|
|
4676
|
+
setPanelOpen: () => {
|
|
4677
|
+
}
|
|
4678
|
+
});
|
|
4679
|
+
function useVariablePanelState() {
|
|
4680
|
+
return React.useContext(VariablePanelContext);
|
|
4681
|
+
}
|
|
4682
|
+
const VariablePanelStateProvider = ({ children }) => {
|
|
4683
|
+
const [panelOpen, setPanelOpen] = React.useState(false);
|
|
4684
|
+
return /* @__PURE__ */ jsxRuntime.jsx(VariablePanelContext.Provider, { value: { panelOpen, setPanelOpen }, children });
|
|
4685
|
+
};
|
|
4686
|
+
const VariableToolbarItem = () => {
|
|
4687
|
+
const { activeEditor, runHistoryAction } = useDocument();
|
|
4688
|
+
const t = useTranslations();
|
|
4689
|
+
const handleInsert = React.useCallback((variableKey) => {
|
|
4690
|
+
runHistoryAction(
|
|
4691
|
+
{ label: interpolate(t.variables.insertVariable, { key: variableKey }), source: "toolbar", region: "document" },
|
|
4692
|
+
() => {
|
|
4693
|
+
if (activeEditor) {
|
|
4694
|
+
activeEditor.dispatchCommand(INSERT_VARIABLE_COMMAND, variableKey);
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
);
|
|
4698
|
+
}, [activeEditor, runHistoryAction]);
|
|
4699
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4700
|
+
VariablePicker,
|
|
4701
|
+
{
|
|
4702
|
+
onInsert: handleInsert,
|
|
4703
|
+
disabled: !activeEditor
|
|
4704
|
+
}
|
|
4705
|
+
);
|
|
4706
|
+
};
|
|
4707
|
+
const VariablePanelToggle = () => {
|
|
4708
|
+
const { panelOpen, setPanelOpen } = useVariablePanelState();
|
|
4709
|
+
const t = useTranslations();
|
|
4710
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4711
|
+
"button",
|
|
4712
|
+
{
|
|
4713
|
+
type: "button",
|
|
4714
|
+
title: panelOpen ? t.variables.closePanel : t.variables.openPanel,
|
|
4715
|
+
"aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
|
|
4716
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
4717
|
+
onClick: () => setPanelOpen(!panelOpen),
|
|
4718
|
+
className: `
|
|
4719
|
+
flex h-7 w-7 items-center justify-center rounded transition-colors
|
|
4720
|
+
${panelOpen ? "bg-blue-50 text-blue-600" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}
|
|
4721
|
+
`,
|
|
4722
|
+
"data-testid": "toggle-variable-panel",
|
|
4723
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Braces, { size: 15 })
|
|
4724
|
+
}
|
|
4725
|
+
);
|
|
4726
|
+
};
|
|
4727
|
+
const VariablePanelWithState = () => {
|
|
4728
|
+
const { panelOpen, setPanelOpen } = useVariablePanelState();
|
|
4729
|
+
return /* @__PURE__ */ jsxRuntime.jsx(VariablePanel, { open: panelOpen, onClose: () => setPanelOpen(false) });
|
|
4730
|
+
};
|
|
4731
|
+
const VariableDefinitionsSync = ({ children }) => {
|
|
4732
|
+
const { definitions } = useVariables();
|
|
4733
|
+
const extState = useExtensionState();
|
|
4734
|
+
React.useEffect(() => {
|
|
4735
|
+
extState.set("variableDefinitions", definitions);
|
|
4736
|
+
}, [definitions, extState]);
|
|
4737
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
4738
|
+
};
|
|
4739
|
+
function variablesExtension(definitions = []) {
|
|
4740
|
+
const ProviderWrapper = ({ children }) => {
|
|
4741
|
+
return /* @__PURE__ */ jsxRuntime.jsx(VariablePanelStateProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(VariableProvider, { initialDefinitions: definitions, children: /* @__PURE__ */ jsxRuntime.jsx(VariableDefinitionsSync, { children }) }) });
|
|
4742
|
+
};
|
|
4743
|
+
return {
|
|
4744
|
+
name: "variables",
|
|
4745
|
+
nodes: [VariableNode],
|
|
4746
|
+
bodyPlugins: [VariablePlugin],
|
|
4747
|
+
toolbarItems: [VariableToolbarItem, VariablePanelToggle],
|
|
4748
|
+
sidePanel: VariablePanelWithState,
|
|
4749
|
+
provider: ProviderWrapper,
|
|
4750
|
+
handleMethods: (ctx) => ({
|
|
4751
|
+
insertVariable: (key) => {
|
|
4752
|
+
const editor = ctx.getActiveEditor();
|
|
4753
|
+
if (editor) {
|
|
4754
|
+
editor.dispatchCommand(INSERT_VARIABLE_COMMAND, key);
|
|
4755
|
+
}
|
|
4756
|
+
},
|
|
4757
|
+
refreshVariables: (newDefs) => {
|
|
4758
|
+
ctx.setExtensionState("variableDefinitions", newDefs);
|
|
4759
|
+
}
|
|
4760
|
+
})
|
|
4761
|
+
};
|
|
4762
|
+
}
|
|
4763
|
+
exports.$createVariableNode = $createVariableNode;
|
|
4764
|
+
exports.$isVariableNode = $isVariableNode;
|
|
3762
4765
|
exports.A4_HEIGHT_MM = A4_HEIGHT_MM;
|
|
3763
4766
|
exports.A4_HEIGHT_PX = A4_HEIGHT_PX;
|
|
3764
4767
|
exports.A4_WIDTH_MM = A4_WIDTH_MM;
|
|
3765
4768
|
exports.A4_WIDTH_PX = A4_WIDTH_PX;
|
|
4769
|
+
exports.AST_VERSION = AST_VERSION;
|
|
4770
|
+
exports.DEFAULT_TRANSLATIONS = DEFAULT_TRANSLATIONS;
|
|
4771
|
+
exports.INSERT_VARIABLE_COMMAND = INSERT_VARIABLE_COMMAND;
|
|
3766
4772
|
exports.Lex4Editor = Lex4Editor;
|
|
3767
4773
|
exports.MAX_FOOTER_HEIGHT_PX = MAX_FOOTER_HEIGHT_PX;
|
|
3768
4774
|
exports.MAX_HEADER_HEIGHT_PX = MAX_HEADER_HEIGHT_PX;
|
|
4775
|
+
exports.VariableNode = VariableNode;
|
|
4776
|
+
exports.astExtension = astExtension;
|
|
4777
|
+
exports.buildSavePayload = buildSavePayload;
|
|
3769
4778
|
exports.createEmptyDocument = createEmptyDocument;
|
|
3770
4779
|
exports.createEmptyPage = createEmptyPage;
|
|
4780
|
+
exports.serializeDocument = serializeDocument;
|
|
4781
|
+
exports.serializeDocumentJson = serializeDocumentJson;
|
|
3771
4782
|
exports.useHeaderFooter = useHeaderFooter;
|
|
3772
4783
|
exports.useOverflowDetection = useOverflowDetection;
|
|
3773
4784
|
exports.usePagination = usePagination;
|
|
4785
|
+
exports.variablesExtension = variablesExtension;
|
|
3774
4786
|
//# sourceMappingURL=lex4-editor.cjs.map
|