@bendyline/docblocks-react 2.2.0 → 2.2.2

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ExportDialog
3
- } from "./chunk-3CZ27FOA.js";
3
+ } from "./chunk-MHTQORMH.js";
4
4
  import "./chunk-YBEYTVU2.js";
5
5
  import "./chunk-LG6HAWCK.js";
6
6
  import "./chunk-EBWYGTN7.js";
@@ -1,3 +1,6 @@
1
+ import {
2
+ useMenuKeyboard
3
+ } from "./chunk-MRUK56JS.js";
1
4
  import {
2
5
  loadTransformStyleSummaries
3
6
  } from "./chunk-YBEYTVU2.js";
@@ -26,7 +29,7 @@ import { getThemeSummaries } from "@bendyline/squisq/schemas";
26
29
  import { parseMarkdown } from "@bendyline/squisq/markdown";
27
30
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
28
31
  var ExportDialog = lazy(
29
- () => import("./ExportDialog-VYATD5UC.js").then((module) => ({ default: module.ExportDialog }))
32
+ () => import("./ExportDialog-HPFPXELL.js").then((module) => ({ default: module.ExportDialog }))
30
33
  );
31
34
  var ShareDialog = lazy(
32
35
  () => import("./ShareDialog-PAMOQQFB.js").then((module) => ({ default: module.ShareDialog }))
@@ -36,6 +39,8 @@ function loadRunExportModule() {
36
39
  runExportModulePromise ?? (runExportModulePromise = import("./run-export-Z5BVFLIR.js"));
37
40
  return runExportModulePromise;
38
41
  }
42
+ var ExportCancelledError = class extends Error {
43
+ };
39
44
  var SHARED_USE_MODES = Object.freeze({
40
45
  slideshow: "slideshow",
41
46
  video: "video",
@@ -69,10 +74,10 @@ function loadVideoExportModules() {
69
74
  return videoExportModulesPromise;
70
75
  }
71
76
  function exportErrorMessage(caught) {
72
- const detail = caught instanceof Error ? caught.message.trim() : "";
77
+ const detail = caught instanceof Error ? caught.message.replace(/^Error invoking remote method '[^']+': Error:\s*/, "").trim() : "";
73
78
  return detail ? `Export failed: ${detail}` : "Export failed. The document could not be exported.";
74
79
  }
75
- function quickLabel(opts, transformSummaries) {
80
+ function quickLabel(opts, transformSummaries, destinationPath) {
76
81
  const baseExt = FORMAT_EXTENSIONS[opts.format].toUpperCase().replace(".", "");
77
82
  const isRecursiveHtml = opts.format === "html" && opts.includeLinkedDocs;
78
83
  const ext = opts.format === "html" && (opts.htmlBundle === "zip" || isRecursiveHtml) ? "ZIP" : baseExt;
@@ -92,9 +97,11 @@ function quickLabel(opts, transformSummaries) {
92
97
  if (transform) parts.push(transform.name);
93
98
  }
94
99
  if (parts.length > 0) {
95
- return `Export ${ext} with ${parts.join(" + ")}`;
100
+ const label2 = `Export ${ext} with ${parts.join(" + ")}`;
101
+ return destinationPath ? `${label2} to ${destinationPath}` : label2;
96
102
  }
97
- return `Export ${ext}`;
103
+ const label = `Export ${ext}`;
104
+ return destinationPath ? `${label} to ${destinationPath}` : label;
98
105
  }
99
106
  function ExportToolbarControls({
100
107
  selectedFile,
@@ -123,9 +130,14 @@ function ExportToolbarControls({
123
130
  const [exporting, setExporting] = useState(false);
124
131
  const [exportError, setExportError] = useState(null);
125
132
  const [destinationTarget, setDestinationTarget] = useState(null);
133
+ const [quickDestination, setQuickDestination] = useState(null);
134
+ const [quickDestinationPending, setQuickDestinationPending] = useState(false);
126
135
  const destinationRequestRef = useRef(0);
127
- const menuRef = useRef(null);
136
+ const menuContainerRef = useRef(null);
137
+ const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(menuOpen, setMenuOpen);
128
138
  const lastOptions = loadLastExportOptions();
139
+ const quickDestinationKey = lastOptions ? JSON.stringify([selectedFile, lastOptions]) : null;
140
+ const quickDestinationTarget = quickDestination?.key === quickDestinationKey ? quickDestination.target : null;
129
141
  const docThemeId = useMemo(() => {
130
142
  const fm = markdownDoc?.frontmatter;
131
143
  if (!fm) return null;
@@ -142,12 +154,17 @@ function ExportToolbarControls({
142
154
  useEffect(() => {
143
155
  if (!menuOpen) return;
144
156
  const onPointerDown = (e) => {
145
- if (menuRef.current && !menuRef.current.contains(e.target)) {
146
- setMenuOpen(false);
157
+ if (menuContainerRef.current && !menuContainerRef.current.contains(e.target)) {
158
+ closeMenu(false);
147
159
  }
148
160
  };
149
161
  document.addEventListener("pointerdown", onPointerDown);
150
162
  return () => document.removeEventListener("pointerdown", onPointerDown);
163
+ }, [closeMenu, menuOpen]);
164
+ useEffect(() => {
165
+ if (menuOpen) return;
166
+ setQuickDestination(null);
167
+ setQuickDestinationPending(false);
151
168
  }, [menuOpen]);
152
169
  useEffect(() => {
153
170
  if (!menuOpen || lastOptions?.format !== "pptx" || !lastOptions.transformStyle || transformSummaries.length > 0) {
@@ -163,9 +180,33 @@ function ExportToolbarControls({
163
180
  cancelled = true;
164
181
  };
165
182
  }, [lastOptions?.format, lastOptions?.transformStyle, menuOpen, transformSummaries.length]);
183
+ useEffect(() => {
184
+ if (!menuOpen || !destinationAdapter || !quickDestinationKey) return;
185
+ const quickOptions = loadLastExportOptions();
186
+ if (!quickOptions) return;
187
+ let cancelled = false;
188
+ setQuickDestination(null);
189
+ setQuickDestinationPending(true);
190
+ void loadRunExportModule().then(
191
+ ({ buildExportFilename }) => destinationAdapter.resolveTarget(buildExportFilename(selectedFile, quickOptions))
192
+ ).then((target) => {
193
+ if (!cancelled) setQuickDestination({ key: quickDestinationKey, target });
194
+ }).catch(() => {
195
+ if (!cancelled) setQuickDestination(null);
196
+ }).finally(() => {
197
+ if (!cancelled) setQuickDestinationPending(false);
198
+ });
199
+ return () => {
200
+ cancelled = true;
201
+ };
202
+ }, [destinationAdapter, menuOpen, quickDestinationKey, selectedFile]);
166
203
  const handleToggleMenu = useCallback(() => {
204
+ if (!menuOpen && destinationAdapter) {
205
+ setQuickDestination(null);
206
+ setQuickDestinationPending(true);
207
+ }
167
208
  setMenuOpen((prev) => !prev);
168
- }, []);
209
+ }, [destinationAdapter, menuOpen]);
169
210
  const refreshDestination = useCallback(
170
211
  async (options) => {
171
212
  if (!destinationAdapter) return;
@@ -225,10 +266,6 @@ function ExportToolbarControls({
225
266
  },
226
267
  [markdownSource, videoModules]
227
268
  );
228
- const handleOpenAnimatedGifFromDialog = useCallback(() => {
229
- setDialogOpen(false);
230
- void handleOpenVideoModal("gif");
231
- }, [handleOpenVideoModal]);
232
269
  const handleCloseVideoModal = useCallback(() => {
233
270
  setVideoModalOpen(false);
234
271
  setVideoDoc(null);
@@ -249,6 +286,7 @@ function ExportToolbarControls({
249
286
  const pickedTarget = await destinationAdapter.pickTarget(filename, destinationTarget);
250
287
  if (pickedTarget === null) return;
251
288
  setDestinationTarget(pickedTarget);
289
+ setQuickDestination(null);
252
290
  } catch {
253
291
  }
254
292
  },
@@ -258,7 +296,8 @@ function ExportToolbarControls({
258
296
  async (blob, filename, target) => {
259
297
  if (!destinationAdapter) return;
260
298
  const savedTarget = await destinationAdapter.saveBlob(blob, filename, target);
261
- if (savedTarget) setDestinationTarget(savedTarget);
299
+ if (!savedTarget) throw new ExportCancelledError();
300
+ setDestinationTarget(savedTarget);
262
301
  },
263
302
  [destinationAdapter]
264
303
  );
@@ -283,7 +322,7 @@ function ExportToolbarControls({
283
322
  );
284
323
  setDialogOpen(false);
285
324
  } catch (caught) {
286
- setExportError(exportErrorMessage(caught));
325
+ if (!(caught instanceof ExportCancelledError)) setExportError(exportErrorMessage(caught));
287
326
  } finally {
288
327
  setExporting(false);
289
328
  }
@@ -307,10 +346,8 @@ function ExportToolbarControls({
307
346
  const { runExport } = await loadRunExportModule();
308
347
  let quickTarget = null;
309
348
  if (destinationAdapter) {
310
- const { buildExportFilename } = await loadRunExportModule();
311
- quickTarget = await destinationAdapter.resolveTarget(
312
- buildExportFilename(selectedFile, lastOptions)
313
- );
349
+ if (!quickDestinationTarget) return;
350
+ quickTarget = quickDestinationTarget;
314
351
  }
315
352
  await runExport(
316
353
  markdownSource,
@@ -320,7 +357,7 @@ function ExportToolbarControls({
320
357
  destinationAdapter ? async (blob, filename) => saveToDestination(blob, filename, quickTarget) : saveBlob
321
358
  );
322
359
  } catch (caught) {
323
- setExportError(exportErrorMessage(caught));
360
+ if (!(caught instanceof ExportCancelledError)) setExportError(exportErrorMessage(caught));
324
361
  } finally {
325
362
  setExporting(false);
326
363
  }
@@ -331,7 +368,8 @@ function ExportToolbarControls({
331
368
  mediaContainer,
332
369
  saveBlob,
333
370
  saveToDestination,
334
- selectedFile
371
+ selectedFile,
372
+ quickDestinationTarget
335
373
  ]);
336
374
  const handleDismissExportError = useCallback(() => {
337
375
  setExportError(null);
@@ -351,13 +389,15 @@ function ExportToolbarControls({
351
389
  "data-tooltip": exporting ? "Exporting..." : "Export document",
352
390
  children: /* @__PURE__ */ jsx(ExportGlyph, {})
353
391
  }
354
- ) : /* @__PURE__ */ jsxs("div", { className: "db-toolbar-menu", ref: menuRef, children: [
392
+ ) : /* @__PURE__ */ jsxs("div", { className: "db-toolbar-menu", ref: menuContainerRef, children: [
355
393
  /* @__PURE__ */ jsxs(
356
394
  "button",
357
395
  {
396
+ ref: triggerRef,
358
397
  type: "button",
359
398
  className: "db-toolbar-menu-trigger",
360
399
  onClick: handleToggleMenu,
400
+ onKeyDown: handleTriggerKeyDown,
361
401
  "aria-label": "Export and share",
362
402
  "aria-haspopup": "menu",
363
403
  "aria-expanded": menuOpen,
@@ -368,62 +408,85 @@ function ExportToolbarControls({
368
408
  ]
369
409
  }
370
410
  ),
371
- menuOpen && /* @__PURE__ */ jsxs("div", { className: "db-toolbar-menu-dropdown", role: "menu", children: [
372
- lastOptions && /* @__PURE__ */ jsx(
373
- "button",
374
- {
375
- type: "button",
376
- role: "menuitem",
377
- className: "db-toolbar-menu-item",
378
- onClick: handleQuickExport,
379
- disabled: exporting,
380
- children: quickLabel(lastOptions, transformSummaries)
381
- }
382
- ),
383
- /* @__PURE__ */ jsx(
384
- "button",
385
- {
386
- type: "button",
387
- role: "menuitem",
388
- className: "db-toolbar-menu-item",
389
- onClick: handleOpenDialog,
390
- children: "Export..."
391
- }
392
- ),
393
- /* @__PURE__ */ jsx(
394
- "button",
395
- {
396
- type: "button",
397
- role: "menuitem",
398
- className: "db-toolbar-menu-item",
399
- onClick: handleOpenShareDialog,
400
- children: "Share link with content embedded..."
401
- }
402
- ),
403
- showVideoExport && /* @__PURE__ */ jsxs(Fragment, { children: [
404
- /* @__PURE__ */ jsx("div", { className: "db-toolbar-menu-divider", role: "separator" }),
405
- /* @__PURE__ */ jsx(
406
- "button",
407
- {
408
- type: "button",
409
- role: "menuitem",
410
- className: "db-toolbar-menu-item",
411
- onClick: () => void handleOpenVideoModal("mp4"),
412
- children: "Export video..."
413
- }
414
- ),
415
- showAnimatedGifExport && /* @__PURE__ */ jsx(
416
- "button",
417
- {
418
- type: "button",
419
- role: "menuitem",
420
- className: "db-toolbar-menu-item",
421
- onClick: () => void handleOpenVideoModal("gif"),
422
- children: "Export animated gif..."
423
- }
424
- )
425
- ] })
426
- ] })
411
+ menuOpen && /* @__PURE__ */ jsxs(
412
+ "div",
413
+ {
414
+ ref: menuRef,
415
+ className: "db-toolbar-menu-dropdown db-export-toolbar-menu-dropdown",
416
+ role: "menu",
417
+ onKeyDown: handleMenuKeyDown,
418
+ children: [
419
+ lastOptions && /* @__PURE__ */ jsx(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ role: "menuitem",
424
+ tabIndex: -1,
425
+ className: "db-toolbar-menu-item",
426
+ onClick: handleQuickExport,
427
+ disabled: exporting || Boolean(destinationAdapter) && !quickDestinationTarget,
428
+ title: quickDestinationTarget ? quickLabel(
429
+ lastOptions,
430
+ transformSummaries,
431
+ quickDestinationTarget.displayPath
432
+ ) : void 0,
433
+ children: quickDestinationTarget ? quickLabel(
434
+ lastOptions,
435
+ transformSummaries,
436
+ quickDestinationTarget.displayPath
437
+ ) : quickLabel(lastOptions, transformSummaries) + (destinationAdapter ? quickDestinationPending ? " (finding destination...)" : " (destination unavailable)" : "")
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx(
441
+ "button",
442
+ {
443
+ type: "button",
444
+ role: "menuitem",
445
+ tabIndex: -1,
446
+ className: "db-toolbar-menu-item",
447
+ onClick: handleOpenDialog,
448
+ children: "Export..."
449
+ }
450
+ ),
451
+ /* @__PURE__ */ jsx(
452
+ "button",
453
+ {
454
+ type: "button",
455
+ role: "menuitem",
456
+ tabIndex: -1,
457
+ className: "db-toolbar-menu-item",
458
+ onClick: handleOpenShareDialog,
459
+ children: "Share link with content embedded..."
460
+ }
461
+ ),
462
+ showVideoExport && /* @__PURE__ */ jsxs(Fragment, { children: [
463
+ /* @__PURE__ */ jsx("div", { className: "db-toolbar-menu-divider", role: "separator" }),
464
+ /* @__PURE__ */ jsx(
465
+ "button",
466
+ {
467
+ type: "button",
468
+ role: "menuitem",
469
+ tabIndex: -1,
470
+ className: "db-toolbar-menu-item",
471
+ onClick: () => void handleOpenVideoModal("mp4"),
472
+ children: "Export video..."
473
+ }
474
+ ),
475
+ showAnimatedGifExport && /* @__PURE__ */ jsx(
476
+ "button",
477
+ {
478
+ type: "button",
479
+ role: "menuitem",
480
+ tabIndex: -1,
481
+ className: "db-toolbar-menu-item",
482
+ onClick: () => void handleOpenVideoModal("gif"),
483
+ children: "Export animated gif..."
484
+ }
485
+ )
486
+ ] })
487
+ ]
488
+ }
489
+ )
427
490
  ] }),
428
491
  dialogOpen && /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
429
492
  ExportDialog,
@@ -438,7 +501,6 @@ function ExportToolbarControls({
438
501
  } : void 0,
439
502
  onExport: handleExport,
440
503
  onOptionsChange: destinationAdapter ? handleOptionsChange : void 0,
441
- onAnimatedGifExport: showAnimatedGifExport ? handleOpenAnimatedGifFromDialog : void 0,
442
504
  onClose: handleCloseDialog
443
505
  }
444
506
  ) }),
@@ -1,10 +1,11 @@
1
- import {
2
- useMenuKeyboard
3
- } from "./chunk-TFMO7EVO.js";
1
+ import "./chunk-APC4KI4B.js";
4
2
  import {
5
3
  isFileDirty,
6
4
  useGitContext
7
5
  } from "./chunk-HFYPTTCH.js";
6
+ import {
7
+ useMenuKeyboard
8
+ } from "./chunk-MRUK56JS.js";
8
9
 
9
10
  // src/Git/GitToolbarControl.tsx
10
11
  import { useEffect, useRef, useState } from "react";
@@ -1,6 +1,4 @@
1
- import {
2
- useMenuKeyboard
3
- } from "./chunk-TFMO7EVO.js";
1
+ import "./chunk-APC4KI4B.js";
4
2
  import {
5
3
  BADGE_GLYPHS,
6
4
  BADGE_LABELS,
@@ -9,6 +7,9 @@ import {
9
7
  summarizeDirty,
10
8
  useGitContext
11
9
  } from "./chunk-HFYPTTCH.js";
10
+ import {
11
+ useMenuKeyboard
12
+ } from "./chunk-MRUK56JS.js";
12
13
  import {
13
14
  Dialog
14
15
  } from "./chunk-LG6HAWCK.js";
File without changes
@@ -1,4 +1,24 @@
1
1
  // src/editor.ts
2
+ import { useEffect, useState } from "react";
3
+ var PORTRAIT_FORM_FACTOR_QUERY = "(orientation: portrait)";
4
+ function previewViewportPresetForOrientation(isPortrait) {
5
+ return isPortrait ? "portrait" : "landscape";
6
+ }
7
+ function useResponsivePreviewViewportPreset() {
8
+ const [isPortrait, setIsPortrait] = useState(() => {
9
+ if (typeof globalThis.matchMedia !== "function") return false;
10
+ return globalThis.matchMedia(PORTRAIT_FORM_FACTOR_QUERY).matches;
11
+ });
12
+ useEffect(() => {
13
+ if (typeof globalThis.matchMedia !== "function") return;
14
+ const query = globalThis.matchMedia(PORTRAIT_FORM_FACTOR_QUERY);
15
+ const update = () => setIsPortrait(query.matches);
16
+ update();
17
+ query.addEventListener("change", update);
18
+ return () => query.removeEventListener("change", update);
19
+ }, []);
20
+ return previewViewportPresetForOrientation(isPortrait);
21
+ }
2
22
  var EMPTY_DOCUMENT_PROMPTS = [
3
23
  "Start typing your content, or drop images on top of me...",
4
24
  "Write anything -- paste markdown, drag in images, or just start typing...",
@@ -64,6 +84,9 @@ function pickEmptyDocumentPrompt(random = Math.random) {
64
84
  }
65
85
 
66
86
  export {
87
+ PORTRAIT_FORM_FACTOR_QUERY,
88
+ previewViewportPresetForOrientation,
89
+ useResponsivePreviewViewportPreset,
67
90
  EMPTY_DOCUMENT_PROMPTS,
68
91
  pickEmptyDocumentPrompt
69
92
  };
@@ -76,7 +76,6 @@ function ExportDialog({
76
76
  destination,
77
77
  onExport,
78
78
  onOptionsChange,
79
- onAnimatedGifExport,
80
79
  onClose
81
80
  }) {
82
81
  const [format, setFormat] = useState(initial.format);
@@ -327,23 +326,8 @@ function ExportDialog({
327
326
  }
328
327
  )
329
328
  ] }),
330
- onAnimatedGifExport && /* @__PURE__ */ jsxs("div", { className: "db-export-field", children: [
331
- /* @__PURE__ */ jsx("span", { className: "db-export-label", children: "Animated media" }),
332
- /* @__PURE__ */ jsx(
333
- "button",
334
- {
335
- type: "button",
336
- className: "db-export-btn db-export-btn--secondary",
337
- onClick: onAnimatedGifExport,
338
- children: "Animated GIF..."
339
- }
340
- ),
341
- /* @__PURE__ */ jsx("span", { className: "db-export-hint", children: "Opens timing, orientation, captions, looping, and color controls." })
342
- ] }),
343
329
  /* @__PURE__ */ jsxs("p", { className: "db-export-more-formats", children: [
344
- "Need spreadsheets, ",
345
- onAnimatedGifExport ? "" : "animated media, ",
346
- "portable bundles, or automation?",
330
+ "Need spreadsheets, animated media, portable bundles, or automation?",
347
331
  " ",
348
332
  /* @__PURE__ */ jsx("a", { href: "https://docblocks.com/cli/", target: "_blank", rel: "noopener noreferrer", children: "More formats with the DocBlocks CLI" }),
349
333
  "."
@@ -1,5 +1,9 @@
1
- // src/Git/useMenuKeyboard.ts
2
- import { useCallback, useEffect, useRef } from "react";
1
+ // src/components/useMenuKeyboard.ts
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useRef
6
+ } from "react";
3
7
  function useMenuKeyboard(open, setOpen) {
4
8
  const menuRef = useRef(null);
5
9
  const triggerRef = useRef(null);
@@ -22,7 +26,7 @@ function useMenuKeyboard(open, setOpen) {
22
26
  (returnFocus) => {
23
27
  pendingFocusRef.current = "none";
24
28
  setOpen(false);
25
- if (returnFocus) triggerRef.current?.focus();
29
+ if (returnFocus) triggerRef.current?.focus({ preventScroll: true });
26
30
  },
27
31
  [setOpen]
28
32
  );
@@ -32,6 +36,17 @@ function useMenuKeyboard(open, setOpen) {
32
36
  pendingFocusRef.current = "none";
33
37
  focusItem(where === "last" ? items().length - 1 : 0);
34
38
  }, [open, focusItem, items]);
39
+ useEffect(() => {
40
+ if (!open) return;
41
+ const handleDocumentKeyDown = (event) => {
42
+ if (event.key !== "Escape" || event.defaultPrevented) return;
43
+ event.preventDefault();
44
+ event.stopPropagation();
45
+ closeMenu(true);
46
+ };
47
+ document.addEventListener("keydown", handleDocumentKeyDown);
48
+ return () => document.removeEventListener("keydown", handleDocumentKeyDown);
49
+ }, [closeMenu, open]);
35
50
  const handleTriggerKeyDown = useCallback(
36
51
  (event) => {
37
52
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
@@ -48,12 +63,16 @@ function useMenuKeyboard(open, setOpen) {
48
63
  );
49
64
  const handleMenuKeyDown = useCallback(
50
65
  (event) => {
51
- if (event.key === "Escape" || event.key === "Tab") {
66
+ if (event.key === "Escape") {
52
67
  event.preventDefault();
53
68
  event.stopPropagation();
54
69
  closeMenu(true);
55
70
  return;
56
71
  }
72
+ if (event.key === "Tab") {
73
+ closeMenu(false);
74
+ return;
75
+ }
57
76
  const all = items();
58
77
  if (all.length === 0) return;
59
78
  const current = all.indexOf(document.activeElement);