@june-ai/june-ui 0.2.4 → 0.2.5

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/dist/index.js CHANGED
@@ -6,13 +6,18 @@ import {
6
6
  docToHtml,
7
7
  docToMarkdown,
8
8
  emptyDoc,
9
+ getSearchState,
10
+ gotoMatch,
9
11
  isJuneError,
10
12
  parseDocumentContent,
13
+ replaceAll,
14
+ replaceCurrent,
11
15
  sanitizeHref,
16
+ setSearch,
12
17
  slugify,
13
18
  toJuneError,
14
19
  wordCountOf
15
- } from "./chunk-HFHRSNIL.js";
20
+ } from "./chunk-CZE533PM.js";
16
21
 
17
22
  // src/renderer/renderer.tsx
18
23
  import { Renderer } from "@openuidev/react-lang";
@@ -1090,18 +1095,1164 @@ function DocumentDraft({ title, words, state }) {
1090
1095
 
1091
1096
  // src/components/document-editor/document-editor.tsx
1092
1097
  import { useEditor } from "@tiptap/react";
1093
- import { useCallback as useCallback3, useEffect as useEffect2, useId, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
1098
+ import { useCallback as useCallback3, useEffect as useEffect5, useId, useMemo as useMemo4, useRef as useRef7, useState as useState6 } from "react";
1099
+
1100
+ // src/components/document-editor/document-bubble.tsx
1101
+ import { useEditorState as useEditorState2 } from "@tiptap/react";
1102
+ import { BubbleMenu } from "@tiptap/react/menus";
1103
+
1104
+ // src/components/document-editor/document-toolbar.tsx
1105
+ import { useEditorState } from "@tiptap/react";
1106
+ import { useRef as useRef4, useState as useState3 } from "react";
1107
+
1108
+ // src/components/document-editor/document-icons.tsx
1109
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1110
+ var PATHS = {
1111
+ undo: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1112
+ /* @__PURE__ */ jsx5("path", { d: "M9 14 4 9l5-5" }),
1113
+ /* @__PURE__ */ jsx5("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 0 11H11" })
1114
+ ] }),
1115
+ redo: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1116
+ /* @__PURE__ */ jsx5("path", { d: "m15 14 5-5-5-5" }),
1117
+ /* @__PURE__ */ jsx5("path", { d: "M20 9H9.5a5.5 5.5 0 0 0 0 11H13" })
1118
+ ] }),
1119
+ bold: /* @__PURE__ */ jsx5("path", { 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" }),
1120
+ italic: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1121
+ /* @__PURE__ */ jsx5("line", { x1: "19", x2: "10", y1: "4", y2: "4" }),
1122
+ /* @__PURE__ */ jsx5("line", { x1: "14", x2: "5", y1: "20", y2: "20" }),
1123
+ /* @__PURE__ */ jsx5("line", { x1: "15", x2: "9", y1: "4", y2: "20" })
1124
+ ] }),
1125
+ underline: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1126
+ /* @__PURE__ */ jsx5("path", { d: "M6 4v6a6 6 0 0 0 12 0V4" }),
1127
+ /* @__PURE__ */ jsx5("line", { x1: "4", x2: "20", y1: "20", y2: "20" })
1128
+ ] }),
1129
+ strike: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1130
+ /* @__PURE__ */ jsx5("path", { d: "M16 4H9a3 3 0 0 0-2.83 4" }),
1131
+ /* @__PURE__ */ jsx5("path", { d: "M14 12a4 4 0 0 1 0 8H6" }),
1132
+ /* @__PURE__ */ jsx5("line", { x1: "4", x2: "20", y1: "12", y2: "12" })
1133
+ ] }),
1134
+ code: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1135
+ /* @__PURE__ */ jsx5("path", { d: "m16 18 6-6-6-6" }),
1136
+ /* @__PURE__ */ jsx5("path", { d: "m8 6-6 6 6 6" })
1137
+ ] }),
1138
+ codeBlock: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1139
+ /* @__PURE__ */ jsx5("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1140
+ /* @__PURE__ */ jsx5("path", { d: "m10 9-3 3 3 3" }),
1141
+ /* @__PURE__ */ jsx5("path", { d: "m14 15 3-3-3-3" })
1142
+ ] }),
1143
+ superscript: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1144
+ /* @__PURE__ */ jsx5("path", { d: "m4 19 8-8" }),
1145
+ /* @__PURE__ */ jsx5("path", { d: "m12 19-8-8" }),
1146
+ /* @__PURE__ */ jsx5("path", { d: "M20 12h-4c0-1.5.44-2 1.5-2.5S20 8.33 20 7c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07" })
1147
+ ] }),
1148
+ subscript: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1149
+ /* @__PURE__ */ jsx5("path", { d: "m4 5 8 8" }),
1150
+ /* @__PURE__ */ jsx5("path", { d: "m12 5-8 8" }),
1151
+ /* @__PURE__ */ jsx5("path", { d: "M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07" })
1152
+ ] }),
1153
+ alignLeft: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1154
+ /* @__PURE__ */ jsx5("path", { d: "M15 12H3" }),
1155
+ /* @__PURE__ */ jsx5("path", { d: "M17 18H3" }),
1156
+ /* @__PURE__ */ jsx5("path", { d: "M21 6H3" })
1157
+ ] }),
1158
+ alignCenter: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1159
+ /* @__PURE__ */ jsx5("path", { d: "M17 12H7" }),
1160
+ /* @__PURE__ */ jsx5("path", { d: "M19 18H5" }),
1161
+ /* @__PURE__ */ jsx5("path", { d: "M21 6H3" })
1162
+ ] }),
1163
+ alignRight: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1164
+ /* @__PURE__ */ jsx5("path", { d: "M21 12H9" }),
1165
+ /* @__PURE__ */ jsx5("path", { d: "M21 18H7" }),
1166
+ /* @__PURE__ */ jsx5("path", { d: "M21 6H3" })
1167
+ ] }),
1168
+ alignJustify: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1169
+ /* @__PURE__ */ jsx5("path", { d: "M3 12h18" }),
1170
+ /* @__PURE__ */ jsx5("path", { d: "M3 18h18" }),
1171
+ /* @__PURE__ */ jsx5("path", { d: "M3 6h18" })
1172
+ ] }),
1173
+ list: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1174
+ /* @__PURE__ */ jsx5("path", { d: "M3 12h.01" }),
1175
+ /* @__PURE__ */ jsx5("path", { d: "M3 18h.01" }),
1176
+ /* @__PURE__ */ jsx5("path", { d: "M3 6h.01" }),
1177
+ /* @__PURE__ */ jsx5("path", { d: "M8 12h13" }),
1178
+ /* @__PURE__ */ jsx5("path", { d: "M8 18h13" }),
1179
+ /* @__PURE__ */ jsx5("path", { d: "M8 6h13" })
1180
+ ] }),
1181
+ listOrdered: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1182
+ /* @__PURE__ */ jsx5("path", { d: "M10 12h11" }),
1183
+ /* @__PURE__ */ jsx5("path", { d: "M10 18h11" }),
1184
+ /* @__PURE__ */ jsx5("path", { d: "M10 6h11" }),
1185
+ /* @__PURE__ */ jsx5("path", { d: "M4 10h2" }),
1186
+ /* @__PURE__ */ jsx5("path", { d: "M4 6h1v4" }),
1187
+ /* @__PURE__ */ jsx5("path", { d: "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" })
1188
+ ] }),
1189
+ outdent: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1190
+ /* @__PURE__ */ jsx5("path", { d: "M21 12H11" }),
1191
+ /* @__PURE__ */ jsx5("path", { d: "M21 18H11" }),
1192
+ /* @__PURE__ */ jsx5("path", { d: "M21 6H11" }),
1193
+ /* @__PURE__ */ jsx5("path", { d: "m7 8-4 4 4 4" })
1194
+ ] }),
1195
+ indent: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1196
+ /* @__PURE__ */ jsx5("path", { d: "M21 12H11" }),
1197
+ /* @__PURE__ */ jsx5("path", { d: "M21 18H11" }),
1198
+ /* @__PURE__ */ jsx5("path", { d: "M21 6H11" }),
1199
+ /* @__PURE__ */ jsx5("path", { d: "m3 8 4 4-4 4" })
1200
+ ] }),
1201
+ quote: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1202
+ /* @__PURE__ */ jsx5("path", { d: "M17 6H3" }),
1203
+ /* @__PURE__ */ jsx5("path", { d: "M21 12H8" }),
1204
+ /* @__PURE__ */ jsx5("path", { d: "M21 18H8" }),
1205
+ /* @__PURE__ */ jsx5("path", { d: "M3 12v6" })
1206
+ ] }),
1207
+ pilcrow: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1208
+ /* @__PURE__ */ jsx5("path", { d: "M13 4v16" }),
1209
+ /* @__PURE__ */ jsx5("path", { d: "M17 4v16" }),
1210
+ /* @__PURE__ */ jsx5("path", { d: "M19 4H9.5a4.5 4.5 0 0 0 0 9H13" })
1211
+ ] }),
1212
+ pageBreak: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1213
+ /* @__PURE__ */ jsx5("line", { x1: "3", x2: "21", y1: "12", y2: "12" }),
1214
+ /* @__PURE__ */ jsx5("polyline", { points: "8 8 12 4 16 8" }),
1215
+ /* @__PURE__ */ jsx5("polyline", { points: "16 16 12 20 8 16" })
1216
+ ] }),
1217
+ divider: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1218
+ /* @__PURE__ */ jsx5("path", { d: "M3 12h18" }),
1219
+ /* @__PURE__ */ jsx5("path", { d: "M8 6h8", opacity: ".45" }),
1220
+ /* @__PURE__ */ jsx5("path", { d: "M8 18h8", opacity: ".45" })
1221
+ ] }),
1222
+ layout: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1223
+ /* @__PURE__ */ jsx5("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
1224
+ /* @__PURE__ */ jsx5("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
1225
+ /* @__PURE__ */ jsx5("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
1226
+ ] }),
1227
+ search: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1228
+ /* @__PURE__ */ jsx5("circle", { cx: "11", cy: "11", r: "8" }),
1229
+ /* @__PURE__ */ jsx5("path", { d: "m21 21-4.3-4.3" })
1230
+ ] }),
1231
+ palette: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1232
+ /* @__PURE__ */ jsx5("circle", { cx: "13.5", cy: "6.5", r: ".5", fill: "currentColor" }),
1233
+ /* @__PURE__ */ jsx5("circle", { cx: "17.5", cy: "10.5", r: ".5", fill: "currentColor" }),
1234
+ /* @__PURE__ */ jsx5("circle", { cx: "8.5", cy: "7.5", r: ".5", fill: "currentColor" }),
1235
+ /* @__PURE__ */ jsx5("circle", { cx: "6.5", cy: "12.5", r: ".5", fill: "currentColor" }),
1236
+ /* @__PURE__ */ jsx5("path", { d: "M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.93 0 1.65-.75 1.65-1.69 0-.44-.18-.84-.44-1.13-.29-.29-.44-.65-.44-1.13a1.64 1.64 0 0 1 1.67-1.67h2c3.05 0 5.55-2.5 5.55-5.55C21.97 6.01 17.46 2 12 2z" })
1237
+ ] }),
1238
+ highlighter: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1239
+ /* @__PURE__ */ jsx5("path", { d: "m9 11-6 6v3h9l3-3" }),
1240
+ /* @__PURE__ */ jsx5("path", { d: "m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" })
1241
+ ] }),
1242
+ link: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1243
+ /* @__PURE__ */ jsx5("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
1244
+ /* @__PURE__ */ jsx5("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
1245
+ ] }),
1246
+ image: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1247
+ /* @__PURE__ */ jsx5("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
1248
+ /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "2" }),
1249
+ /* @__PURE__ */ jsx5("path", { d: "m21 15-3.09-3.09a2 2 0 0 0-2.82 0L6 21" })
1250
+ ] }),
1251
+ table: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1252
+ /* @__PURE__ */ jsx5("path", { d: "M12 3v18" }),
1253
+ /* @__PURE__ */ jsx5("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
1254
+ /* @__PURE__ */ jsx5("path", { d: "M3 9h18" }),
1255
+ /* @__PURE__ */ jsx5("path", { d: "M3 15h18" })
1256
+ ] }),
1257
+ eraser: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1258
+ /* @__PURE__ */ jsx5("path", { d: "m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21" }),
1259
+ /* @__PURE__ */ jsx5("path", { d: "M22 21H7" }),
1260
+ /* @__PURE__ */ jsx5("path", { d: "m5 11 9 9" })
1261
+ ] }),
1262
+ save: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1263
+ /* @__PURE__ */ jsx5("path", { d: "M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" }),
1264
+ /* @__PURE__ */ jsx5("path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7" }),
1265
+ /* @__PURE__ */ jsx5("path", { d: "M7 3v4a1 1 0 0 0 1 1h7" })
1266
+ ] }),
1267
+ download: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1268
+ /* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
1269
+ /* @__PURE__ */ jsx5("polyline", { points: "7 10 12 15 17 10" }),
1270
+ /* @__PURE__ */ jsx5("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
1271
+ ] }),
1272
+ chevronDown: /* @__PURE__ */ jsx5("path", { d: "m6 9 6 6 6-6" }),
1273
+ chevronUp: /* @__PURE__ */ jsx5("path", { d: "m18 15-6-6-6 6" }),
1274
+ close: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1275
+ /* @__PURE__ */ jsx5("path", { d: "M18 6 6 18" }),
1276
+ /* @__PURE__ */ jsx5("path", { d: "m6 6 12 12" })
1277
+ ] }),
1278
+ maximize: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1279
+ /* @__PURE__ */ jsx5("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
1280
+ /* @__PURE__ */ jsx5("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
1281
+ /* @__PURE__ */ jsx5("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
1282
+ /* @__PURE__ */ jsx5("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
1283
+ ] }),
1284
+ copy: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1285
+ /* @__PURE__ */ jsx5("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2" }),
1286
+ /* @__PURE__ */ jsx5("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
1287
+ ] }),
1288
+ file: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1289
+ /* @__PURE__ */ jsx5("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
1290
+ /* @__PURE__ */ jsx5("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
1291
+ ] }),
1292
+ plus: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1293
+ /* @__PURE__ */ jsx5("path", { d: "M5 12h14" }),
1294
+ /* @__PURE__ */ jsx5("path", { d: "M12 5v14" })
1295
+ ] }),
1296
+ minus: /* @__PURE__ */ jsx5("path", { d: "M5 12h14" }),
1297
+ check: /* @__PURE__ */ jsx5("path", { d: "M20 6 9 17l-5-5" }),
1298
+ sparkles: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1299
+ /* @__PURE__ */ jsx5("path", { d: "M9.94 14.06 8 20l-1.94-5.94L0 12l6.06-1.94L8 4l1.94 6.06L16 12z", transform: "translate(3 -1) scale(.85)" }),
1300
+ /* @__PURE__ */ jsx5("path", { d: "M20 3v4" }),
1301
+ /* @__PURE__ */ jsx5("path", { d: "M22 5h-4" })
1302
+ ] }),
1303
+ upload: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1304
+ /* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
1305
+ /* @__PURE__ */ jsx5("polyline", { points: "17 8 12 3 7 8" }),
1306
+ /* @__PURE__ */ jsx5("line", { x1: "12", x2: "12", y1: "3", y2: "15" })
1307
+ ] })
1308
+ };
1309
+ function Icon({ name, size = 16 }) {
1310
+ return /* @__PURE__ */ jsx5(
1311
+ "svg",
1312
+ {
1313
+ className: "june-doc__icon",
1314
+ width: size,
1315
+ height: size,
1316
+ viewBox: "0 0 24 24",
1317
+ fill: "none",
1318
+ stroke: "currentColor",
1319
+ strokeWidth: 2,
1320
+ strokeLinecap: "round",
1321
+ strokeLinejoin: "round",
1322
+ "aria-hidden": "true",
1323
+ focusable: "false",
1324
+ children: PATHS[name]
1325
+ }
1326
+ );
1327
+ }
1328
+
1329
+ // src/components/document-editor/document-popover.tsx
1330
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
1331
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1332
+ function Dropdown({ trigger, children, align = "start", className, onOpen }) {
1333
+ const [open, setOpen] = useState2(false);
1334
+ const anchor = useRef3(null);
1335
+ useEffect2(() => {
1336
+ if (!open) return;
1337
+ const onDown = (e) => {
1338
+ if (anchor.current && !anchor.current.contains(e.target)) setOpen(false);
1339
+ };
1340
+ document.addEventListener("mousedown", onDown);
1341
+ return () => document.removeEventListener("mousedown", onDown);
1342
+ }, [open]);
1343
+ const toggle = () => setOpen((v) => {
1344
+ if (!v) onOpen?.();
1345
+ return !v;
1346
+ });
1347
+ const close = () => setOpen(false);
1348
+ return /* @__PURE__ */ jsxs4(
1349
+ "div",
1350
+ {
1351
+ ref: anchor,
1352
+ className: "june-doc__anchor",
1353
+ onKeyDown: (e) => {
1354
+ if (e.key === "Escape" && open) {
1355
+ e.stopPropagation();
1356
+ close();
1357
+ }
1358
+ },
1359
+ children: [
1360
+ trigger({ open, toggle }),
1361
+ open ? /* @__PURE__ */ jsx6("div", { className: `june-doc__pop june-doc__pop--${align}${className ? ` ${className}` : ""}`, children: children(close) }) : null
1362
+ ]
1363
+ }
1364
+ );
1365
+ }
1366
+
1367
+ // src/components/document-editor/document-toolbar.tsx
1368
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1369
+ var FONT_FAMILIES = [
1370
+ { label: "Default", value: "" },
1371
+ { label: "Arial", value: "Arial, Helvetica, sans-serif" },
1372
+ { label: "Calibri", value: "Calibri, Carlito, sans-serif" },
1373
+ { label: "Georgia", value: "Georgia, serif" },
1374
+ { label: "Garamond", value: "Garamond, 'EB Garamond', serif" },
1375
+ { label: "Times New Roman", value: "'Times New Roman', Times, serif" },
1376
+ { label: "Verdana", value: "Verdana, Geneva, sans-serif" },
1377
+ { label: "Trebuchet MS", value: "'Trebuchet MS', sans-serif" },
1378
+ { label: "Courier New", value: "'Courier New', Courier, monospace" }
1379
+ ];
1380
+ var FONT_SIZES = [8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72];
1381
+ var TEXT_COLORS = [
1382
+ "#111827",
1383
+ "#374151",
1384
+ "#6b7280",
1385
+ "#9ca3af",
1386
+ "#dc2626",
1387
+ "#ea580c",
1388
+ "#ca8a04",
1389
+ "#16a34a",
1390
+ "#0d9488",
1391
+ "#0284c7",
1392
+ "#2563eb",
1393
+ "#4f46e5",
1394
+ "#9333ea",
1395
+ "#db2777",
1396
+ "#7f1d1d",
1397
+ "#1e3a8a"
1398
+ ];
1399
+ var HIGHLIGHT_COLORS = [
1400
+ "#fef08a",
1401
+ "#fde68a",
1402
+ "#fed7aa",
1403
+ "#fecaca",
1404
+ "#fbcfe8",
1405
+ "#e9d5ff",
1406
+ "#c7d2fe",
1407
+ "#bfdbfe",
1408
+ "#a5f3fc",
1409
+ "#bbf7d0",
1410
+ "#d9f99d",
1411
+ "#e5e7eb"
1412
+ ];
1413
+ var MAX_IMAGE_BYTES = 8 * 1024 * 1024;
1414
+ function Btn({ label, icon, active, disabled, toggle, onClick, children, className, text }) {
1415
+ return /* @__PURE__ */ jsxs5(
1416
+ "button",
1417
+ {
1418
+ type: "button",
1419
+ className: `june-doc__btn${icon && !text && !children ? " june-doc__btn--icon" : ""}${className ? ` ${className}` : ""}`,
1420
+ "aria-label": label,
1421
+ title: label,
1422
+ "aria-pressed": toggle ? !!active : void 0,
1423
+ "data-active": active || void 0,
1424
+ disabled,
1425
+ onMouseDown: (e) => e.preventDefault(),
1426
+ onClick,
1427
+ children: [
1428
+ icon ? /* @__PURE__ */ jsx7(Icon, { name: icon }) : null,
1429
+ children ?? (text ? /* @__PURE__ */ jsx7("span", { children: label }) : null)
1430
+ ]
1431
+ }
1432
+ );
1433
+ }
1434
+ function Group({ label, children }) {
1435
+ return /* @__PURE__ */ jsxs5("div", { className: "june-doc__group", role: "group", "aria-label": label, children: [
1436
+ label ? /* @__PURE__ */ jsx7("span", { className: "june-doc__group-label", "aria-hidden": "true", children: label }) : null,
1437
+ children
1438
+ ] });
1439
+ }
1440
+ function Field({ label, children }) {
1441
+ return /* @__PURE__ */ jsxs5("label", { className: "june-doc__field", children: [
1442
+ /* @__PURE__ */ jsx7("span", { className: "june-doc__field-label", children: label }),
1443
+ children
1444
+ ] });
1445
+ }
1446
+ function ColorPicker({ label, icon, colors, current, disabled, resetLabel, onPick, onReset }) {
1447
+ return /* @__PURE__ */ jsx7(
1448
+ Dropdown,
1449
+ {
1450
+ trigger: ({ open, toggle }) => /* @__PURE__ */ jsxs5(
1451
+ "button",
1452
+ {
1453
+ type: "button",
1454
+ className: "june-doc__btn june-doc__btn--icon june-doc__btn--color",
1455
+ "aria-label": label,
1456
+ title: label,
1457
+ "aria-expanded": open,
1458
+ "aria-haspopup": "dialog",
1459
+ disabled,
1460
+ onMouseDown: (e) => e.preventDefault(),
1461
+ onClick: toggle,
1462
+ children: [
1463
+ /* @__PURE__ */ jsx7(Icon, { name: icon }),
1464
+ /* @__PURE__ */ jsx7("span", { className: "june-doc__color-bar", style: { background: current ?? "transparent" } })
1465
+ ]
1466
+ }
1467
+ ),
1468
+ children: (close) => /* @__PURE__ */ jsxs5("div", { className: "june-doc__swatches-wrap", role: "dialog", "aria-label": label, children: [
1469
+ /* @__PURE__ */ jsx7("div", { className: "june-doc__pop-title", children: label }),
1470
+ /* @__PURE__ */ jsx7("div", { className: "june-doc__swatches", children: colors.map((c) => /* @__PURE__ */ jsx7(
1471
+ "button",
1472
+ {
1473
+ type: "button",
1474
+ className: "june-doc__swatch",
1475
+ style: { background: c },
1476
+ "aria-label": c,
1477
+ title: c,
1478
+ "data-active": current?.toLowerCase() === c ? true : void 0,
1479
+ onMouseDown: (e) => e.preventDefault(),
1480
+ onClick: () => {
1481
+ onPick(c);
1482
+ close();
1483
+ }
1484
+ },
1485
+ c
1486
+ )) }),
1487
+ /* @__PURE__ */ jsxs5("div", { className: "june-doc__pop-row", children: [
1488
+ /* @__PURE__ */ jsx7(
1489
+ "button",
1490
+ {
1491
+ type: "button",
1492
+ className: "june-doc__btn june-doc__btn--ghost",
1493
+ onMouseDown: (e) => e.preventDefault(),
1494
+ onClick: () => {
1495
+ onReset();
1496
+ close();
1497
+ },
1498
+ children: resetLabel
1499
+ }
1500
+ ),
1501
+ /* @__PURE__ */ jsxs5("label", { className: "june-doc__custom-color", title: "Custom color", children: [
1502
+ /* @__PURE__ */ jsx7("span", { children: "Custom" }),
1503
+ /* @__PURE__ */ jsx7(
1504
+ "input",
1505
+ {
1506
+ type: "color",
1507
+ "aria-label": `${label}: custom`,
1508
+ defaultValue: current && /^#[0-9a-f]{6}$/i.test(current) ? current : "#000000",
1509
+ onChange: (e) => onPick(e.target.value)
1510
+ }
1511
+ )
1512
+ ] })
1513
+ ] })
1514
+ ] })
1515
+ }
1516
+ );
1517
+ }
1518
+ function LinkPanel({
1519
+ editor,
1520
+ href,
1521
+ isLink,
1522
+ close
1523
+ }) {
1524
+ const [value, setValue] = useState3(href);
1525
+ const apply = () => {
1526
+ const safe = sanitizeHref(value);
1527
+ if (safe) editor.chain().focus().extendMarkRange("link").setLink({ href: safe }).run();
1528
+ close();
1529
+ };
1530
+ const remove = () => {
1531
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
1532
+ close();
1533
+ };
1534
+ return /* @__PURE__ */ jsxs5("div", { className: "june-doc__form", role: "dialog", "aria-label": "Edit link", children: [
1535
+ /* @__PURE__ */ jsx7("div", { className: "june-doc__pop-title", children: "Link" }),
1536
+ /* @__PURE__ */ jsx7(
1537
+ "input",
1538
+ {
1539
+ "aria-label": "Link address",
1540
+ className: "june-doc__input",
1541
+ placeholder: "https://example.com",
1542
+ value,
1543
+ autoFocus: true,
1544
+ onChange: (e) => setValue(e.target.value),
1545
+ onKeyDown: (e) => {
1546
+ if (e.key === "Enter") {
1547
+ e.preventDefault();
1548
+ apply();
1549
+ }
1550
+ }
1551
+ }
1552
+ ),
1553
+ /* @__PURE__ */ jsxs5("div", { className: "june-doc__pop-row june-doc__pop-row--end", children: [
1554
+ isLink ? /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn june-doc__btn--ghost", onClick: remove, children: "Remove" }) : null,
1555
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: close, children: "Cancel" }),
1556
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn june-doc__btn--primary", onClick: apply, children: "Apply" })
1557
+ ] })
1558
+ ] });
1559
+ }
1560
+ function ImagePanel({ editor, close, onNotice }) {
1561
+ const [url, setUrl] = useState3("");
1562
+ const [alt, setAlt] = useState3("");
1563
+ const file = useRef4(null);
1564
+ const insert = (src) => {
1565
+ editor.chain().focus().setImage({ src, alt: alt || void 0 }).run();
1566
+ close();
1567
+ };
1568
+ const fromUrl = () => {
1569
+ const safe = sanitizeHref(url);
1570
+ if (!safe || !/^https?:/i.test(safe)) {
1571
+ onNotice("Enter an http(s) image address");
1572
+ return;
1573
+ }
1574
+ insert(safe);
1575
+ };
1576
+ const fromFile = (f) => {
1577
+ if (!f) return;
1578
+ if (!f.type.startsWith("image/")) return onNotice("That file is not an image");
1579
+ if (f.size > MAX_IMAGE_BYTES) return onNotice("Images must be 8 MB or smaller");
1580
+ const reader = new FileReader();
1581
+ reader.onload = () => typeof reader.result === "string" && insert(reader.result);
1582
+ reader.onerror = () => onNotice("The image could not be read");
1583
+ reader.readAsDataURL(f);
1584
+ };
1585
+ return /* @__PURE__ */ jsxs5("div", { className: "june-doc__form", role: "dialog", "aria-label": "Insert image", children: [
1586
+ /* @__PURE__ */ jsx7("div", { className: "june-doc__pop-title", children: "Image" }),
1587
+ /* @__PURE__ */ jsxs5(
1588
+ "button",
1589
+ {
1590
+ type: "button",
1591
+ className: "june-doc__dropzone",
1592
+ onClick: () => file.current?.click(),
1593
+ onDragOver: (e) => e.preventDefault(),
1594
+ onDrop: (e) => {
1595
+ e.preventDefault();
1596
+ fromFile(e.dataTransfer.files[0]);
1597
+ },
1598
+ children: [
1599
+ /* @__PURE__ */ jsx7(Icon, { name: "upload", size: 18 }),
1600
+ /* @__PURE__ */ jsx7("span", { children: "Upload or drop an image" })
1601
+ ]
1602
+ }
1603
+ ),
1604
+ /* @__PURE__ */ jsx7(
1605
+ "input",
1606
+ {
1607
+ ref: file,
1608
+ type: "file",
1609
+ accept: "image/*",
1610
+ hidden: true,
1611
+ onChange: (e) => fromFile(e.target.files?.[0])
1612
+ }
1613
+ ),
1614
+ /* @__PURE__ */ jsx7("div", { className: "june-doc__or", children: "or" }),
1615
+ /* @__PURE__ */ jsx7(
1616
+ "input",
1617
+ {
1618
+ "aria-label": "Image address",
1619
+ className: "june-doc__input",
1620
+ placeholder: "https://example.com/image.png",
1621
+ value: url,
1622
+ onChange: (e) => setUrl(e.target.value),
1623
+ onKeyDown: (e) => e.key === "Enter" && fromUrl()
1624
+ }
1625
+ ),
1626
+ /* @__PURE__ */ jsx7(
1627
+ "input",
1628
+ {
1629
+ "aria-label": "Alternative text",
1630
+ className: "june-doc__input",
1631
+ placeholder: "Alt text (optional)",
1632
+ value: alt,
1633
+ onChange: (e) => setAlt(e.target.value)
1634
+ }
1635
+ ),
1636
+ /* @__PURE__ */ jsxs5("div", { className: "june-doc__pop-row june-doc__pop-row--end", children: [
1637
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: close, children: "Cancel" }),
1638
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn june-doc__btn--primary", disabled: !url.trim(), onClick: fromUrl, children: "Insert" })
1639
+ ] })
1640
+ ] });
1641
+ }
1642
+ function TablePanel({ editor, close }) {
1643
+ const [hover, setHover] = useState3([0, 0]);
1644
+ const N = 8;
1645
+ return /* @__PURE__ */ jsxs5("div", { className: "june-doc__form", role: "dialog", "aria-label": "Insert table", children: [
1646
+ /* @__PURE__ */ jsxs5("div", { className: "june-doc__pop-title", children: [
1647
+ "Table ",
1648
+ hover[0] ? `${hover[0]} \xD7 ${hover[1]}` : ""
1649
+ ] }),
1650
+ /* @__PURE__ */ jsx7("div", { className: "june-doc__grid", onMouseLeave: () => setHover([0, 0]), children: Array.from({ length: N * N }, (_, i) => {
1651
+ const r = Math.floor(i / N) + 1;
1652
+ const c = i % N + 1;
1653
+ return /* @__PURE__ */ jsx7(
1654
+ "button",
1655
+ {
1656
+ type: "button",
1657
+ className: "june-doc__cell",
1658
+ "aria-label": `${r} by ${c} table`,
1659
+ "data-active": r <= hover[0] && c <= hover[1] ? true : void 0,
1660
+ onMouseEnter: () => setHover([r, c]),
1661
+ onFocus: () => setHover([r, c]),
1662
+ onMouseDown: (e) => e.preventDefault(),
1663
+ onClick: () => {
1664
+ editor.chain().focus().insertTable({ rows: r, cols: c, withHeaderRow: true }).run();
1665
+ close();
1666
+ }
1667
+ },
1668
+ i
1669
+ );
1670
+ }) })
1671
+ ] });
1672
+ }
1673
+ var px = (v) => typeof v === "string" ? v.replace(/px$/, "") : "";
1674
+ function DocumentToolbar(props) {
1675
+ const { editor, editable, expanded, onToggleExpanded, showMarks, onToggleMarks, layout, onToggleLayout, findOpen, onToggleFind, onNotice } = props;
1676
+ const s = useEditorState({
1677
+ editor,
1678
+ selector: ({ editor: ed }) => {
1679
+ if (!ed) return null;
1680
+ const ts = ed.getAttributes("textStyle");
1681
+ return {
1682
+ bold: ed.isActive("bold"),
1683
+ italic: ed.isActive("italic"),
1684
+ underline: ed.isActive("underline"),
1685
+ strike: ed.isActive("strike"),
1686
+ code: ed.isActive("code"),
1687
+ sup: ed.isActive("superscript"),
1688
+ sub: ed.isActive("subscript"),
1689
+ bullet: ed.isActive("bulletList"),
1690
+ ordered: ed.isActive("orderedList"),
1691
+ quote: ed.isActive("blockquote"),
1692
+ codeBlock: ed.isActive("codeBlock"),
1693
+ link: ed.isActive("link"),
1694
+ linkHref: ed.getAttributes("link").href ?? "",
1695
+ h1: ed.isActive("heading", { level: 1 }),
1696
+ h2: ed.isActive("heading", { level: 2 }),
1697
+ h3: ed.isActive("heading", { level: 3 }),
1698
+ left: ed.isActive({ textAlign: "left" }),
1699
+ center: ed.isActive({ textAlign: "center" }),
1700
+ right: ed.isActive({ textAlign: "right" }),
1701
+ justify: ed.isActive({ textAlign: "justify" }),
1702
+ inTable: ed.isActive("table"),
1703
+ color: ts.color ?? null,
1704
+ fontFamily: ts.fontFamily ?? "",
1705
+ fontSize: px(ts.fontSize),
1706
+ highlight: ed.isActive("highlight") ? ed.getAttributes("highlight").color ?? "#fef08a" : null,
1707
+ canUndo: ed.can().undo(),
1708
+ canRedo: ed.can().redo(),
1709
+ canSink: ed.can().sinkListItem("listItem"),
1710
+ canLift: ed.can().liftListItem("listItem"),
1711
+ canMerge: ed.can().mergeCells(),
1712
+ canSplit: ed.can().splitCell()
1713
+ };
1714
+ }
1715
+ });
1716
+ const run = () => editor?.chain().focus();
1717
+ const off = !editor || !editable;
1718
+ const style = s?.codeBlock ? "code" : s?.h1 ? "1" : s?.h2 ? "2" : s?.h3 ? "3" : s?.quote ? "quote" : "p";
1719
+ const setStyle = (v) => {
1720
+ if (!editor || !s) return;
1721
+ let c = editor.chain().focus();
1722
+ if (v === "quote") {
1723
+ if (!s.quote) c = c.setParagraph().toggleBlockquote();
1724
+ } else if (v === "code") {
1725
+ if (!s.codeBlock) c = c.toggleCodeBlock();
1726
+ } else {
1727
+ if (s.quote) c = c.lift("blockquote");
1728
+ c = v === "p" ? c.setParagraph() : c.setHeading({ level: Number(v) });
1729
+ }
1730
+ c.run();
1731
+ };
1732
+ const fontKnown = FONT_FAMILIES.some((f) => f.value === s?.fontFamily);
1733
+ const sizes = s?.fontSize && !FONT_SIZES.includes(Number(s.fontSize)) ? [...FONT_SIZES, Number(s.fontSize)].sort((a, b) => a - b) : FONT_SIZES;
1734
+ if (!editable) return null;
1735
+ return /* @__PURE__ */ jsxs5("div", { className: "june-doc__ribbon", role: "toolbar", "aria-label": "Document formatting", children: [
1736
+ /* @__PURE__ */ jsxs5("div", { className: "june-doc__row", children: [
1737
+ /* @__PURE__ */ jsxs5(Group, { label: "History", children: [
1738
+ /* @__PURE__ */ jsx7(Btn, { label: "Undo", icon: "undo", disabled: off || !s?.canUndo, onClick: () => run()?.undo().run() }),
1739
+ /* @__PURE__ */ jsx7(Btn, { label: "Redo", icon: "redo", disabled: off || !s?.canRedo, onClick: () => run()?.redo().run() })
1740
+ ] }),
1741
+ /* @__PURE__ */ jsxs5(Group, { label: "Format", children: [
1742
+ /* @__PURE__ */ jsx7(Btn, { label: "Bold", icon: "bold", toggle: true, active: s?.bold, disabled: off, onClick: () => run()?.toggleBold().run() }),
1743
+ /* @__PURE__ */ jsx7(Btn, { label: "Italic", icon: "italic", toggle: true, active: s?.italic, disabled: off, onClick: () => run()?.toggleItalic().run() }),
1744
+ /* @__PURE__ */ jsx7(Btn, { label: "Underline", icon: "underline", toggle: true, active: s?.underline, disabled: off, onClick: () => run()?.toggleUnderline().run() }),
1745
+ /* @__PURE__ */ jsx7(Btn, { label: "Strikethrough", icon: "strike", toggle: true, active: s?.strike, disabled: off, onClick: () => run()?.toggleStrike().run() }),
1746
+ /* @__PURE__ */ jsx7(Btn, { label: "Inline code", icon: "code", toggle: true, active: s?.code, disabled: off, onClick: () => run()?.toggleCode().run() }),
1747
+ /* @__PURE__ */ jsx7(Btn, { label: "Superscript", icon: "superscript", toggle: true, active: s?.sup, disabled: off, onClick: () => run()?.toggleSuperscript().run() }),
1748
+ /* @__PURE__ */ jsx7(Btn, { label: "Subscript", icon: "subscript", toggle: true, active: s?.sub, disabled: off, onClick: () => run()?.toggleSubscript().run() })
1749
+ ] }),
1750
+ /* @__PURE__ */ jsxs5(Group, { label: "Align", children: [
1751
+ /* @__PURE__ */ jsx7(Btn, { label: "Align left", icon: "alignLeft", toggle: true, active: s?.left, disabled: off, onClick: () => run()?.setTextAlign("left").run() }),
1752
+ /* @__PURE__ */ jsx7(Btn, { label: "Align center", icon: "alignCenter", toggle: true, active: s?.center, disabled: off, onClick: () => run()?.setTextAlign("center").run() }),
1753
+ /* @__PURE__ */ jsx7(Btn, { label: "Align right", icon: "alignRight", toggle: true, active: s?.right, disabled: off, onClick: () => run()?.setTextAlign("right").run() }),
1754
+ /* @__PURE__ */ jsx7(Btn, { label: "Justify", icon: "alignJustify", toggle: true, active: s?.justify, disabled: off, onClick: () => run()?.setTextAlign("justify").run() })
1755
+ ] }),
1756
+ /* @__PURE__ */ jsxs5(Group, { label: "Lists", children: [
1757
+ /* @__PURE__ */ jsx7(Btn, { label: "Bullet list", icon: "list", toggle: true, active: s?.bullet, disabled: off, onClick: () => run()?.toggleBulletList().run() }),
1758
+ /* @__PURE__ */ jsx7(Btn, { label: "Numbered list", icon: "listOrdered", toggle: true, active: s?.ordered, disabled: off, onClick: () => run()?.toggleOrderedList().run() }),
1759
+ /* @__PURE__ */ jsx7(Btn, { label: "Decrease indent", icon: "outdent", disabled: off || !s?.canLift, onClick: () => run()?.liftListItem("listItem").run() }),
1760
+ /* @__PURE__ */ jsx7(Btn, { label: "Increase indent", icon: "indent", disabled: off || !s?.canSink, onClick: () => run()?.sinkListItem("listItem").run() })
1761
+ ] }),
1762
+ /* @__PURE__ */ jsxs5(
1763
+ "button",
1764
+ {
1765
+ type: "button",
1766
+ className: "june-doc__btn june-doc__more",
1767
+ "aria-expanded": expanded,
1768
+ "aria-label": expanded ? "Show fewer tools" : "Show more tools",
1769
+ onMouseDown: (e) => e.preventDefault(),
1770
+ onClick: onToggleExpanded,
1771
+ children: [
1772
+ expanded ? "Less" : "More",
1773
+ /* @__PURE__ */ jsx7(Icon, { name: expanded ? "chevronUp" : "chevronDown", size: 14 })
1774
+ ]
1775
+ }
1776
+ )
1777
+ ] }),
1778
+ expanded ? /* @__PURE__ */ jsxs5("div", { className: "june-doc__row june-doc__row--secondary", children: [
1779
+ /* @__PURE__ */ jsxs5(Group, { label: "Style", children: [
1780
+ /* @__PURE__ */ jsx7(Field, { label: "Style", children: /* @__PURE__ */ jsxs5("select", { className: "june-doc__select", "aria-label": "Text style", disabled: off, value: style, onChange: (e) => setStyle(e.target.value), children: [
1781
+ /* @__PURE__ */ jsx7("option", { value: "p", children: "Paragraph" }),
1782
+ /* @__PURE__ */ jsx7("option", { value: "1", children: "Heading 1" }),
1783
+ /* @__PURE__ */ jsx7("option", { value: "2", children: "Heading 2" }),
1784
+ /* @__PURE__ */ jsx7("option", { value: "3", children: "Heading 3" }),
1785
+ /* @__PURE__ */ jsx7("option", { value: "quote", children: "Quote" }),
1786
+ /* @__PURE__ */ jsx7("option", { value: "code", children: "Code block" })
1787
+ ] }) }),
1788
+ /* @__PURE__ */ jsx7(Field, { label: "Font", children: /* @__PURE__ */ jsxs5(
1789
+ "select",
1790
+ {
1791
+ className: "june-doc__select june-doc__select--font",
1792
+ "aria-label": "Font family",
1793
+ disabled: off,
1794
+ value: s?.fontFamily ?? "",
1795
+ onChange: (e) => {
1796
+ const v = e.target.value;
1797
+ if (v) run()?.setFontFamily(v).run();
1798
+ else run()?.unsetFontFamily().run();
1799
+ },
1800
+ children: [
1801
+ FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx7("option", { value: f.value, style: { fontFamily: f.value || void 0 }, children: f.label }, f.label)),
1802
+ !fontKnown && s?.fontFamily ? /* @__PURE__ */ jsx7("option", { value: s.fontFamily, children: s.fontFamily.split(",")[0].replace(/['"]/g, "") }) : null
1803
+ ]
1804
+ }
1805
+ ) }),
1806
+ /* @__PURE__ */ jsx7(Field, { label: "Size", children: /* @__PURE__ */ jsxs5(
1807
+ "select",
1808
+ {
1809
+ className: "june-doc__select june-doc__select--size",
1810
+ "aria-label": "Font size",
1811
+ disabled: off,
1812
+ value: s?.fontSize ?? "",
1813
+ onChange: (e) => {
1814
+ const v = e.target.value;
1815
+ if (v) run()?.setFontSize(`${v}px`).run();
1816
+ else run()?.unsetFontSize().run();
1817
+ },
1818
+ children: [
1819
+ /* @__PURE__ */ jsx7("option", { value: "", children: "Auto" }),
1820
+ sizes.map((n) => /* @__PURE__ */ jsx7("option", { value: String(n), children: n }, n))
1821
+ ]
1822
+ }
1823
+ ) })
1824
+ ] }),
1825
+ /* @__PURE__ */ jsx7(Btn, { label: "Show formatting marks", icon: "pilcrow", toggle: true, active: showMarks, onClick: onToggleMarks, text: true, className: "june-doc__btn--marks", children: /* @__PURE__ */ jsx7("span", { children: "Paragraph" }) }),
1826
+ /* @__PURE__ */ jsxs5(Group, { label: "Document", children: [
1827
+ /* @__PURE__ */ jsx7(Btn, { label: "Insert page break", icon: "pageBreak", disabled: off, onClick: () => run()?.setPageBreak().run() }),
1828
+ /* @__PURE__ */ jsx7(Btn, { label: "Insert divider", icon: "divider", disabled: off, onClick: () => run()?.setHorizontalRule().run() }),
1829
+ /* @__PURE__ */ jsx7(Btn, { label: layout === "page" ? "Switch to full-width layout" : "Switch to page layout", icon: "layout", toggle: true, active: layout === "fluid", onClick: onToggleLayout }),
1830
+ /* @__PURE__ */ jsx7(Btn, { label: "Find and replace", icon: "search", toggle: true, active: findOpen, onClick: onToggleFind })
1831
+ ] }),
1832
+ /* @__PURE__ */ jsxs5(Group, { label: "Color", children: [
1833
+ /* @__PURE__ */ jsx7(
1834
+ ColorPicker,
1835
+ {
1836
+ label: "Text color",
1837
+ icon: "palette",
1838
+ colors: TEXT_COLORS,
1839
+ current: s?.color ?? null,
1840
+ disabled: off,
1841
+ resetLabel: "Default color",
1842
+ onPick: (c) => run()?.setColor(c).run(),
1843
+ onReset: () => run()?.unsetColor().run()
1844
+ }
1845
+ ),
1846
+ /* @__PURE__ */ jsx7(
1847
+ ColorPicker,
1848
+ {
1849
+ label: "Highlight",
1850
+ icon: "highlighter",
1851
+ colors: HIGHLIGHT_COLORS,
1852
+ current: s?.highlight ?? null,
1853
+ disabled: off,
1854
+ resetLabel: "No highlight",
1855
+ onPick: (c) => run()?.setHighlight({ color: c }).run(),
1856
+ onReset: () => run()?.unsetHighlight().run()
1857
+ }
1858
+ )
1859
+ ] }),
1860
+ /* @__PURE__ */ jsxs5(Group, { label: "Insert", children: [
1861
+ editor ? /* @__PURE__ */ jsx7(
1862
+ Dropdown,
1863
+ {
1864
+ trigger: ({ open, toggle }) => /* @__PURE__ */ jsx7(Btn, { label: "Link", icon: "link", toggle: true, active: s?.link || open, disabled: off, onClick: toggle }),
1865
+ children: (close) => /* @__PURE__ */ jsx7(LinkPanel, { editor, href: s?.linkHref ?? "", isLink: !!s?.link, close })
1866
+ }
1867
+ ) : null,
1868
+ editor ? /* @__PURE__ */ jsx7(Dropdown, { trigger: ({ open, toggle }) => /* @__PURE__ */ jsx7(Btn, { label: "Insert image", icon: "image", active: open, disabled: off, onClick: toggle }), children: (close) => /* @__PURE__ */ jsx7(ImagePanel, { editor, close, onNotice }) }) : null,
1869
+ editor ? /* @__PURE__ */ jsx7(Dropdown, { trigger: ({ open, toggle }) => /* @__PURE__ */ jsx7(Btn, { label: "Insert table", icon: "table", active: open, disabled: off, onClick: toggle }), children: (close) => /* @__PURE__ */ jsx7(TablePanel, { editor, close }) }) : null
1870
+ ] }),
1871
+ /* @__PURE__ */ jsx7(Group, { label: "Actions", children: /* @__PURE__ */ jsx7(Btn, { label: "Clear formatting", icon: "eraser", disabled: off, onClick: () => run()?.unsetAllMarks().clearNodes().run() }) })
1872
+ ] }) : null,
1873
+ s?.inTable ? /* @__PURE__ */ jsx7("div", { className: "june-doc__row june-doc__row--context", children: /* @__PURE__ */ jsxs5(Group, { label: "Table", children: [
1874
+ /* @__PURE__ */ jsx7(Btn, { label: "Add row below", disabled: off, onClick: () => run()?.addRowAfter().run(), children: "+ Row" }),
1875
+ /* @__PURE__ */ jsx7(Btn, { label: "Add column right", disabled: off, onClick: () => run()?.addColumnAfter().run(), children: "+ Column" }),
1876
+ /* @__PURE__ */ jsx7(Btn, { label: "Delete row", disabled: off, onClick: () => run()?.deleteRow().run(), children: "\u2212 Row" }),
1877
+ /* @__PURE__ */ jsx7(Btn, { label: "Delete column", disabled: off, onClick: () => run()?.deleteColumn().run(), children: "\u2212 Column" }),
1878
+ /* @__PURE__ */ jsx7(Btn, { label: "Merge cells", disabled: off || !s?.canMerge, onClick: () => run()?.mergeCells().run(), children: "Merge" }),
1879
+ /* @__PURE__ */ jsx7(Btn, { label: "Split cell", disabled: off || !s?.canSplit, onClick: () => run()?.splitCell().run(), children: "Split" }),
1880
+ /* @__PURE__ */ jsx7(Btn, { label: "Toggle header row", disabled: off, onClick: () => run()?.toggleHeaderRow().run(), children: "Header row" }),
1881
+ /* @__PURE__ */ jsx7(Btn, { label: "Delete table", disabled: off, onClick: () => run()?.deleteTable().run(), className: "june-doc__btn--danger", children: "Delete table" })
1882
+ ] }) }) : null
1883
+ ] });
1884
+ }
1885
+
1886
+ // src/components/document-editor/document-bubble.tsx
1887
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1888
+ function DocumentBubbleMenu({ editor }) {
1889
+ const s = useEditorState2({
1890
+ editor,
1891
+ selector: ({ editor: ed }) => ({
1892
+ bold: ed.isActive("bold"),
1893
+ italic: ed.isActive("italic"),
1894
+ underline: ed.isActive("underline"),
1895
+ strike: ed.isActive("strike"),
1896
+ link: ed.isActive("link"),
1897
+ linkHref: ed.getAttributes("link").href ?? "",
1898
+ color: ed.getAttributes("textStyle").color ?? null,
1899
+ highlight: ed.isActive("highlight") ? ed.getAttributes("highlight").color ?? "#fef08a" : null
1900
+ })
1901
+ });
1902
+ const run = () => editor.chain().focus();
1903
+ return /* @__PURE__ */ jsxs6(
1904
+ BubbleMenu,
1905
+ {
1906
+ editor,
1907
+ className: "june-doc__bubble",
1908
+ options: { placement: "top", offset: 8 },
1909
+ shouldShow: ({ editor: ed, state }) => {
1910
+ const { empty } = state.selection;
1911
+ if (empty || !ed.isEditable) return false;
1912
+ if (ed.isActive("image") || ed.isActive("pageBreak") || ed.isActive("codeBlock")) return false;
1913
+ return state.doc.textBetween(state.selection.from, state.selection.to).trim().length > 0;
1914
+ },
1915
+ children: [
1916
+ /* @__PURE__ */ jsx8(Btn, { label: "Bold", icon: "bold", toggle: true, active: s.bold, onClick: () => run().toggleBold().run() }),
1917
+ /* @__PURE__ */ jsx8(Btn, { label: "Italic", icon: "italic", toggle: true, active: s.italic, onClick: () => run().toggleItalic().run() }),
1918
+ /* @__PURE__ */ jsx8(Btn, { label: "Underline", icon: "underline", toggle: true, active: s.underline, onClick: () => run().toggleUnderline().run() }),
1919
+ /* @__PURE__ */ jsx8(Btn, { label: "Strikethrough", icon: "strike", toggle: true, active: s.strike, onClick: () => run().toggleStrike().run() }),
1920
+ /* @__PURE__ */ jsx8("span", { className: "june-doc__bubble-sep", "aria-hidden": "true" }),
1921
+ /* @__PURE__ */ jsx8(Dropdown, { trigger: ({ open, toggle }) => /* @__PURE__ */ jsx8(Btn, { label: "Link", icon: "link", toggle: true, active: s.link || open, onClick: toggle }), children: (close) => /* @__PURE__ */ jsx8(LinkPanel, { editor, href: s.linkHref, isLink: s.link, close }) }),
1922
+ /* @__PURE__ */ jsx8(
1923
+ ColorPicker,
1924
+ {
1925
+ label: "Text color",
1926
+ icon: "palette",
1927
+ colors: TEXT_COLORS,
1928
+ current: s.color,
1929
+ resetLabel: "Default color",
1930
+ onPick: (c) => run().setColor(c).run(),
1931
+ onReset: () => run().unsetColor().run()
1932
+ }
1933
+ ),
1934
+ /* @__PURE__ */ jsx8(
1935
+ ColorPicker,
1936
+ {
1937
+ label: "Highlight",
1938
+ icon: "highlighter",
1939
+ colors: HIGHLIGHT_COLORS,
1940
+ current: s.highlight,
1941
+ resetLabel: "No highlight",
1942
+ onPick: (c) => run().setHighlight({ color: c }).run(),
1943
+ onReset: () => run().unsetHighlight().run()
1944
+ }
1945
+ ),
1946
+ /* @__PURE__ */ jsx8("span", { className: "june-doc__bubble-sep", "aria-hidden": "true" }),
1947
+ /* @__PURE__ */ jsx8(Btn, { label: "Clear formatting", icon: "eraser", onClick: () => run().unsetAllMarks().run() })
1948
+ ]
1949
+ }
1950
+ );
1951
+ }
1094
1952
 
1095
1953
  // src/components/document-editor/document-content.tsx
1096
1954
  import { EditorContent } from "@tiptap/react";
1097
- import { jsx as jsx5 } from "react/jsx-runtime";
1098
- function DocumentContent({ editor }) {
1099
- return /* @__PURE__ */ jsx5("div", { className: "june-doc__content", children: /* @__PURE__ */ jsx5(EditorContent, { editor }) });
1955
+ import { useEffect as useEffect3, useRef as useRef5, useState as useState4 } from "react";
1956
+ import { jsx as jsx9 } from "react/jsx-runtime";
1957
+ var PAGE_HEIGHT = 1123;
1958
+ var PAGE_PAD_Y = 96;
1959
+ function DocumentContent({ editor, layout = "page", zoom = 1, showMarks = false, onMetrics }) {
1960
+ const canvas = useRef5(null);
1961
+ const sheet = useRef5(null);
1962
+ const [pages, setPages] = useState4(1);
1963
+ const metricsRef = useRef5(onMetrics);
1964
+ metricsRef.current = onMetrics;
1965
+ const last = useRef5({ page: 0, pages: 0 });
1966
+ useEffect3(() => {
1967
+ const c = canvas.current;
1968
+ const p = sheet.current;
1969
+ const prose = editor?.view.dom;
1970
+ if (!c || !p || !prose) return;
1971
+ const measure = () => {
1972
+ const contentH = prose.getBoundingClientRect().height / zoom;
1973
+ const total = layout === "page" ? Math.max(1, Math.ceil((contentH + PAGE_PAD_Y * 2) / PAGE_HEIGHT)) : 1;
1974
+ const cRect = c.getBoundingClientRect();
1975
+ const pRect = p.getBoundingClientRect();
1976
+ const y = cRect.top + c.clientHeight * 0.35 - pRect.top;
1977
+ const page = Math.min(total, Math.max(1, Math.floor(y / (PAGE_HEIGHT * zoom)) + 1));
1978
+ setPages(total);
1979
+ if (last.current.page !== page || last.current.pages !== total) {
1980
+ last.current = { page, pages: total };
1981
+ metricsRef.current?.({ page, pages: total });
1982
+ }
1983
+ };
1984
+ measure();
1985
+ c.addEventListener("scroll", measure, { passive: true });
1986
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(measure) : null;
1987
+ ro?.observe(prose);
1988
+ ro?.observe(c);
1989
+ editor?.on("update", measure);
1990
+ return () => {
1991
+ c.removeEventListener("scroll", measure);
1992
+ ro?.disconnect();
1993
+ editor?.off("update", measure);
1994
+ };
1995
+ }, [editor, layout, zoom]);
1996
+ const sheetStyle = layout === "page" ? {
1997
+ zoom,
1998
+ minHeight: pages * PAGE_HEIGHT,
1999
+ maxWidth: `calc(100% / ${zoom})`,
2000
+ "--jd-page-h": `${PAGE_HEIGHT}px`
2001
+ } : { zoom };
2002
+ return /* @__PURE__ */ jsx9("div", { ref: canvas, className: "june-doc__content june-doc__canvas", "data-layout": layout, "data-marks": showMarks || void 0, children: /* @__PURE__ */ jsx9("div", { ref: sheet, className: "june-doc__sheet", style: sheetStyle, children: /* @__PURE__ */ jsx9(EditorContent, { editor }) }) });
2003
+ }
2004
+
2005
+ // src/components/document-editor/document-findbar.tsx
2006
+ import { useEditorState as useEditorState3 } from "@tiptap/react";
2007
+ import { useEffect as useEffect4, useRef as useRef6, useState as useState5 } from "react";
2008
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2009
+ function DocumentFindBar({ editor, editable, onClose, onNotice }) {
2010
+ const [term, setTerm] = useState5(() => {
2011
+ const { from, to, empty } = editor.state.selection;
2012
+ return empty ? "" : editor.state.doc.textBetween(from, to, " ").slice(0, 100);
2013
+ });
2014
+ const [replacement, setReplacement] = useState5("");
2015
+ const [caseSensitive, setCaseSensitive] = useState5(false);
2016
+ const input = useRef6(null);
2017
+ const info = useEditorState3({
2018
+ editor,
2019
+ selector: ({ editor: ed }) => {
2020
+ const s = getSearchState(ed);
2021
+ return { count: s?.matches.length ?? 0, current: s?.current ?? -1 };
2022
+ }
2023
+ });
2024
+ useEffect4(() => {
2025
+ input.current?.focus();
2026
+ input.current?.select();
2027
+ }, []);
2028
+ useEffect4(() => {
2029
+ setSearch(editor, { term, caseSensitive, current: 0 });
2030
+ }, [editor, term, caseSensitive]);
2031
+ useEffect4(() => () => {
2032
+ if (!editor.isDestroyed) setSearch(editor, { term: "" });
2033
+ }, [editor]);
2034
+ const count = info?.count ?? 0;
2035
+ const current = info?.current ?? -1;
2036
+ return /* @__PURE__ */ jsxs7(
2037
+ "div",
2038
+ {
2039
+ className: "june-doc__findbar",
2040
+ role: "search",
2041
+ onKeyDown: (e) => {
2042
+ if (e.key === "Escape") {
2043
+ e.stopPropagation();
2044
+ onClose();
2045
+ editor.commands.focus();
2046
+ }
2047
+ },
2048
+ children: [
2049
+ /* @__PURE__ */ jsxs7("div", { className: "june-doc__find-field", children: [
2050
+ /* @__PURE__ */ jsx10(Icon, { name: "search", size: 14 }),
2051
+ /* @__PURE__ */ jsx10(
2052
+ "input",
2053
+ {
2054
+ ref: input,
2055
+ className: "june-doc__find-input",
2056
+ "aria-label": "Find in document",
2057
+ placeholder: "Find in document",
2058
+ value: term,
2059
+ onChange: (e) => setTerm(e.target.value),
2060
+ onKeyDown: (e) => {
2061
+ if (e.key === "Enter") {
2062
+ e.preventDefault();
2063
+ gotoMatch(editor, e.shiftKey ? -1 : 1);
2064
+ }
2065
+ }
2066
+ }
2067
+ ),
2068
+ /* @__PURE__ */ jsx10("span", { className: "june-doc__find-count", "aria-live": "polite", children: term ? count ? `${current + 1} of ${count}` : "No results" : "" })
2069
+ ] }),
2070
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "june-doc__btn june-doc__btn--icon", "aria-label": "Previous match", title: "Previous (Shift+Enter)", disabled: !count, onClick: () => gotoMatch(editor, -1), children: /* @__PURE__ */ jsx10(Icon, { name: "chevronUp" }) }),
2071
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "june-doc__btn june-doc__btn--icon", "aria-label": "Next match", title: "Next (Enter)", disabled: !count, onClick: () => gotoMatch(editor, 1), children: /* @__PURE__ */ jsx10(Icon, { name: "chevronDown" }) }),
2072
+ /* @__PURE__ */ jsx10(
2073
+ "button",
2074
+ {
2075
+ type: "button",
2076
+ className: "june-doc__btn june-doc__btn--icon june-doc__btn--case",
2077
+ "aria-label": "Match case",
2078
+ title: "Match case",
2079
+ "aria-pressed": caseSensitive,
2080
+ "data-active": caseSensitive || void 0,
2081
+ onClick: () => setCaseSensitive((v) => !v),
2082
+ children: "Aa"
2083
+ }
2084
+ ),
2085
+ editable ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
2086
+ /* @__PURE__ */ jsx10("span", { className: "june-doc__find-sep", "aria-hidden": "true" }),
2087
+ /* @__PURE__ */ jsx10(
2088
+ "input",
2089
+ {
2090
+ className: "june-doc__input june-doc__replace-input",
2091
+ "aria-label": "Replace with",
2092
+ placeholder: "Replace with",
2093
+ value: replacement,
2094
+ onChange: (e) => setReplacement(e.target.value),
2095
+ onKeyDown: (e) => {
2096
+ if (e.key === "Enter") {
2097
+ e.preventDefault();
2098
+ replaceCurrent(editor, replacement);
2099
+ }
2100
+ }
2101
+ }
2102
+ ),
2103
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "june-doc__btn", disabled: !count, onClick: () => replaceCurrent(editor, replacement), children: "Replace" }),
2104
+ /* @__PURE__ */ jsx10(
2105
+ "button",
2106
+ {
2107
+ type: "button",
2108
+ className: "june-doc__btn",
2109
+ disabled: !count,
2110
+ onClick: () => {
2111
+ const n = replaceAll(editor, replacement);
2112
+ onNotice(`Replaced ${n} ${n === 1 ? "match" : "matches"}`);
2113
+ },
2114
+ children: "Replace all"
2115
+ }
2116
+ )
2117
+ ] }) : null,
2118
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost june-doc__find-close", "aria-label": "Close find", onClick: onClose, children: /* @__PURE__ */ jsx10(Icon, { name: "close" }) })
2119
+ ]
2120
+ }
2121
+ );
2122
+ }
2123
+
2124
+ // src/components/document-editor/document-title.tsx
2125
+ import { jsx as jsx11 } from "react/jsx-runtime";
2126
+ function DocumentTitle({ value, disabled, onChange }) {
2127
+ return /* @__PURE__ */ jsx11(
2128
+ "input",
2129
+ {
2130
+ className: "june-doc__title",
2131
+ "aria-label": "Document title",
2132
+ value,
2133
+ disabled,
2134
+ placeholder: "Untitled document",
2135
+ onChange: (e) => onChange(e.target.value)
2136
+ }
2137
+ );
2138
+ }
2139
+
2140
+ // src/components/document-editor/document-header.tsx
2141
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2142
+ var FORMATS = {
2143
+ docx: { label: "Word document", hint: ".docx", icon: "file" },
2144
+ pdf: { label: "PDF", hint: "Print / Save as PDF", icon: "file" },
2145
+ html: { label: "Web page", hint: ".html", icon: "code" },
2146
+ markdown: { label: "Markdown", hint: ".md", icon: "file" },
2147
+ txt: { label: "Plain text", hint: ".txt", icon: "file" }
2148
+ };
2149
+ function DocumentHeader(props) {
2150
+ const { title, editable, isDraft, fullscreen, onToggleFullscreen, onTitle, exportFormats, canSave, saveState, onExport, onCopy, onSave } = props;
2151
+ return /* @__PURE__ */ jsxs8("div", { className: "june-doc__header", children: [
2152
+ fullscreen ? /* @__PURE__ */ jsx12("button", { type: "button", className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost", "aria-label": "Close full screen", title: "Close (Esc)", onClick: onToggleFullscreen, children: /* @__PURE__ */ jsx12(Icon, { name: "close", size: 18 }) }) : /* @__PURE__ */ jsx12("span", { className: "june-doc__doc-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx12(Icon, { name: "file", size: 16 }) }),
2153
+ /* @__PURE__ */ jsx12(DocumentTitle, { value: title, disabled: !editable, onChange: onTitle }),
2154
+ isDraft ? /* @__PURE__ */ jsxs8("span", { className: "june-doc__badge", children: [
2155
+ /* @__PURE__ */ jsx12(Icon, { name: "sparkles", size: 12 }),
2156
+ "AI draft"
2157
+ ] }) : null,
2158
+ /* @__PURE__ */ jsxs8("div", { className: "june-doc__header-actions", children: [
2159
+ canSave ? /* @__PURE__ */ jsxs8(
2160
+ "button",
2161
+ {
2162
+ type: "button",
2163
+ className: "june-doc__btn june-doc__btn--outline",
2164
+ "aria-label": "Save",
2165
+ title: "Save (Ctrl+S)",
2166
+ disabled: saveState === "saving" || saveState === "saved" || saveState === "idle",
2167
+ onClick: onSave,
2168
+ children: [
2169
+ /* @__PURE__ */ jsx12(Icon, { name: saveState === "saved" ? "check" : "save" }),
2170
+ /* @__PURE__ */ jsx12("span", { children: saveState === "saving" ? "Saving\u2026" : saveState === "saved" ? "Saved" : "Save" })
2171
+ ]
2172
+ }
2173
+ ) : null,
2174
+ /* @__PURE__ */ jsx12(
2175
+ Dropdown,
2176
+ {
2177
+ align: "end",
2178
+ className: "june-doc__menu",
2179
+ trigger: ({ open, toggle }) => /* @__PURE__ */ jsxs8(
2180
+ "button",
2181
+ {
2182
+ type: "button",
2183
+ className: "june-doc__btn june-doc__btn--solid",
2184
+ "aria-label": "Download",
2185
+ "aria-haspopup": "menu",
2186
+ "aria-expanded": open,
2187
+ onClick: toggle,
2188
+ children: [
2189
+ /* @__PURE__ */ jsx12("span", { children: "Download" }),
2190
+ /* @__PURE__ */ jsx12(Icon, { name: "chevronDown", size: 14 })
2191
+ ]
2192
+ }
2193
+ ),
2194
+ children: (close) => /* @__PURE__ */ jsxs8("div", { role: "menu", "aria-label": "Download options", children: [
2195
+ exportFormats.map((f) => {
2196
+ const meta = FORMATS[f] ?? { label: f.toUpperCase(), hint: "", icon: "file" };
2197
+ return /* @__PURE__ */ jsxs8(
2198
+ "button",
2199
+ {
2200
+ type: "button",
2201
+ role: "menuitem",
2202
+ className: "june-doc__menu-item",
2203
+ onClick: () => {
2204
+ close();
2205
+ onExport(f);
2206
+ },
2207
+ children: [
2208
+ /* @__PURE__ */ jsx12(Icon, { name: meta.icon }),
2209
+ /* @__PURE__ */ jsx12("span", { className: "june-doc__menu-label", children: meta.label }),
2210
+ /* @__PURE__ */ jsx12("span", { className: "june-doc__menu-hint", children: meta.hint })
2211
+ ]
2212
+ },
2213
+ f
2214
+ );
2215
+ }),
2216
+ exportFormats.length ? /* @__PURE__ */ jsx12("div", { className: "june-doc__menu-sep", role: "separator" }) : null,
2217
+ /* @__PURE__ */ jsxs8(
2218
+ "button",
2219
+ {
2220
+ type: "button",
2221
+ role: "menuitem",
2222
+ className: "june-doc__menu-item",
2223
+ "aria-label": "Copy document",
2224
+ onClick: () => {
2225
+ close();
2226
+ onCopy();
2227
+ },
2228
+ children: [
2229
+ /* @__PURE__ */ jsx12(Icon, { name: "copy" }),
2230
+ /* @__PURE__ */ jsx12("span", { className: "june-doc__menu-label", children: "Copy to clipboard" })
2231
+ ]
2232
+ }
2233
+ )
2234
+ ] })
2235
+ }
2236
+ ),
2237
+ /* @__PURE__ */ jsx12(
2238
+ "button",
2239
+ {
2240
+ type: "button",
2241
+ className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost",
2242
+ "aria-label": fullscreen ? "Exit full screen" : "Open full screen",
2243
+ title: fullscreen ? "Exit full screen" : "Open full screen",
2244
+ "aria-pressed": fullscreen,
2245
+ onClick: onToggleFullscreen,
2246
+ children: /* @__PURE__ */ jsx12(Icon, { name: "maximize" })
2247
+ }
2248
+ )
2249
+ ] })
2250
+ ] });
1100
2251
  }
1101
2252
 
1102
2253
  // src/components/document-editor/document-export.ts
1103
2254
  var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1104
- var PRINT_CSS = 'body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;line-height:1.6;color:#111827;max-width:780px;margin:32px auto;padding:0 20px}h1,h2,h3{line-height:1.25}table{border-collapse:collapse;width:100%}th,td{border:1px solid #d1d5db;padding:6px 10px;text-align:left}blockquote{border-left:4px solid #d1d5db;margin:1em 0;padding-left:1em;color:#4b5563}pre,code{font-family:ui-monospace,Menlo,monospace;background:#f3f4f6}pre{padding:12px;overflow:auto}@media print{body{margin:0;max-width:none}}';
2255
+ var PRINT_CSS = 'body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;line-height:1.6;color:#111827;max-width:780px;margin:32px auto;padding:0 20px}h1,h2,h3{line-height:1.25}table{border-collapse:collapse;width:100%}th,td{border:1px solid #d1d5db;padding:6px 10px;text-align:left}blockquote{border-left:4px solid #d1d5db;margin:1em 0;padding-left:1em;color:#4b5563}pre,code{font-family:ui-monospace,Menlo,monospace;background:#f3f4f6}pre{padding:12px;overflow:auto}img{max-width:100%;height:auto}mark{padding:0 .1em;border-radius:2px}div[data-page-break]{break-after:page;page-break-after:always;height:0}@media screen{div[data-page-break]{border-top:1px dashed #d1d5db;margin:24px 0}}@media print{body{margin:0;max-width:none}}';
1105
2256
  function buildHtmlDocument(title, bodyHtml) {
1106
2257
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${escapeHtml(
1107
2258
  title
@@ -1114,7 +2265,7 @@ var textResult = (text, filename, mimeType) => ({
1114
2265
  blob: new Blob([text], { type: `${mimeType};charset=utf-8` })
1115
2266
  });
1116
2267
  var exportHtml = async (ctx) => {
1117
- const { docToHtml: docToHtml2 } = await import("./document-engine-C6ILFMRP.js");
2268
+ const { docToHtml: docToHtml2 } = await import("./document-engine-IAY2T6B3.js");
1118
2269
  return textResult(
1119
2270
  buildHtmlDocument(ctx.title, docToHtml2(ctx.json)),
1120
2271
  `${ctx.basename}.html`,
@@ -1122,7 +2273,7 @@ var exportHtml = async (ctx) => {
1122
2273
  );
1123
2274
  };
1124
2275
  var exportMarkdown = async (ctx) => {
1125
- const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-C6ILFMRP.js");
2276
+ const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-IAY2T6B3.js");
1126
2277
  return textResult(
1127
2278
  `# ${ctx.title}
1128
2279
 
@@ -1133,12 +2284,39 @@ ${docToMarkdown2(ctx.json)}
1133
2284
  );
1134
2285
  };
1135
2286
  var exportTxt = async (ctx) => {
1136
- const { docToText } = await import("./document-engine-C6ILFMRP.js");
2287
+ const { docToText } = await import("./document-engine-IAY2T6B3.js");
1137
2288
  return textResult(`${ctx.title}
1138
2289
 
1139
2290
  ${docToText(ctx.json)}
1140
2291
  `, `${ctx.basename}.txt`, "text/plain");
1141
2292
  };
2293
+ function toDocxColor(value) {
2294
+ if (typeof value !== "string") return void 0;
2295
+ const v = value.trim();
2296
+ let m = /^#([0-9a-f]{3})$/i.exec(v);
2297
+ if (m) return m[1].split("").map((c) => c + c).join("").toUpperCase();
2298
+ m = /^#([0-9a-f]{6})$/i.exec(v);
2299
+ if (m) return m[1].toUpperCase();
2300
+ m = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(v);
2301
+ if (m) {
2302
+ return [m[1], m[2], m[3]].map((n) => Math.min(255, Number(n)).toString(16).padStart(2, "0")).join("").toUpperCase();
2303
+ }
2304
+ return void 0;
2305
+ }
2306
+ function toHalfPoints(value) {
2307
+ if (typeof value !== "string") return void 0;
2308
+ const m = /^([\d.]+)\s*(px|pt)?$/i.exec(value.trim());
2309
+ if (!m) return void 0;
2310
+ const n = Number(m[1]);
2311
+ if (!Number.isFinite(n) || n <= 0) return void 0;
2312
+ const pt = m[2]?.toLowerCase() === "pt" ? n : n * 0.75;
2313
+ return Math.round(pt * 2);
2314
+ }
2315
+ function firstFontFamily(value) {
2316
+ if (typeof value !== "string") return void 0;
2317
+ const first = value.split(",")[0]?.trim().replace(/^['"]|['"]$/g, "");
2318
+ return first || void 0;
2319
+ }
1142
2320
  var ORDERED_REF = "june-ordered";
1143
2321
  var DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
1144
2322
  function runs(d, node, fmt) {
@@ -1152,14 +2330,21 @@ function runs(d, node, fmt) {
1152
2330
  const marks = child.marks ?? [];
1153
2331
  const has = (type) => marks.some((m) => m.type === type);
1154
2332
  const link = marks.find((m) => m.type === "link");
2333
+ const style = marks.find((m) => m.type === "textStyle")?.attrs ?? {};
2334
+ const highlight = marks.find((m) => m.type === "highlight");
2335
+ const fill = highlight ? toDocxColor(highlight.attrs?.color) ?? "FFF59D" : void 0;
1155
2336
  const run = new d.TextRun({
1156
2337
  text: child.text,
1157
2338
  bold: fmt.bold || has("bold") || void 0,
1158
2339
  italics: has("italic") || void 0,
1159
2340
  underline: has("underline") || link ? {} : void 0,
1160
2341
  strike: has("strike") || void 0,
1161
- font: has("code") ? "Courier New" : void 0,
1162
- color: link ? "0563C1" : void 0
2342
+ superScript: has("superscript") || void 0,
2343
+ subScript: has("subscript") || void 0,
2344
+ font: has("code") ? "Courier New" : firstFontFamily(style.fontFamily),
2345
+ size: toHalfPoints(style.fontSize),
2346
+ color: toDocxColor(style.color) ?? (link ? "0563C1" : void 0),
2347
+ shading: fill ? { type: d.ShadingType.CLEAR, fill, color: "auto" } : void 0
1163
2348
  });
1164
2349
  const href = typeof link?.attrs?.href === "string" ? sanitizeHref(link.attrs.href) : null;
1165
2350
  out.push(href ? new d.ExternalHyperlink({ link: href, children: [run] }) : run);
@@ -1228,6 +2413,100 @@ function table(d, node, state) {
1228
2413
  if (rows.length === 0) return new d.Paragraph("");
1229
2414
  return new d.Table({ rows, width: { size: 100, type: d.WidthType.PERCENTAGE } });
1230
2415
  }
2416
+ function image(d, node, state) {
2417
+ const src = typeof node.attrs?.src === "string" ? node.attrs.src : "";
2418
+ const alt = typeof node.attrs?.alt === "string" ? node.attrs.alt : "";
2419
+ const img = state.images.get(src);
2420
+ if (img) {
2421
+ return [
2422
+ new d.Paragraph({
2423
+ children: [
2424
+ new d.ImageRun({
2425
+ type: img.type,
2426
+ data: img.data,
2427
+ transformation: { width: img.width, height: img.height },
2428
+ altText: alt ? { name: alt, description: alt, title: alt } : void 0
2429
+ })
2430
+ ],
2431
+ spacing: { after: 120 }
2432
+ })
2433
+ ];
2434
+ }
2435
+ const href = src && !src.startsWith("data:") ? sanitizeHref(src) : null;
2436
+ if (!href) return [];
2437
+ return [
2438
+ new d.Paragraph({
2439
+ children: [
2440
+ new d.ExternalHyperlink({
2441
+ link: href,
2442
+ children: [new d.TextRun({ text: alt || href, color: "0563C1", underline: {} })]
2443
+ })
2444
+ ]
2445
+ })
2446
+ ];
2447
+ }
2448
+ function collectImages(nodes, out) {
2449
+ for (const node of nodes ?? []) {
2450
+ const src = node.attrs?.src;
2451
+ if (node.type === "image" && typeof src === "string" && src && !out.has(src)) out.set(src, node.attrs);
2452
+ if (node.content) collectImages(node.content, out);
2453
+ }
2454
+ return out;
2455
+ }
2456
+ var IMAGE_TYPES = {
2457
+ "image/png": "png",
2458
+ "image/jpeg": "jpg",
2459
+ "image/jpg": "jpg",
2460
+ "image/gif": "gif",
2461
+ "image/bmp": "bmp"
2462
+ };
2463
+ var MAX_IMAGE_WIDTH = 600;
2464
+ function naturalSize(blob) {
2465
+ const fallback = { width: 400, height: 300 };
2466
+ return new Promise((resolve) => {
2467
+ if (typeof Image === "undefined" || typeof URL.createObjectURL !== "function") return resolve(fallback);
2468
+ const url = URL.createObjectURL(blob);
2469
+ const img = new Image();
2470
+ const done = (size) => {
2471
+ URL.revokeObjectURL(url);
2472
+ resolve(size);
2473
+ };
2474
+ img.onload = () => done({ width: img.naturalWidth || 400, height: img.naturalHeight || 300 });
2475
+ img.onerror = () => done(fallback);
2476
+ img.src = url;
2477
+ });
2478
+ }
2479
+ async function loadImage(src, attrs) {
2480
+ try {
2481
+ const res = await fetch(src);
2482
+ if (!res.ok) return null;
2483
+ const blob = await res.blob();
2484
+ const type = IMAGE_TYPES[blob.type.toLowerCase()];
2485
+ if (!type) return null;
2486
+ const natural = await naturalSize(blob);
2487
+ let width = Number(attrs?.width) || natural.width;
2488
+ let height = Number(attrs?.height) || natural.height * width / natural.width;
2489
+ if (width > MAX_IMAGE_WIDTH) {
2490
+ height = height * MAX_IMAGE_WIDTH / width;
2491
+ width = MAX_IMAGE_WIDTH;
2492
+ }
2493
+ return { data: new Uint8Array(await blob.arrayBuffer()), type, width: Math.round(width), height: Math.round(height) };
2494
+ } catch {
2495
+ return null;
2496
+ }
2497
+ }
2498
+ async function resolveImages(doc) {
2499
+ const map = /* @__PURE__ */ new Map();
2500
+ if (typeof fetch !== "function") return map;
2501
+ const found = collectImages(doc.content, /* @__PURE__ */ new Map());
2502
+ await Promise.all(
2503
+ [...found].map(async ([src, attrs]) => {
2504
+ const info = await loadImage(src, attrs);
2505
+ if (info) map.set(src, info);
2506
+ })
2507
+ );
2508
+ return map;
2509
+ }
1231
2510
  function blocks(d, nodes, fmt, state) {
1232
2511
  const headings = [d.HeadingLevel.HEADING_1, d.HeadingLevel.HEADING_2, d.HeadingLevel.HEADING_3];
1233
2512
  const out = [];
@@ -1262,6 +2541,12 @@ function blocks(d, nodes, fmt, state) {
1262
2541
  case "table":
1263
2542
  out.push(table(d, node, state));
1264
2543
  break;
2544
+ case "pageBreak":
2545
+ out.push(new d.Paragraph({ children: [new d.PageBreak()] }));
2546
+ break;
2547
+ case "image":
2548
+ out.push(...image(d, node, state));
2549
+ break;
1265
2550
  default:
1266
2551
  if (node.content) out.push(...blocks(d, node.content, fmt, state));
1267
2552
  }
@@ -1270,7 +2555,7 @@ function blocks(d, nodes, fmt, state) {
1270
2555
  }
1271
2556
  var exportDocx = async (ctx) => {
1272
2557
  const d = await import("docx");
1273
- const state = { instance: 0 };
2558
+ const state = { instance: 0, images: await resolveImages(ctx.json) };
1274
2559
  const children = [
1275
2560
  new d.Paragraph({ text: ctx.title, heading: d.HeadingLevel.TITLE }),
1276
2561
  ...blocks(d, ctx.json.content, {}, state)
@@ -1321,7 +2606,7 @@ function printHtml(html) {
1321
2606
  });
1322
2607
  }
1323
2608
  var exportPdf = async (ctx) => {
1324
- const { docToHtml: docToHtml2 } = await import("./document-engine-C6ILFMRP.js");
2609
+ const { docToHtml: docToHtml2 } = await import("./document-engine-IAY2T6B3.js");
1325
2610
  await printHtml(buildHtmlDocument(ctx.title, docToHtml2(ctx.json)));
1326
2611
  return { filename: `${ctx.basename}.pdf`, mimeType: "application/pdf", handled: true };
1327
2612
  };
@@ -1359,7 +2644,7 @@ function downloadResult(result) {
1359
2644
  setTimeout(() => URL.revokeObjectURL(url), 1e3);
1360
2645
  }
1361
2646
  async function copyDocumentToClipboard(doc) {
1362
- const { docToHtml: docToHtml2, docToText } = await import("./document-engine-C6ILFMRP.js");
2647
+ const { docToHtml: docToHtml2, docToText } = await import("./document-engine-IAY2T6B3.js");
1363
2648
  const html = docToHtml2(doc.json);
1364
2649
  const text = `${doc.title}
1365
2650
 
@@ -1596,189 +2881,6 @@ function getDefaultDocumentStore() {
1596
2881
  return defaultStore;
1597
2882
  }
1598
2883
 
1599
- // src/components/document-editor/document-title.tsx
1600
- import { jsx as jsx6 } from "react/jsx-runtime";
1601
- function DocumentTitle({ value, disabled, onChange }) {
1602
- return /* @__PURE__ */ jsx6(
1603
- "input",
1604
- {
1605
- className: "june-doc__title",
1606
- "aria-label": "Document title",
1607
- value,
1608
- disabled,
1609
- placeholder: "Untitled document",
1610
- onChange: (e) => onChange(e.target.value)
1611
- }
1612
- );
1613
- }
1614
-
1615
- // src/components/document-editor/document-toolbar.tsx
1616
- import { useEditorState } from "@tiptap/react";
1617
- import { useState as useState2 } from "react";
1618
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1619
- var FORMAT_LABELS = {
1620
- html: "HTML",
1621
- markdown: "Markdown",
1622
- txt: "Plain text",
1623
- docx: "Word (.docx)",
1624
- pdf: "PDF (print)"
1625
- };
1626
- function Btn({ label, active, disabled, toggle, onClick, children }) {
1627
- return /* @__PURE__ */ jsx7(
1628
- "button",
1629
- {
1630
- type: "button",
1631
- className: "june-doc__btn",
1632
- "aria-label": label,
1633
- title: label,
1634
- "aria-pressed": toggle ? !!active : void 0,
1635
- "data-active": active || void 0,
1636
- disabled,
1637
- onMouseDown: (e) => e.preventDefault(),
1638
- onClick,
1639
- children
1640
- }
1641
- );
1642
- }
1643
- function DocumentToolbar(props) {
1644
- const { editor, editable, exportFormats, canSave, saveState, onExport, onCopy, onSave } = props;
1645
- const [linkOpen, setLinkOpen] = useState2(false);
1646
- const [linkValue, setLinkValue] = useState2("");
1647
- const s = useEditorState({
1648
- editor,
1649
- selector: ({ editor: ed }) => ed ? {
1650
- bold: ed.isActive("bold"),
1651
- italic: ed.isActive("italic"),
1652
- underline: ed.isActive("underline"),
1653
- strike: ed.isActive("strike"),
1654
- bullet: ed.isActive("bulletList"),
1655
- ordered: ed.isActive("orderedList"),
1656
- quote: ed.isActive("blockquote"),
1657
- link: ed.isActive("link"),
1658
- linkHref: ed.getAttributes("link").href ?? "",
1659
- h1: ed.isActive("heading", { level: 1 }),
1660
- h2: ed.isActive("heading", { level: 2 }),
1661
- h3: ed.isActive("heading", { level: 3 }),
1662
- left: ed.isActive({ textAlign: "left" }),
1663
- center: ed.isActive({ textAlign: "center" }),
1664
- right: ed.isActive({ textAlign: "right" }),
1665
- justify: ed.isActive({ textAlign: "justify" }),
1666
- inTable: ed.isActive("table"),
1667
- canUndo: ed.can().undo(),
1668
- canRedo: ed.can().redo()
1669
- } : null
1670
- });
1671
- const run = () => editor?.chain().focus();
1672
- const off = !editor || !editable;
1673
- const heading = s?.h1 ? "1" : s?.h2 ? "2" : s?.h3 ? "3" : "p";
1674
- const openLink = () => {
1675
- setLinkValue(s?.linkHref ?? "");
1676
- setLinkOpen(true);
1677
- };
1678
- const applyLink = () => {
1679
- const href = sanitizeHref(linkValue);
1680
- if (href) run()?.extendMarkRange("link").setLink({ href }).run();
1681
- setLinkOpen(false);
1682
- };
1683
- const removeLink = () => {
1684
- run()?.extendMarkRange("link").unsetLink().run();
1685
- setLinkOpen(false);
1686
- };
1687
- return /* @__PURE__ */ jsxs3("div", { className: "june-doc__toolbar", role: "toolbar", "aria-label": "Document formatting", children: [
1688
- editable ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1689
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1690
- /* @__PURE__ */ jsx7(Btn, { label: "Undo", disabled: off || !s?.canUndo, onClick: () => run()?.undo().run(), children: "\u21B6" }),
1691
- /* @__PURE__ */ jsx7(Btn, { label: "Redo", disabled: off || !s?.canRedo, onClick: () => run()?.redo().run(), children: "\u21B7" })
1692
- ] }),
1693
- /* @__PURE__ */ jsx7("div", { className: "june-doc__group", children: /* @__PURE__ */ jsxs3(
1694
- "select",
1695
- {
1696
- className: "june-doc__select",
1697
- "aria-label": "Text style",
1698
- disabled: off,
1699
- value: heading,
1700
- onChange: (e) => {
1701
- const v = e.target.value;
1702
- if (v === "p") run()?.setParagraph().run();
1703
- else run()?.toggleHeading({ level: Number(v) }).run();
1704
- },
1705
- children: [
1706
- /* @__PURE__ */ jsx7("option", { value: "p", children: "Paragraph" }),
1707
- /* @__PURE__ */ jsx7("option", { value: "1", children: "Heading 1" }),
1708
- /* @__PURE__ */ jsx7("option", { value: "2", children: "Heading 2" }),
1709
- /* @__PURE__ */ jsx7("option", { value: "3", children: "Heading 3" })
1710
- ]
1711
- }
1712
- ) }),
1713
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1714
- /* @__PURE__ */ jsx7(Btn, { label: "Bold", toggle: true, active: s?.bold, disabled: off, onClick: () => run()?.toggleBold().run(), children: /* @__PURE__ */ jsx7("b", { children: "B" }) }),
1715
- /* @__PURE__ */ jsx7(Btn, { label: "Italic", toggle: true, active: s?.italic, disabled: off, onClick: () => run()?.toggleItalic().run(), children: /* @__PURE__ */ jsx7("i", { children: "I" }) }),
1716
- /* @__PURE__ */ jsx7(Btn, { label: "Underline", toggle: true, active: s?.underline, disabled: off, onClick: () => run()?.toggleUnderline().run(), children: /* @__PURE__ */ jsx7("u", { children: "U" }) }),
1717
- /* @__PURE__ */ jsx7(Btn, { label: "Strikethrough", toggle: true, active: s?.strike, disabled: off, onClick: () => run()?.toggleStrike().run(), children: /* @__PURE__ */ jsx7("s", { children: "S" }) })
1718
- ] }),
1719
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1720
- /* @__PURE__ */ jsx7(Btn, { label: "Bullet list", toggle: true, active: s?.bullet, disabled: off, onClick: () => run()?.toggleBulletList().run(), children: "\u2022 List" }),
1721
- /* @__PURE__ */ jsx7(Btn, { label: "Numbered list", toggle: true, active: s?.ordered, disabled: off, onClick: () => run()?.toggleOrderedList().run(), children: "1. List" }),
1722
- /* @__PURE__ */ jsx7(Btn, { label: "Blockquote", toggle: true, active: s?.quote, disabled: off, onClick: () => run()?.toggleBlockquote().run(), children: "Quote" }),
1723
- /* @__PURE__ */ jsx7(Btn, { label: "Link", toggle: true, active: s?.link, disabled: off, onClick: openLink, children: "Link" })
1724
- ] }),
1725
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1726
- /* @__PURE__ */ jsx7(Btn, { label: "Align left", toggle: true, active: s?.left, disabled: off, onClick: () => run()?.setTextAlign("left").run(), children: "L" }),
1727
- /* @__PURE__ */ jsx7(Btn, { label: "Align center", toggle: true, active: s?.center, disabled: off, onClick: () => run()?.setTextAlign("center").run(), children: "C" }),
1728
- /* @__PURE__ */ jsx7(Btn, { label: "Align right", toggle: true, active: s?.right, disabled: off, onClick: () => run()?.setTextAlign("right").run(), children: "R" }),
1729
- /* @__PURE__ */ jsx7(Btn, { label: "Justify", toggle: true, active: s?.justify, disabled: off, onClick: () => run()?.setTextAlign("justify").run(), children: "J" })
1730
- ] }),
1731
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1732
- /* @__PURE__ */ jsx7(Btn, { label: "Insert table", disabled: off, onClick: () => run()?.insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), children: "Table" }),
1733
- s?.inTable ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1734
- /* @__PURE__ */ jsx7(Btn, { label: "Add row below", disabled: off, onClick: () => run()?.addRowAfter().run(), children: "+Row" }),
1735
- /* @__PURE__ */ jsx7(Btn, { label: "Add column right", disabled: off, onClick: () => run()?.addColumnAfter().run(), children: "+Col" }),
1736
- /* @__PURE__ */ jsx7(Btn, { label: "Delete row", disabled: off, onClick: () => run()?.deleteRow().run(), children: "\u2212Row" }),
1737
- /* @__PURE__ */ jsx7(Btn, { label: "Delete column", disabled: off, onClick: () => run()?.deleteColumn().run(), children: "\u2212Col" }),
1738
- /* @__PURE__ */ jsx7(Btn, { label: "Delete table", disabled: off, onClick: () => run()?.deleteTable().run(), children: "Del table" })
1739
- ] }) : null
1740
- ] })
1741
- ] }) : null,
1742
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group june-doc__group--end", children: [
1743
- /* @__PURE__ */ jsx7(Btn, { label: "Copy document", onClick: onCopy, children: "Copy" }),
1744
- exportFormats.length > 0 ? /* @__PURE__ */ jsxs3(
1745
- "select",
1746
- {
1747
- className: "june-doc__select",
1748
- "aria-label": "Export document",
1749
- value: "",
1750
- onChange: (e) => e.target.value && onExport(e.target.value),
1751
- children: [
1752
- /* @__PURE__ */ jsx7("option", { value: "", children: "Export\u2026" }),
1753
- exportFormats.map((f) => /* @__PURE__ */ jsx7("option", { value: f, children: FORMAT_LABELS[f] ?? f }, f))
1754
- ]
1755
- }
1756
- ) : null,
1757
- canSave ? /* @__PURE__ */ jsx7(Btn, { label: "Save", disabled: saveState === "saving" || saveState === "saved" || saveState === "idle", onClick: onSave, children: "Save" }) : null
1758
- ] }),
1759
- linkOpen ? /* @__PURE__ */ jsxs3("div", { className: "june-doc__linkbar", children: [
1760
- /* @__PURE__ */ jsx7(
1761
- "input",
1762
- {
1763
- "aria-label": "Link address",
1764
- className: "june-doc__input",
1765
- placeholder: "https://example.com",
1766
- value: linkValue,
1767
- autoFocus: true,
1768
- onChange: (e) => setLinkValue(e.target.value),
1769
- onKeyDown: (e) => {
1770
- if (e.key === "Enter") applyLink();
1771
- if (e.key === "Escape") setLinkOpen(false);
1772
- }
1773
- }
1774
- ),
1775
- /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: applyLink, children: "Apply" }),
1776
- s?.link ? /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: removeLink, children: "Remove" }) : null,
1777
- /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: () => setLinkOpen(false), children: "Cancel" })
1778
- ] }) : null
1779
- ] });
1780
- }
1781
-
1782
2884
  // src/components/document-editor/document-types.ts
1783
2885
  var ALL_EXPORT_FORMATS = [
1784
2886
  "html",
@@ -1789,7 +2891,7 @@ var ALL_EXPORT_FORMATS = [
1789
2891
  ];
1790
2892
 
1791
2893
  // src/components/document-editor/document-editor.tsx
1792
- import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
2894
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1793
2895
  var snapshotOf = (r) => ({
1794
2896
  documentId: r.id,
1795
2897
  title: r.title,
@@ -1804,6 +2906,8 @@ var SAVE_LABEL = {
1804
2906
  saved: "Saved",
1805
2907
  error: "Save failed"
1806
2908
  };
2909
+ var ZOOM_STEPS = [0.5, 0.67, 0.75, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2];
2910
+ var stepZoom = (z2, dir) => dir > 0 ? ZOOM_STEPS.find((s) => s > z2 + 1e-3) ?? z2 : [...ZOOM_STEPS].reverse().find((s) => s < z2 - 1e-3) ?? z2;
1807
2911
  function DocumentEditorImpl(props) {
1808
2912
  const {
1809
2913
  documentId,
@@ -1818,7 +2922,7 @@ function DocumentEditorImpl(props) {
1818
2922
  } = props;
1819
2923
  const ctxOptions = useDocumentOptions();
1820
2924
  const options = useMemo4(() => ({ ...ctxOptions, ...props.options }), [ctxOptions, props.options]);
1821
- const optionsRef = useRef3(options);
2925
+ const optionsRef = useRef7(options);
1822
2926
  optionsRef.current = options;
1823
2927
  const store = useMemo4(() => options.store ?? getDefaultDocumentStore(), [options.store]);
1824
2928
  const instanceId = useId();
@@ -1833,22 +2937,37 @@ function DocumentEditorImpl(props) {
1833
2937
  return { json: null, error: toJuneError(e, "DOCUMENT_ERROR") };
1834
2938
  }
1835
2939
  }, [contentKey]);
1836
- useEffect2(() => {
2940
+ useEffect5(() => {
1837
2941
  if (parsed.error) optionsRef.current.onError?.(parsed.error);
1838
2942
  }, [parsed.error]);
1839
2943
  const title = useDocumentValue(store, documentId, (r) => r?.title ?? titleProp);
1840
2944
  const pending = useDocumentValue(store, documentId, (r) => r?.pending ?? null);
1841
2945
  const words = useDocumentValue(store, documentId, (r) => r ? wordCountOf(r.json) : 0);
1842
2946
  const recordStatus = useDocumentValue(store, documentId, (r) => r?.status ?? status);
1843
- const [saveState, setSaveState] = useState3("idle");
1844
- const [notice, setNotice] = useState3(null);
1845
- const lastSavedKey = useRef3("");
1846
- const seenRevision = useRef3(store.get(documentId)?.revision ?? 0);
1847
- const readyRef = useRef3(false);
2947
+ const [saveState, setSaveState] = useState6("idle");
2948
+ const [notice, setNotice] = useState6(null);
2949
+ const [fullscreen, setFullscreen] = useState6(false);
2950
+ const [expanded, setExpanded] = useState6(true);
2951
+ const [showMarks, setShowMarks] = useState6(false);
2952
+ const [layout, setLayout] = useState6("page");
2953
+ const [findOpen, setFindOpen] = useState6(false);
2954
+ const [zoom, setZoom] = useState6(1);
2955
+ const [metrics, setMetrics] = useState6({ page: 1, pages: 1 });
2956
+ useEffect5(() => {
2957
+ if (!fullscreen || typeof document === "undefined") return;
2958
+ const prev = document.body.style.overflow;
2959
+ document.body.style.overflow = "hidden";
2960
+ return () => {
2961
+ document.body.style.overflow = prev;
2962
+ };
2963
+ }, [fullscreen]);
2964
+ const lastSavedKey = useRef7("");
2965
+ const seenRevision = useRef7(store.get(documentId)?.revision ?? 0);
2966
+ const readyRef = useRef7(false);
1848
2967
  const flash = useCallback3((message) => {
1849
2968
  setNotice(message);
1850
2969
  }, []);
1851
- useEffect2(() => {
2970
+ useEffect5(() => {
1852
2971
  if (!notice) return;
1853
2972
  const t = setTimeout(() => setNotice(null), 3e3);
1854
2973
  return () => clearTimeout(t);
@@ -1860,13 +2979,13 @@ function DocumentEditorImpl(props) {
1860
2979
  },
1861
2980
  [store, documentId]
1862
2981
  );
1863
- const saveRef = useRef3(async () => {
2982
+ const saveRef = useRef7(async () => {
1864
2983
  });
1865
2984
  const autosave = useMemo4(() => {
1866
2985
  const delay = options.autosaveDelay ?? 1500;
1867
2986
  return delay > 0 ? debounce(() => void saveRef.current(), delay) : null;
1868
2987
  }, [options.autosaveDelay]);
1869
- useEffect2(() => () => autosave?.flush(), [autosave]);
2988
+ useEffect5(() => () => autosave?.flush(), [autosave]);
1870
2989
  saveRef.current = async () => {
1871
2990
  const record = store.get(documentId);
1872
2991
  const onSave = optionsRef.current.onSave;
@@ -1897,9 +3016,9 @@ function DocumentEditorImpl(props) {
1897
3016
  setSaveState("dirty");
1898
3017
  autosave?.();
1899
3018
  }, [autosave]);
1900
- const [initialJson] = useState3(() => store.get(documentId)?.json ?? parsed.json ?? emptyDoc());
3019
+ const [initialJson] = useState6(() => store.get(documentId)?.json ?? parsed.json ?? emptyDoc());
1901
3020
  const extensions = useMemo4(() => createDocumentExtensions(), []);
1902
- const onUpdateRef = useRef3(() => {
3021
+ const onUpdateRef = useRef7(() => {
1903
3022
  });
1904
3023
  onUpdateRef.current = (json) => {
1905
3024
  store.edit(documentId, json, instanceId);
@@ -1921,10 +3040,10 @@ function DocumentEditorImpl(props) {
1921
3040
  },
1922
3041
  onUpdate: ({ editor: ed }) => onUpdateRef.current(ed.getJSON())
1923
3042
  });
1924
- useEffect2(() => {
3043
+ useEffect5(() => {
1925
3044
  editor?.setEditable(editable);
1926
3045
  }, [editor, editable]);
1927
- useEffect2(() => {
3046
+ useEffect5(() => {
1928
3047
  if (!parsed.json) return;
1929
3048
  const outcome = store.receive({
1930
3049
  id: documentId,
@@ -1945,7 +3064,7 @@ function DocumentEditorImpl(props) {
1945
3064
  });
1946
3065
  }
1947
3066
  }, [store, documentId, titleProp, parsed.json, documentType, status, emitChange, markDirty]);
1948
- useEffect2(() => {
3067
+ useEffect5(() => {
1949
3068
  if (!editor) return;
1950
3069
  const apply = () => {
1951
3070
  const record = store.get(documentId);
@@ -1962,7 +3081,7 @@ function DocumentEditorImpl(props) {
1962
3081
  apply();
1963
3082
  return store.subscribe(apply);
1964
3083
  }, [editor, store, documentId, instanceId]);
1965
- useEffect2(() => {
3084
+ useEffect5(() => {
1966
3085
  if (!editor || readyRef.current) return;
1967
3086
  const record = store.get(documentId);
1968
3087
  if (!record) return;
@@ -2006,67 +3125,114 @@ function DocumentEditorImpl(props) {
2006
3125
  };
2007
3126
  const formats = (exportFormats ?? [...ALL_EXPORT_FORMATS]).filter((f) => listDocumentExporters().includes(f));
2008
3127
  if (parsed.error && !store.get(documentId)) {
2009
- return /* @__PURE__ */ jsxs4("div", { className: "june-doc june-doc--error", "data-theme": resolvedTheme, role: "alert", children: [
2010
- /* @__PURE__ */ jsx8("p", { className: "june-doc__draft-text", children: "This document could not be displayed." }),
2011
- /* @__PURE__ */ jsxs4("details", { children: [
2012
- /* @__PURE__ */ jsx8("summary", { children: "Show raw content" }),
2013
- /* @__PURE__ */ jsx8("pre", { className: "june-doc__raw", children: contentKey })
3128
+ return /* @__PURE__ */ jsxs9("div", { className: "june-doc june-doc--error", "data-theme": resolvedTheme, role: "alert", children: [
3129
+ /* @__PURE__ */ jsx13("p", { className: "june-doc__draft-text", children: "This document could not be displayed." }),
3130
+ /* @__PURE__ */ jsxs9("details", { children: [
3131
+ /* @__PURE__ */ jsx13("summary", { children: "Show raw content" }),
3132
+ /* @__PURE__ */ jsx13("pre", { className: "june-doc__raw", children: contentKey })
2014
3133
  ] })
2015
3134
  ] });
2016
3135
  }
2017
- if (!editor) return /* @__PURE__ */ jsx8(DocumentDraft, { title, state: "loading" });
3136
+ if (!editor) return /* @__PURE__ */ jsx13(DocumentDraft, { title, state: "loading" });
2018
3137
  const isDraft = recordStatus === "draft";
2019
- return /* @__PURE__ */ jsxs4(
3138
+ const classes = ["june-doc", fullscreen ? "june-doc--fullscreen" : "", className ?? ""].filter(Boolean).join(" ");
3139
+ return /* @__PURE__ */ jsxs9(
2020
3140
  "div",
2021
3141
  {
2022
- className: className ? `june-doc ${className}` : "june-doc",
3142
+ className: classes,
2023
3143
  "data-theme": resolvedTheme,
2024
3144
  "data-status": recordStatus,
3145
+ role: fullscreen ? "dialog" : void 0,
3146
+ "aria-modal": fullscreen || void 0,
3147
+ "aria-label": fullscreen ? title : void 0,
2025
3148
  onKeyDown: (e) => {
2026
- if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s" && options.onSave) {
3149
+ const mod = e.ctrlKey || e.metaKey;
3150
+ if (mod && e.key.toLowerCase() === "s" && options.onSave) {
2027
3151
  e.preventDefault();
2028
3152
  void saveRef.current();
3153
+ } else if (mod && e.key.toLowerCase() === "f") {
3154
+ e.preventDefault();
3155
+ setFindOpen(true);
3156
+ } else if (e.key === "Escape" && fullscreen && !e.defaultPrevented) {
3157
+ setFullscreen(false);
2029
3158
  }
2030
3159
  },
2031
3160
  children: [
2032
- /* @__PURE__ */ jsxs4("div", { className: "june-doc__header", children: [
2033
- /* @__PURE__ */ jsx8(DocumentTitle, { value: title, disabled: !editable, onChange: handleTitle }),
2034
- isDraft ? /* @__PURE__ */ jsx8("span", { className: "june-doc__badge", children: "AI draft" }) : null
2035
- ] }),
2036
- pending ? /* @__PURE__ */ jsxs4("div", { className: "june-doc__banner", role: "status", children: [
2037
- /* @__PURE__ */ jsxs4("span", { children: [
3161
+ /* @__PURE__ */ jsx13(
3162
+ DocumentHeader,
3163
+ {
3164
+ title,
3165
+ editable,
3166
+ isDraft,
3167
+ fullscreen,
3168
+ onToggleFullscreen: () => setFullscreen((v) => !v),
3169
+ onTitle: handleTitle,
3170
+ exportFormats: formats,
3171
+ canSave: !!options.onSave,
3172
+ saveState,
3173
+ onExport: (f) => void handleExport(f),
3174
+ onCopy: () => void handleCopy(),
3175
+ onSave: () => void saveRef.current()
3176
+ }
3177
+ ),
3178
+ pending ? /* @__PURE__ */ jsxs9("div", { className: "june-doc__banner", role: "status", children: [
3179
+ /* @__PURE__ */ jsxs9("span", { children: [
2038
3180
  "The assistant suggested changes that overlap your edits (",
2039
3181
  pending.conflicts,
2040
3182
  " ",
2041
3183
  pending.conflicts === 1 ? "section" : "sections",
2042
3184
  ")."
2043
3185
  ] }),
2044
- /* @__PURE__ */ jsxs4("span", { className: "june-doc__banner-actions", children: [
2045
- /* @__PURE__ */ jsx8("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("merge"), children: "Merge, keep my edits" }),
2046
- /* @__PURE__ */ jsx8("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("replace"), children: "Use AI version" }),
2047
- /* @__PURE__ */ jsx8("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("keep"), children: "Keep mine" })
3186
+ /* @__PURE__ */ jsxs9("span", { className: "june-doc__banner-actions", children: [
3187
+ /* @__PURE__ */ jsx13("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("merge"), children: "Merge, keep my edits" }),
3188
+ /* @__PURE__ */ jsx13("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("replace"), children: "Use AI version" }),
3189
+ /* @__PURE__ */ jsx13("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("keep"), children: "Keep mine" })
2048
3190
  ] })
2049
3191
  ] }) : null,
2050
- /* @__PURE__ */ jsx8(
3192
+ /* @__PURE__ */ jsx13(
2051
3193
  DocumentToolbar,
2052
3194
  {
2053
3195
  editor,
2054
3196
  editable,
2055
- exportFormats: formats,
2056
- canSave: !!options.onSave,
2057
- saveState,
2058
- onExport: (f) => void handleExport(f),
2059
- onCopy: () => void handleCopy(),
2060
- onSave: () => void saveRef.current()
3197
+ expanded,
3198
+ onToggleExpanded: () => setExpanded((v) => !v),
3199
+ showMarks,
3200
+ onToggleMarks: () => setShowMarks((v) => !v),
3201
+ layout,
3202
+ onToggleLayout: () => setLayout((v) => v === "page" ? "fluid" : "page"),
3203
+ findOpen,
3204
+ onToggleFind: () => setFindOpen((v) => !v),
3205
+ onNotice: flash
2061
3206
  }
2062
3207
  ),
2063
- /* @__PURE__ */ jsx8(DocumentContent, { editor }),
2064
- /* @__PURE__ */ jsxs4("div", { className: "june-doc__footer", "aria-live": "polite", children: [
2065
- /* @__PURE__ */ jsxs4("span", { children: [
2066
- words,
2067
- " words"
3208
+ findOpen ? /* @__PURE__ */ jsx13(DocumentFindBar, { editor, editable, onClose: () => setFindOpen(false), onNotice: flash }) : null,
3209
+ editable ? /* @__PURE__ */ jsx13(DocumentBubbleMenu, { editor }) : null,
3210
+ /* @__PURE__ */ jsx13(DocumentContent, { editor, layout, zoom, showMarks, onMetrics: setMetrics }),
3211
+ /* @__PURE__ */ jsxs9("div", { className: "june-doc__footer", children: [
3212
+ /* @__PURE__ */ jsxs9("div", { className: "june-doc__stats", children: [
3213
+ layout === "page" ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
3214
+ /* @__PURE__ */ jsxs9("span", { children: [
3215
+ "Page ",
3216
+ metrics.page,
3217
+ " of ",
3218
+ metrics.pages
3219
+ ] }),
3220
+ /* @__PURE__ */ jsx13("span", { className: "june-doc__stats-sep", "aria-hidden": "true" })
3221
+ ] }) : null,
3222
+ /* @__PURE__ */ jsxs9("span", { children: [
3223
+ words,
3224
+ " words"
3225
+ ] })
2068
3226
  ] }),
2069
- /* @__PURE__ */ jsx8("span", { children: notice ?? SAVE_LABEL[saveState] })
3227
+ /* @__PURE__ */ jsx13("span", { className: "june-doc__status", "aria-live": "polite", "data-state": notice ? "notice" : saveState, children: notice ?? SAVE_LABEL[saveState] }),
3228
+ /* @__PURE__ */ jsxs9("div", { className: "june-doc__zoom", role: "group", "aria-label": "Zoom", children: [
3229
+ /* @__PURE__ */ jsx13("button", { type: "button", className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost", "aria-label": "Zoom out", disabled: zoom <= ZOOM_STEPS[0], onClick: () => setZoom(stepZoom(zoom, -1)), children: /* @__PURE__ */ jsx13(Icon, { name: "minus", size: 14 }) }),
3230
+ /* @__PURE__ */ jsxs9("button", { type: "button", className: "june-doc__zoom-value", "aria-label": "Reset zoom", title: "Reset zoom", onClick: () => setZoom(1), children: [
3231
+ Math.round(zoom * 100),
3232
+ "%"
3233
+ ] }),
3234
+ /* @__PURE__ */ jsx13("button", { type: "button", className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost", "aria-label": "Zoom in", disabled: zoom >= ZOOM_STEPS[ZOOM_STEPS.length - 1], onClick: () => setZoom(stepZoom(zoom, 1)), children: /* @__PURE__ */ jsx13(Icon, { name: "plus", size: 14 }) })
3235
+ ] })
2070
3236
  ] })
2071
3237
  ]
2072
3238
  }
@@ -2074,7 +3240,7 @@ function DocumentEditorImpl(props) {
2074
3240
  }
2075
3241
 
2076
3242
  // src/components/document-editor/document-component.tsx
2077
- import { jsx as jsx9 } from "react/jsx-runtime";
3243
+ import { jsx as jsx14 } from "react/jsx-runtime";
2078
3244
  var DOCUMENT_COMPONENT_DESCRIPTION = 'Custom component. An editable document. DocumentEditor(documentId, title, content, documentType?, status?, editable?, exportFormats?). Place it in the root container (Card or Stack). "content" is the full document body as one Markdown string. Reuse documentId when revising the same document. Use it only when the user asks to create or revise a document.';
2079
3245
  var documentProps = z.object({
2080
3246
  documentId: z.string().describe("Stable id. Reuse it when revising the same document."),
@@ -2088,12 +3254,12 @@ var documentProps = z.object({
2088
3254
  function DocumentEditor(props) {
2089
3255
  const { isStreaming } = useJuneRuntime();
2090
3256
  if (props.generating || isStreaming) {
2091
- return /* @__PURE__ */ jsx9(DocumentDraft, { title: props.title, state: "drafting" });
3257
+ return /* @__PURE__ */ jsx14(DocumentDraft, { title: props.title, state: "drafting" });
2092
3258
  }
2093
- return /* @__PURE__ */ jsx9(DocumentEditorImpl, { ...props });
3259
+ return /* @__PURE__ */ jsx14(DocumentEditorImpl, { ...props });
2094
3260
  }
2095
3261
  function OpenUIDocument({ props }) {
2096
- return /* @__PURE__ */ jsx9(
3262
+ return /* @__PURE__ */ jsx14(
2097
3263
  DocumentEditor,
2098
3264
  {
2099
3265
  documentId: props.documentId,
@@ -2113,11 +3279,15 @@ var JuneDocumentEditorComponent = defineComponent2({
2113
3279
  component: OpenUIDocument
2114
3280
  });
2115
3281
  function withDocumentEditor(library) {
2116
- return library.extend({
2117
- components: {
2118
- add: [{ component: JuneDocumentEditorComponent, slots: ["Card"] }]
2119
- }
2120
- });
3282
+ try {
3283
+ return library.extend({
3284
+ components: {
3285
+ add: [{ component: JuneDocumentEditorComponent, slots: ["Card"] }]
3286
+ }
3287
+ });
3288
+ } catch {
3289
+ return library.extend({ components: { add: [JuneDocumentEditorComponent] } });
3290
+ }
2121
3291
  }
2122
3292
 
2123
3293
  // src/components/document-editor/document-instructions.ts
@@ -2164,7 +3334,7 @@ function documentPromptOptions(options = {}) {
2164
3334
  var attr = (value) => value.replace(/"/g, "&quot;");
2165
3335
  async function serializeDocumentsForAgent(options = {}) {
2166
3336
  const store = options.store ?? getDefaultDocumentStore();
2167
- const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-C6ILFMRP.js");
3337
+ const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-IAY2T6B3.js");
2168
3338
  return store.list().filter((r) => !options.ids || options.ids.includes(r.id)).map(
2169
3339
  (r) => `<document id="${attr(r.id)}" title="${attr(r.title)}"${r.documentType ? ` type="${attr(r.documentType)}"` : ""}>
2170
3340
  ${docToMarkdown2(r.json)}
@@ -2177,9 +3347,9 @@ import { openuiLibrary } from "@openuidev/react-ui";
2177
3347
  var juneLibrary = withDocumentEditor(openuiLibrary);
2178
3348
 
2179
3349
  // src/core/JuneUI.tsx
2180
- import { jsx as jsx10 } from "react/jsx-runtime";
3350
+ import { jsx as jsx15 } from "react/jsx-runtime";
2181
3351
  function JuneUI({ library = juneLibrary, ...props }) {
2182
- return /* @__PURE__ */ jsx10(JuneRenderer, { library, ...props });
3352
+ return /* @__PURE__ */ jsx15(JuneRenderer, { library, ...props });
2183
3353
  }
2184
3354
  export {
2185
3355
  ALL_EXPORT_FORMATS,