@june-ai/june-ui 0.2.3 → 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";
@@ -203,6 +208,7 @@ var ATTACHABLE = /* @__PURE__ */ new Set([
203
208
  "CodeBlock",
204
209
  "CompositeCardBlock",
205
210
  "ContextCardBlock",
211
+ "DocumentEditor",
206
212
  "EditableTable",
207
213
  "EntityList",
208
214
  "Form",
@@ -1089,18 +1095,1164 @@ function DocumentDraft({ title, words, state }) {
1089
1095
 
1090
1096
  // src/components/document-editor/document-editor.tsx
1091
1097
  import { useEditor } from "@tiptap/react";
1092
- 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
+ }
1093
1952
 
1094
1953
  // src/components/document-editor/document-content.tsx
1095
1954
  import { EditorContent } from "@tiptap/react";
1096
- import { jsx as jsx5 } from "react/jsx-runtime";
1097
- function DocumentContent({ editor }) {
1098
- 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
+ ] });
1099
2251
  }
1100
2252
 
1101
2253
  // src/components/document-editor/document-export.ts
1102
2254
  var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1103
- 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}}';
1104
2256
  function buildHtmlDocument(title, bodyHtml) {
1105
2257
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${escapeHtml(
1106
2258
  title
@@ -1113,7 +2265,7 @@ var textResult = (text, filename, mimeType) => ({
1113
2265
  blob: new Blob([text], { type: `${mimeType};charset=utf-8` })
1114
2266
  });
1115
2267
  var exportHtml = async (ctx) => {
1116
- const { docToHtml: docToHtml2 } = await import("./document-engine-C6ILFMRP.js");
2268
+ const { docToHtml: docToHtml2 } = await import("./document-engine-IAY2T6B3.js");
1117
2269
  return textResult(
1118
2270
  buildHtmlDocument(ctx.title, docToHtml2(ctx.json)),
1119
2271
  `${ctx.basename}.html`,
@@ -1121,7 +2273,7 @@ var exportHtml = async (ctx) => {
1121
2273
  );
1122
2274
  };
1123
2275
  var exportMarkdown = async (ctx) => {
1124
- const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-C6ILFMRP.js");
2276
+ const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-IAY2T6B3.js");
1125
2277
  return textResult(
1126
2278
  `# ${ctx.title}
1127
2279
 
@@ -1132,12 +2284,39 @@ ${docToMarkdown2(ctx.json)}
1132
2284
  );
1133
2285
  };
1134
2286
  var exportTxt = async (ctx) => {
1135
- const { docToText } = await import("./document-engine-C6ILFMRP.js");
2287
+ const { docToText } = await import("./document-engine-IAY2T6B3.js");
1136
2288
  return textResult(`${ctx.title}
1137
2289
 
1138
2290
  ${docToText(ctx.json)}
1139
2291
  `, `${ctx.basename}.txt`, "text/plain");
1140
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
+ }
1141
2320
  var ORDERED_REF = "june-ordered";
1142
2321
  var DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
1143
2322
  function runs(d, node, fmt) {
@@ -1151,14 +2330,21 @@ function runs(d, node, fmt) {
1151
2330
  const marks = child.marks ?? [];
1152
2331
  const has = (type) => marks.some((m) => m.type === type);
1153
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;
1154
2336
  const run = new d.TextRun({
1155
2337
  text: child.text,
1156
2338
  bold: fmt.bold || has("bold") || void 0,
1157
2339
  italics: has("italic") || void 0,
1158
2340
  underline: has("underline") || link ? {} : void 0,
1159
2341
  strike: has("strike") || void 0,
1160
- font: has("code") ? "Courier New" : void 0,
1161
- 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
1162
2348
  });
1163
2349
  const href = typeof link?.attrs?.href === "string" ? sanitizeHref(link.attrs.href) : null;
1164
2350
  out.push(href ? new d.ExternalHyperlink({ link: href, children: [run] }) : run);
@@ -1227,6 +2413,100 @@ function table(d, node, state) {
1227
2413
  if (rows.length === 0) return new d.Paragraph("");
1228
2414
  return new d.Table({ rows, width: { size: 100, type: d.WidthType.PERCENTAGE } });
1229
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
+ }
1230
2510
  function blocks(d, nodes, fmt, state) {
1231
2511
  const headings = [d.HeadingLevel.HEADING_1, d.HeadingLevel.HEADING_2, d.HeadingLevel.HEADING_3];
1232
2512
  const out = [];
@@ -1261,6 +2541,12 @@ function blocks(d, nodes, fmt, state) {
1261
2541
  case "table":
1262
2542
  out.push(table(d, node, state));
1263
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;
1264
2550
  default:
1265
2551
  if (node.content) out.push(...blocks(d, node.content, fmt, state));
1266
2552
  }
@@ -1269,7 +2555,7 @@ function blocks(d, nodes, fmt, state) {
1269
2555
  }
1270
2556
  var exportDocx = async (ctx) => {
1271
2557
  const d = await import("docx");
1272
- const state = { instance: 0 };
2558
+ const state = { instance: 0, images: await resolveImages(ctx.json) };
1273
2559
  const children = [
1274
2560
  new d.Paragraph({ text: ctx.title, heading: d.HeadingLevel.TITLE }),
1275
2561
  ...blocks(d, ctx.json.content, {}, state)
@@ -1320,7 +2606,7 @@ function printHtml(html) {
1320
2606
  });
1321
2607
  }
1322
2608
  var exportPdf = async (ctx) => {
1323
- const { docToHtml: docToHtml2 } = await import("./document-engine-C6ILFMRP.js");
2609
+ const { docToHtml: docToHtml2 } = await import("./document-engine-IAY2T6B3.js");
1324
2610
  await printHtml(buildHtmlDocument(ctx.title, docToHtml2(ctx.json)));
1325
2611
  return { filename: `${ctx.basename}.pdf`, mimeType: "application/pdf", handled: true };
1326
2612
  };
@@ -1358,7 +2644,7 @@ function downloadResult(result) {
1358
2644
  setTimeout(() => URL.revokeObjectURL(url), 1e3);
1359
2645
  }
1360
2646
  async function copyDocumentToClipboard(doc) {
1361
- const { docToHtml: docToHtml2, docToText } = await import("./document-engine-C6ILFMRP.js");
2647
+ const { docToHtml: docToHtml2, docToText } = await import("./document-engine-IAY2T6B3.js");
1362
2648
  const html = docToHtml2(doc.json);
1363
2649
  const text = `${doc.title}
1364
2650
 
@@ -1595,189 +2881,6 @@ function getDefaultDocumentStore() {
1595
2881
  return defaultStore;
1596
2882
  }
1597
2883
 
1598
- // src/components/document-editor/document-title.tsx
1599
- import { jsx as jsx6 } from "react/jsx-runtime";
1600
- function DocumentTitle({ value, disabled, onChange }) {
1601
- return /* @__PURE__ */ jsx6(
1602
- "input",
1603
- {
1604
- className: "june-doc__title",
1605
- "aria-label": "Document title",
1606
- value,
1607
- disabled,
1608
- placeholder: "Untitled document",
1609
- onChange: (e) => onChange(e.target.value)
1610
- }
1611
- );
1612
- }
1613
-
1614
- // src/components/document-editor/document-toolbar.tsx
1615
- import { useEditorState } from "@tiptap/react";
1616
- import { useState as useState2 } from "react";
1617
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1618
- var FORMAT_LABELS = {
1619
- html: "HTML",
1620
- markdown: "Markdown",
1621
- txt: "Plain text",
1622
- docx: "Word (.docx)",
1623
- pdf: "PDF (print)"
1624
- };
1625
- function Btn({ label, active, disabled, toggle, onClick, children }) {
1626
- return /* @__PURE__ */ jsx7(
1627
- "button",
1628
- {
1629
- type: "button",
1630
- className: "june-doc__btn",
1631
- "aria-label": label,
1632
- title: label,
1633
- "aria-pressed": toggle ? !!active : void 0,
1634
- "data-active": active || void 0,
1635
- disabled,
1636
- onMouseDown: (e) => e.preventDefault(),
1637
- onClick,
1638
- children
1639
- }
1640
- );
1641
- }
1642
- function DocumentToolbar(props) {
1643
- const { editor, editable, exportFormats, canSave, saveState, onExport, onCopy, onSave } = props;
1644
- const [linkOpen, setLinkOpen] = useState2(false);
1645
- const [linkValue, setLinkValue] = useState2("");
1646
- const s = useEditorState({
1647
- editor,
1648
- selector: ({ editor: ed }) => ed ? {
1649
- bold: ed.isActive("bold"),
1650
- italic: ed.isActive("italic"),
1651
- underline: ed.isActive("underline"),
1652
- strike: ed.isActive("strike"),
1653
- bullet: ed.isActive("bulletList"),
1654
- ordered: ed.isActive("orderedList"),
1655
- quote: ed.isActive("blockquote"),
1656
- link: ed.isActive("link"),
1657
- linkHref: ed.getAttributes("link").href ?? "",
1658
- h1: ed.isActive("heading", { level: 1 }),
1659
- h2: ed.isActive("heading", { level: 2 }),
1660
- h3: ed.isActive("heading", { level: 3 }),
1661
- left: ed.isActive({ textAlign: "left" }),
1662
- center: ed.isActive({ textAlign: "center" }),
1663
- right: ed.isActive({ textAlign: "right" }),
1664
- justify: ed.isActive({ textAlign: "justify" }),
1665
- inTable: ed.isActive("table"),
1666
- canUndo: ed.can().undo(),
1667
- canRedo: ed.can().redo()
1668
- } : null
1669
- });
1670
- const run = () => editor?.chain().focus();
1671
- const off = !editor || !editable;
1672
- const heading = s?.h1 ? "1" : s?.h2 ? "2" : s?.h3 ? "3" : "p";
1673
- const openLink = () => {
1674
- setLinkValue(s?.linkHref ?? "");
1675
- setLinkOpen(true);
1676
- };
1677
- const applyLink = () => {
1678
- const href = sanitizeHref(linkValue);
1679
- if (href) run()?.extendMarkRange("link").setLink({ href }).run();
1680
- setLinkOpen(false);
1681
- };
1682
- const removeLink = () => {
1683
- run()?.extendMarkRange("link").unsetLink().run();
1684
- setLinkOpen(false);
1685
- };
1686
- return /* @__PURE__ */ jsxs3("div", { className: "june-doc__toolbar", role: "toolbar", "aria-label": "Document formatting", children: [
1687
- editable ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1688
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1689
- /* @__PURE__ */ jsx7(Btn, { label: "Undo", disabled: off || !s?.canUndo, onClick: () => run()?.undo().run(), children: "\u21B6" }),
1690
- /* @__PURE__ */ jsx7(Btn, { label: "Redo", disabled: off || !s?.canRedo, onClick: () => run()?.redo().run(), children: "\u21B7" })
1691
- ] }),
1692
- /* @__PURE__ */ jsx7("div", { className: "june-doc__group", children: /* @__PURE__ */ jsxs3(
1693
- "select",
1694
- {
1695
- className: "june-doc__select",
1696
- "aria-label": "Text style",
1697
- disabled: off,
1698
- value: heading,
1699
- onChange: (e) => {
1700
- const v = e.target.value;
1701
- if (v === "p") run()?.setParagraph().run();
1702
- else run()?.toggleHeading({ level: Number(v) }).run();
1703
- },
1704
- children: [
1705
- /* @__PURE__ */ jsx7("option", { value: "p", children: "Paragraph" }),
1706
- /* @__PURE__ */ jsx7("option", { value: "1", children: "Heading 1" }),
1707
- /* @__PURE__ */ jsx7("option", { value: "2", children: "Heading 2" }),
1708
- /* @__PURE__ */ jsx7("option", { value: "3", children: "Heading 3" })
1709
- ]
1710
- }
1711
- ) }),
1712
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1713
- /* @__PURE__ */ jsx7(Btn, { label: "Bold", toggle: true, active: s?.bold, disabled: off, onClick: () => run()?.toggleBold().run(), children: /* @__PURE__ */ jsx7("b", { children: "B" }) }),
1714
- /* @__PURE__ */ jsx7(Btn, { label: "Italic", toggle: true, active: s?.italic, disabled: off, onClick: () => run()?.toggleItalic().run(), children: /* @__PURE__ */ jsx7("i", { children: "I" }) }),
1715
- /* @__PURE__ */ jsx7(Btn, { label: "Underline", toggle: true, active: s?.underline, disabled: off, onClick: () => run()?.toggleUnderline().run(), children: /* @__PURE__ */ jsx7("u", { children: "U" }) }),
1716
- /* @__PURE__ */ jsx7(Btn, { label: "Strikethrough", toggle: true, active: s?.strike, disabled: off, onClick: () => run()?.toggleStrike().run(), children: /* @__PURE__ */ jsx7("s", { children: "S" }) })
1717
- ] }),
1718
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1719
- /* @__PURE__ */ jsx7(Btn, { label: "Bullet list", toggle: true, active: s?.bullet, disabled: off, onClick: () => run()?.toggleBulletList().run(), children: "\u2022 List" }),
1720
- /* @__PURE__ */ jsx7(Btn, { label: "Numbered list", toggle: true, active: s?.ordered, disabled: off, onClick: () => run()?.toggleOrderedList().run(), children: "1. List" }),
1721
- /* @__PURE__ */ jsx7(Btn, { label: "Blockquote", toggle: true, active: s?.quote, disabled: off, onClick: () => run()?.toggleBlockquote().run(), children: "Quote" }),
1722
- /* @__PURE__ */ jsx7(Btn, { label: "Link", toggle: true, active: s?.link, disabled: off, onClick: openLink, children: "Link" })
1723
- ] }),
1724
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1725
- /* @__PURE__ */ jsx7(Btn, { label: "Align left", toggle: true, active: s?.left, disabled: off, onClick: () => run()?.setTextAlign("left").run(), children: "L" }),
1726
- /* @__PURE__ */ jsx7(Btn, { label: "Align center", toggle: true, active: s?.center, disabled: off, onClick: () => run()?.setTextAlign("center").run(), children: "C" }),
1727
- /* @__PURE__ */ jsx7(Btn, { label: "Align right", toggle: true, active: s?.right, disabled: off, onClick: () => run()?.setTextAlign("right").run(), children: "R" }),
1728
- /* @__PURE__ */ jsx7(Btn, { label: "Justify", toggle: true, active: s?.justify, disabled: off, onClick: () => run()?.setTextAlign("justify").run(), children: "J" })
1729
- ] }),
1730
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group", children: [
1731
- /* @__PURE__ */ jsx7(Btn, { label: "Insert table", disabled: off, onClick: () => run()?.insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), children: "Table" }),
1732
- s?.inTable ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1733
- /* @__PURE__ */ jsx7(Btn, { label: "Add row below", disabled: off, onClick: () => run()?.addRowAfter().run(), children: "+Row" }),
1734
- /* @__PURE__ */ jsx7(Btn, { label: "Add column right", disabled: off, onClick: () => run()?.addColumnAfter().run(), children: "+Col" }),
1735
- /* @__PURE__ */ jsx7(Btn, { label: "Delete row", disabled: off, onClick: () => run()?.deleteRow().run(), children: "\u2212Row" }),
1736
- /* @__PURE__ */ jsx7(Btn, { label: "Delete column", disabled: off, onClick: () => run()?.deleteColumn().run(), children: "\u2212Col" }),
1737
- /* @__PURE__ */ jsx7(Btn, { label: "Delete table", disabled: off, onClick: () => run()?.deleteTable().run(), children: "Del table" })
1738
- ] }) : null
1739
- ] })
1740
- ] }) : null,
1741
- /* @__PURE__ */ jsxs3("div", { className: "june-doc__group june-doc__group--end", children: [
1742
- /* @__PURE__ */ jsx7(Btn, { label: "Copy document", onClick: onCopy, children: "Copy" }),
1743
- exportFormats.length > 0 ? /* @__PURE__ */ jsxs3(
1744
- "select",
1745
- {
1746
- className: "june-doc__select",
1747
- "aria-label": "Export document",
1748
- value: "",
1749
- onChange: (e) => e.target.value && onExport(e.target.value),
1750
- children: [
1751
- /* @__PURE__ */ jsx7("option", { value: "", children: "Export\u2026" }),
1752
- exportFormats.map((f) => /* @__PURE__ */ jsx7("option", { value: f, children: FORMAT_LABELS[f] ?? f }, f))
1753
- ]
1754
- }
1755
- ) : null,
1756
- canSave ? /* @__PURE__ */ jsx7(Btn, { label: "Save", disabled: saveState === "saving" || saveState === "saved" || saveState === "idle", onClick: onSave, children: "Save" }) : null
1757
- ] }),
1758
- linkOpen ? /* @__PURE__ */ jsxs3("div", { className: "june-doc__linkbar", children: [
1759
- /* @__PURE__ */ jsx7(
1760
- "input",
1761
- {
1762
- "aria-label": "Link address",
1763
- className: "june-doc__input",
1764
- placeholder: "https://example.com",
1765
- value: linkValue,
1766
- autoFocus: true,
1767
- onChange: (e) => setLinkValue(e.target.value),
1768
- onKeyDown: (e) => {
1769
- if (e.key === "Enter") applyLink();
1770
- if (e.key === "Escape") setLinkOpen(false);
1771
- }
1772
- }
1773
- ),
1774
- /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: applyLink, children: "Apply" }),
1775
- s?.link ? /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: removeLink, children: "Remove" }) : null,
1776
- /* @__PURE__ */ jsx7("button", { type: "button", className: "june-doc__btn", onClick: () => setLinkOpen(false), children: "Cancel" })
1777
- ] }) : null
1778
- ] });
1779
- }
1780
-
1781
2884
  // src/components/document-editor/document-types.ts
1782
2885
  var ALL_EXPORT_FORMATS = [
1783
2886
  "html",
@@ -1788,7 +2891,7 @@ var ALL_EXPORT_FORMATS = [
1788
2891
  ];
1789
2892
 
1790
2893
  // src/components/document-editor/document-editor.tsx
1791
- 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";
1792
2895
  var snapshotOf = (r) => ({
1793
2896
  documentId: r.id,
1794
2897
  title: r.title,
@@ -1803,6 +2906,8 @@ var SAVE_LABEL = {
1803
2906
  saved: "Saved",
1804
2907
  error: "Save failed"
1805
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;
1806
2911
  function DocumentEditorImpl(props) {
1807
2912
  const {
1808
2913
  documentId,
@@ -1817,7 +2922,7 @@ function DocumentEditorImpl(props) {
1817
2922
  } = props;
1818
2923
  const ctxOptions = useDocumentOptions();
1819
2924
  const options = useMemo4(() => ({ ...ctxOptions, ...props.options }), [ctxOptions, props.options]);
1820
- const optionsRef = useRef3(options);
2925
+ const optionsRef = useRef7(options);
1821
2926
  optionsRef.current = options;
1822
2927
  const store = useMemo4(() => options.store ?? getDefaultDocumentStore(), [options.store]);
1823
2928
  const instanceId = useId();
@@ -1832,22 +2937,37 @@ function DocumentEditorImpl(props) {
1832
2937
  return { json: null, error: toJuneError(e, "DOCUMENT_ERROR") };
1833
2938
  }
1834
2939
  }, [contentKey]);
1835
- useEffect2(() => {
2940
+ useEffect5(() => {
1836
2941
  if (parsed.error) optionsRef.current.onError?.(parsed.error);
1837
2942
  }, [parsed.error]);
1838
2943
  const title = useDocumentValue(store, documentId, (r) => r?.title ?? titleProp);
1839
2944
  const pending = useDocumentValue(store, documentId, (r) => r?.pending ?? null);
1840
2945
  const words = useDocumentValue(store, documentId, (r) => r ? wordCountOf(r.json) : 0);
1841
2946
  const recordStatus = useDocumentValue(store, documentId, (r) => r?.status ?? status);
1842
- const [saveState, setSaveState] = useState3("idle");
1843
- const [notice, setNotice] = useState3(null);
1844
- const lastSavedKey = useRef3("");
1845
- const seenRevision = useRef3(store.get(documentId)?.revision ?? 0);
1846
- 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);
1847
2967
  const flash = useCallback3((message) => {
1848
2968
  setNotice(message);
1849
2969
  }, []);
1850
- useEffect2(() => {
2970
+ useEffect5(() => {
1851
2971
  if (!notice) return;
1852
2972
  const t = setTimeout(() => setNotice(null), 3e3);
1853
2973
  return () => clearTimeout(t);
@@ -1859,13 +2979,13 @@ function DocumentEditorImpl(props) {
1859
2979
  },
1860
2980
  [store, documentId]
1861
2981
  );
1862
- const saveRef = useRef3(async () => {
2982
+ const saveRef = useRef7(async () => {
1863
2983
  });
1864
2984
  const autosave = useMemo4(() => {
1865
2985
  const delay = options.autosaveDelay ?? 1500;
1866
2986
  return delay > 0 ? debounce(() => void saveRef.current(), delay) : null;
1867
2987
  }, [options.autosaveDelay]);
1868
- useEffect2(() => () => autosave?.flush(), [autosave]);
2988
+ useEffect5(() => () => autosave?.flush(), [autosave]);
1869
2989
  saveRef.current = async () => {
1870
2990
  const record = store.get(documentId);
1871
2991
  const onSave = optionsRef.current.onSave;
@@ -1896,9 +3016,9 @@ function DocumentEditorImpl(props) {
1896
3016
  setSaveState("dirty");
1897
3017
  autosave?.();
1898
3018
  }, [autosave]);
1899
- const [initialJson] = useState3(() => store.get(documentId)?.json ?? parsed.json ?? emptyDoc());
3019
+ const [initialJson] = useState6(() => store.get(documentId)?.json ?? parsed.json ?? emptyDoc());
1900
3020
  const extensions = useMemo4(() => createDocumentExtensions(), []);
1901
- const onUpdateRef = useRef3(() => {
3021
+ const onUpdateRef = useRef7(() => {
1902
3022
  });
1903
3023
  onUpdateRef.current = (json) => {
1904
3024
  store.edit(documentId, json, instanceId);
@@ -1920,10 +3040,10 @@ function DocumentEditorImpl(props) {
1920
3040
  },
1921
3041
  onUpdate: ({ editor: ed }) => onUpdateRef.current(ed.getJSON())
1922
3042
  });
1923
- useEffect2(() => {
3043
+ useEffect5(() => {
1924
3044
  editor?.setEditable(editable);
1925
3045
  }, [editor, editable]);
1926
- useEffect2(() => {
3046
+ useEffect5(() => {
1927
3047
  if (!parsed.json) return;
1928
3048
  const outcome = store.receive({
1929
3049
  id: documentId,
@@ -1944,7 +3064,7 @@ function DocumentEditorImpl(props) {
1944
3064
  });
1945
3065
  }
1946
3066
  }, [store, documentId, titleProp, parsed.json, documentType, status, emitChange, markDirty]);
1947
- useEffect2(() => {
3067
+ useEffect5(() => {
1948
3068
  if (!editor) return;
1949
3069
  const apply = () => {
1950
3070
  const record = store.get(documentId);
@@ -1961,7 +3081,7 @@ function DocumentEditorImpl(props) {
1961
3081
  apply();
1962
3082
  return store.subscribe(apply);
1963
3083
  }, [editor, store, documentId, instanceId]);
1964
- useEffect2(() => {
3084
+ useEffect5(() => {
1965
3085
  if (!editor || readyRef.current) return;
1966
3086
  const record = store.get(documentId);
1967
3087
  if (!record) return;
@@ -2005,67 +3125,114 @@ function DocumentEditorImpl(props) {
2005
3125
  };
2006
3126
  const formats = (exportFormats ?? [...ALL_EXPORT_FORMATS]).filter((f) => listDocumentExporters().includes(f));
2007
3127
  if (parsed.error && !store.get(documentId)) {
2008
- return /* @__PURE__ */ jsxs4("div", { className: "june-doc june-doc--error", "data-theme": resolvedTheme, role: "alert", children: [
2009
- /* @__PURE__ */ jsx8("p", { className: "june-doc__draft-text", children: "This document could not be displayed." }),
2010
- /* @__PURE__ */ jsxs4("details", { children: [
2011
- /* @__PURE__ */ jsx8("summary", { children: "Show raw content" }),
2012
- /* @__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 })
2013
3133
  ] })
2014
3134
  ] });
2015
3135
  }
2016
- if (!editor) return /* @__PURE__ */ jsx8(DocumentDraft, { title, state: "loading" });
3136
+ if (!editor) return /* @__PURE__ */ jsx13(DocumentDraft, { title, state: "loading" });
2017
3137
  const isDraft = recordStatus === "draft";
2018
- return /* @__PURE__ */ jsxs4(
3138
+ const classes = ["june-doc", fullscreen ? "june-doc--fullscreen" : "", className ?? ""].filter(Boolean).join(" ");
3139
+ return /* @__PURE__ */ jsxs9(
2019
3140
  "div",
2020
3141
  {
2021
- className: className ? `june-doc ${className}` : "june-doc",
3142
+ className: classes,
2022
3143
  "data-theme": resolvedTheme,
2023
3144
  "data-status": recordStatus,
3145
+ role: fullscreen ? "dialog" : void 0,
3146
+ "aria-modal": fullscreen || void 0,
3147
+ "aria-label": fullscreen ? title : void 0,
2024
3148
  onKeyDown: (e) => {
2025
- 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) {
2026
3151
  e.preventDefault();
2027
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);
2028
3158
  }
2029
3159
  },
2030
3160
  children: [
2031
- /* @__PURE__ */ jsxs4("div", { className: "june-doc__header", children: [
2032
- /* @__PURE__ */ jsx8(DocumentTitle, { value: title, disabled: !editable, onChange: handleTitle }),
2033
- isDraft ? /* @__PURE__ */ jsx8("span", { className: "june-doc__badge", children: "AI draft" }) : null
2034
- ] }),
2035
- pending ? /* @__PURE__ */ jsxs4("div", { className: "june-doc__banner", role: "status", children: [
2036
- /* @__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: [
2037
3180
  "The assistant suggested changes that overlap your edits (",
2038
3181
  pending.conflicts,
2039
3182
  " ",
2040
3183
  pending.conflicts === 1 ? "section" : "sections",
2041
3184
  ")."
2042
3185
  ] }),
2043
- /* @__PURE__ */ jsxs4("span", { className: "june-doc__banner-actions", children: [
2044
- /* @__PURE__ */ jsx8("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("merge"), children: "Merge, keep my edits" }),
2045
- /* @__PURE__ */ jsx8("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("replace"), children: "Use AI version" }),
2046
- /* @__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" })
2047
3190
  ] })
2048
3191
  ] }) : null,
2049
- /* @__PURE__ */ jsx8(
3192
+ /* @__PURE__ */ jsx13(
2050
3193
  DocumentToolbar,
2051
3194
  {
2052
3195
  editor,
2053
3196
  editable,
2054
- exportFormats: formats,
2055
- canSave: !!options.onSave,
2056
- saveState,
2057
- onExport: (f) => void handleExport(f),
2058
- onCopy: () => void handleCopy(),
2059
- 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
2060
3206
  }
2061
3207
  ),
2062
- /* @__PURE__ */ jsx8(DocumentContent, { editor }),
2063
- /* @__PURE__ */ jsxs4("div", { className: "june-doc__footer", "aria-live": "polite", children: [
2064
- /* @__PURE__ */ jsxs4("span", { children: [
2065
- words,
2066
- " 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
+ ] })
2067
3226
  ] }),
2068
- /* @__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
+ ] })
2069
3236
  ] })
2070
3237
  ]
2071
3238
  }
@@ -2073,8 +3240,8 @@ function DocumentEditorImpl(props) {
2073
3240
  }
2074
3241
 
2075
3242
  // src/components/document-editor/document-component.tsx
2076
- import { jsx as jsx9 } from "react/jsx-runtime";
2077
- var DOCUMENT_COMPONENT_DESCRIPTION = 'An editable document. DocumentEditor(documentId, title, content, documentType?, status?, editable?, exportFormats?). "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.';
3243
+ import { jsx as jsx14 } from "react/jsx-runtime";
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.';
2078
3245
  var documentProps = z.object({
2079
3246
  documentId: z.string().describe("Stable id. Reuse it when revising the same document."),
2080
3247
  title: z.string().describe("Document title. Do not repeat it as a heading in content."),
@@ -2087,12 +3254,12 @@ var documentProps = z.object({
2087
3254
  function DocumentEditor(props) {
2088
3255
  const { isStreaming } = useJuneRuntime();
2089
3256
  if (props.generating || isStreaming) {
2090
- return /* @__PURE__ */ jsx9(DocumentDraft, { title: props.title, state: "drafting" });
3257
+ return /* @__PURE__ */ jsx14(DocumentDraft, { title: props.title, state: "drafting" });
2091
3258
  }
2092
- return /* @__PURE__ */ jsx9(DocumentEditorImpl, { ...props });
3259
+ return /* @__PURE__ */ jsx14(DocumentEditorImpl, { ...props });
2093
3260
  }
2094
3261
  function OpenUIDocument({ props }) {
2095
- return /* @__PURE__ */ jsx9(
3262
+ return /* @__PURE__ */ jsx14(
2096
3263
  DocumentEditor,
2097
3264
  {
2098
3265
  documentId: props.documentId,
@@ -2112,7 +3279,15 @@ var JuneDocumentEditorComponent = defineComponent2({
2112
3279
  component: OpenUIDocument
2113
3280
  });
2114
3281
  function withDocumentEditor(library) {
2115
- return library.extend({ components: { add: [JuneDocumentEditorComponent] } });
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
+ }
2116
3291
  }
2117
3292
 
2118
3293
  // src/components/document-editor/document-instructions.ts
@@ -2159,7 +3334,7 @@ function documentPromptOptions(options = {}) {
2159
3334
  var attr = (value) => value.replace(/"/g, "&quot;");
2160
3335
  async function serializeDocumentsForAgent(options = {}) {
2161
3336
  const store = options.store ?? getDefaultDocumentStore();
2162
- const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-C6ILFMRP.js");
3337
+ const { docToMarkdown: docToMarkdown2 } = await import("./document-engine-IAY2T6B3.js");
2163
3338
  return store.list().filter((r) => !options.ids || options.ids.includes(r.id)).map(
2164
3339
  (r) => `<document id="${attr(r.id)}" title="${attr(r.title)}"${r.documentType ? ` type="${attr(r.documentType)}"` : ""}>
2165
3340
  ${docToMarkdown2(r.json)}
@@ -2172,9 +3347,9 @@ import { openuiLibrary } from "@openuidev/react-ui";
2172
3347
  var juneLibrary = withDocumentEditor(openuiLibrary);
2173
3348
 
2174
3349
  // src/core/JuneUI.tsx
2175
- import { jsx as jsx10 } from "react/jsx-runtime";
3350
+ import { jsx as jsx15 } from "react/jsx-runtime";
2176
3351
  function JuneUI({ library = juneLibrary, ...props }) {
2177
- return /* @__PURE__ */ jsx10(JuneRenderer, { library, ...props });
3352
+ return /* @__PURE__ */ jsx15(JuneRenderer, { library, ...props });
2178
3353
  }
2179
3354
  export {
2180
3355
  ALL_EXPORT_FORMATS,