canopycms 0.2.0-int.121 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +241 -78
  2. package/dist/api/branch.d.ts +2 -2
  3. package/dist/cli/cli.js +0 -18
  4. package/dist/cli/generate-ai-content.js +0 -6
  5. package/dist/cli/init.js +0 -6
  6. package/dist/dev-content-watcher.js +8 -1
  7. package/dist/editor/BranchManager.d.ts +2 -0
  8. package/dist/editor/BranchManager.js +3 -3
  9. package/dist/editor/Editor.js +276 -130
  10. package/dist/editor/EntryNavigator.d.ts +44 -9
  11. package/dist/editor/EntryNavigator.js +278 -104
  12. package/dist/editor/branch-lock.d.ts +42 -0
  13. package/dist/editor/branch-lock.js +79 -0
  14. package/dist/editor/branch-status.d.ts +0 -6
  15. package/dist/editor/branch-status.js +0 -10
  16. package/dist/editor/components/BranchNotAvailableNotice.d.ts +11 -0
  17. package/dist/editor/components/BranchNotAvailableNotice.js +10 -0
  18. package/dist/editor/components/BranchPill.d.ts +3 -18
  19. package/dist/editor/components/BranchPill.js +7 -10
  20. package/dist/editor/components/BranchRowLock.d.ts +3 -1
  21. package/dist/editor/components/BranchRowLock.js +4 -2
  22. package/dist/editor/components/EditorHeader.d.ts +24 -52
  23. package/dist/editor/components/EditorHeader.js +151 -71
  24. package/dist/editor/components/EditorRail.d.ts +8 -8
  25. package/dist/editor/components/EditorRail.js +7 -5
  26. package/dist/editor/components/EntryBreadcrumb.d.ts +5 -1
  27. package/dist/editor/components/EntryBreadcrumb.js +3 -3
  28. package/dist/editor/components/EntryHeader.d.ts +60 -0
  29. package/dist/editor/components/EntryHeader.js +45 -0
  30. package/dist/editor/components/EntrySaveButton.d.ts +24 -0
  31. package/dist/editor/components/EntrySaveButton.js +101 -0
  32. package/dist/editor/components/NoEditPermissionNotice.d.ts +1 -1
  33. package/dist/editor/components/NoEditPermissionNotice.js +1 -1
  34. package/dist/editor/components/RetryToastMessage.d.ts +3 -1
  35. package/dist/editor/components/RetryToastMessage.js +8 -4
  36. package/dist/editor/components/SaveAllButton.d.ts +25 -0
  37. package/dist/editor/components/SaveAllButton.js +81 -0
  38. package/dist/editor/components/UnsavedMark.d.ts +1 -1
  39. package/dist/editor/components/UnsavedMark.js +1 -1
  40. package/dist/editor/components/index.d.ts +1 -1
  41. package/dist/editor/components/index.js +1 -1
  42. package/dist/editor/copy.d.ts +0 -1
  43. package/dist/editor/copy.js +0 -1
  44. package/dist/editor/editor-utils.d.ts +2 -2
  45. package/dist/editor/editor-utils.js +2 -2
  46. package/dist/editor/entry-search.d.ts +10 -16
  47. package/dist/editor/entry-search.js +4 -24
  48. package/dist/editor/hooks/editor-history.d.ts +7 -0
  49. package/dist/editor/hooks/editor-history.js +80 -0
  50. package/dist/editor/hooks/useBranchActions.js +3 -1
  51. package/dist/editor/hooks/useBranchManager.d.ts +7 -3
  52. package/dist/editor/hooks/useBranchManager.js +18 -11
  53. package/dist/editor/hooks/useEntryManager.d.ts +12 -2
  54. package/dist/editor/hooks/useEntryManager.js +141 -20
  55. package/dist/editor/shell/ActivityRail.d.ts +4 -0
  56. package/dist/editor/shell/ActivityRail.js +13 -4
  57. package/dist/editor/shell/EditorShell.d.ts +6 -7
  58. package/dist/editor/shell/EditorShell.js +9 -13
  59. package/dist/editor/shell/PaneLayout.d.ts +1 -0
  60. package/dist/editor/shell/PaneLayout.js +23 -5
  61. package/dist/editor/shell/SidePanelHost.d.ts +11 -4
  62. package/dist/editor/shell/SidePanelHost.js +95 -7
  63. package/dist/editor/shell/TopBar.d.ts +4 -4
  64. package/dist/editor/shell/TopBar.js +6 -3
  65. package/dist/editor/theme.js +22 -1
  66. package/dist/editor/utils/focus-return.d.ts +10 -2
  67. package/dist/editor/utils/focus-return.js +40 -6
  68. package/dist/operating-mode/client-safe-strategy.d.ts +1 -6
  69. package/dist/operating-mode/client-safe-strategy.js +1 -10
  70. package/dist/operating-mode/types.d.ts +0 -3
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/package.json +1 -1
  74. package/dist/editor/components/BranchLockBanner.d.ts +0 -23
  75. package/dist/editor/components/BranchLockBanner.js +0 -66
  76. package/dist/editor/components/QuickOpenPalette.d.ts +0 -28
  77. package/dist/editor/components/QuickOpenPalette.js +0 -71
  78. package/dist/editor/components/SaveSplitButton.d.ts +0 -31
  79. package/dist/editor/components/SaveSplitButton.js +0 -121
@@ -1,31 +0,0 @@
1
- import type { SaveAllProgress } from '../hooks/save-all.js';
2
- export interface SaveSplitButtonProps {
3
- onSave: () => void;
4
- /** Why Save can't act now; empty when it can. */
5
- saveBlockedReason: string;
6
- /** The editor is loading or saving: Save waits without a reason. */
7
- busy: boolean;
8
- onSaveAll: () => void;
9
- /** Entries on the branch with unsaved changes. */
10
- unsavedCount: number;
11
- /** Why Save all can't act now, other than nothing being unsaved. */
12
- saveAllBlockedReason?: string;
13
- /** Set while Save all runs. */
14
- saveAllProgress: SaveAllProgress | null;
15
- /** A Save of the open entry is in flight. */
16
- saving?: boolean;
17
- /** Rises each time a single Save lands, whichever entry it saved. */
18
- savedSignal?: number;
19
- /** The last Save that landed saved the open entry. */
20
- savedHere?: boolean;
21
- /** The open entry has unsaved changes. */
22
- entryDirty: boolean;
23
- /** Writing drafts to localStorage failed, so they live only in this tab. */
24
- draftStorageFailed: boolean;
25
- }
26
- /**
27
- * Save with a caret offering "Save all". While the open entry has unsaved changes, Save's Tooltip
28
- * and description say they are kept on this device. Only when keeping them failed does the bar
29
- * carry a warning before Save, icon-only below `lg`, its text then its Tooltip and accessible name.
30
- */
31
- export declare function SaveSplitButton({ onSave, saveBlockedReason, busy, onSaveAll, unsavedCount, saveAllBlockedReason, saveAllProgress, saving, savedSignal, savedHere, entryDirty, draftStorageFailed, }: SaveSplitButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -1,121 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useId, useRef, useState } from 'react';
4
- import { Box, Button, Loader, Menu, Text, Tooltip, VisuallyHidden } from '@mantine/core';
5
- import { useReducedMotion } from '@mantine/hooks';
6
- import { IconAlertTriangle, IconCheck, IconChevronDown, IconLoader2 } from '@tabler/icons-react';
7
- import { EDITOR_ACTIONS } from '../copy.js';
8
- import { ReasonedMenuItem } from './ReasonedMenuItem.js';
9
- import { useMenuItemFocusReturn } from '../utils/focus-return.js';
10
- const STORAGE_FAILED_TEXT = 'Unsaved · not kept on this device';
11
- const STORAGE_FAILED_HINT = "Save before you leave or reload, or they'll be lost.";
12
- const KEPT_HINT = 'Unsaved changes are kept on this device until you save.';
13
- // Visually hidden, as Mantine's VisuallyHidden, for a region that must stay mounted.
14
- const HIDDEN_STYLE = {
15
- position: 'absolute',
16
- width: 1,
17
- height: 1,
18
- overflow: 'hidden',
19
- clip: 'rect(0 0 0 0)',
20
- whiteSpace: 'nowrap',
21
- };
22
- const SAVED_SHOWN_MS = 2000;
23
- // Below `sm` the status words are visually hidden, so Save keeps its own width beside an icon.
24
- const LABEL_TEXT_CLASS = 'canopy-save-label-text';
25
- const LABEL_STYLES = `
26
- @media (max-width: 47.99em) {
27
- .${LABEL_TEXT_CLASS} {
28
- position: absolute; width: 1px; height: 1px; overflow: hidden;
29
- clip: rect(0 0 0 0); white-space: nowrap;
30
- }
31
- }
32
- `;
33
- /**
34
- * Every label in one grid cell, only `shown` visible, so the button keeps the widest one's width
35
- * and the top bar never shifts.
36
- */
37
- function SaveLabels({ shown, spinner }) {
38
- const labels = {
39
- save: EDITOR_ACTIONS.save,
40
- saving: (_jsxs(_Fragment, { children: [_jsx(Box, { component: "span", display: "inline-flex", w: 14, children: shown === 'saving' &&
41
- (spinner ? (_jsx(Loader, { size: 14, color: "currentColor" })) : (_jsx(IconLoader2, { size: 14, "aria-hidden": "true" }))) }), _jsx("span", { className: LABEL_TEXT_CLASS, children: "Saving\u2026" })] })),
42
- saved: (_jsxs(_Fragment, { children: [_jsx(IconCheck, { size: 14, "aria-hidden": "true" }), _jsx("span", { className: LABEL_TEXT_CLASS, children: "Saved" })] })),
43
- };
44
- return (_jsx(Box, { component: "span", pos: "relative", style: { display: 'inline-grid' }, children: Object.keys(labels).map((key) => (_jsx(Box, { component: "span", "aria-hidden": key === shown ? undefined : true, style: {
45
- gridArea: '1 / 1',
46
- display: 'inline-flex',
47
- alignItems: 'center',
48
- justifyContent: 'center',
49
- gap: 6,
50
- visibility: key === shown ? 'visible' : 'hidden',
51
- }, children: labels[key] }, key))) }));
52
- }
53
- /**
54
- * Save with a caret offering "Save all". While the open entry has unsaved changes, Save's Tooltip
55
- * and description say they are kept on this device. Only when keeping them failed does the bar
56
- * carry a warning before Save, icon-only below `lg`, its text then its Tooltip and accessible name.
57
- */
58
- export function SaveSplitButton({ onSave, saveBlockedReason, busy, onSaveAll, unsavedCount, saveAllBlockedReason, saveAllProgress, saving = false, savedSignal, savedHere = false, entryDirty, draftStorageFailed, }) {
59
- const caret = useRef(null);
60
- const keptHintId = useId();
61
- const menuFocus = useMenuItemFocusReturn(caret);
62
- const reducedMotion = useReducedMotion();
63
- // Each landed Save shows "Saved" once, as it lands and only on its own entry: one that landed
64
- // before mount or while another entry was open shows nothing later.
65
- const [seenSignal, setSeenSignal] = useState(savedSignal ?? 0);
66
- const [flashing, setFlashing] = useState();
67
- // Leaving the saved entry ends its "Saved", so a quick return doesn't show or announce it again.
68
- const [wasHere, setWasHere] = useState(savedHere);
69
- if (wasHere !== savedHere) {
70
- setWasHere(savedHere);
71
- if (!savedHere)
72
- setFlashing(undefined);
73
- }
74
- if (savedSignal !== undefined && savedSignal > seenSignal) {
75
- setSeenSignal(savedSignal);
76
- setFlashing(savedHere ? savedSignal : undefined);
77
- }
78
- useEffect(() => {
79
- if (flashing === undefined)
80
- return;
81
- const timer = setTimeout(() => setFlashing(undefined), SAVED_SHOWN_MS);
82
- return () => clearTimeout(timer);
83
- }, [flashing]);
84
- // Blocked, Save stays focusable (`aria-disabled`, not `disabled`), so its reason opens on focus.
85
- const saveBlocked = !!saveBlockedReason || busy || !!saveAllProgress;
86
- const label = saving
87
- ? 'saving'
88
- : flashing !== undefined &&
89
- flashing === savedSignal &&
90
- savedHere &&
91
- !entryDirty &&
92
- !saveAllProgress
93
- ? 'saved'
94
- : 'save';
95
- const saveGreyed = saveBlocked && !saveAllProgress && label === 'save';
96
- const storageWarning = entryDirty && draftStorageFailed;
97
- // Why Save is blocked, else where unsaved changes are held; a blocked Save's reason leads.
98
- const keptHint = entryDirty && !draftStorageFailed && !saveBlockedReason ? KEPT_HINT : '';
99
- const saveTooltip = saveBlockedReason || keptHint;
100
- return (_jsxs(_Fragment, { children: [_jsx(Box, { role: "status", ta: "right", w: { base: 20, lg: 112 }, style: storageWarning ? { flexShrink: 0 } : HIDDEN_STYLE, children: storageWarning && (_jsx(Tooltip, { label: `${STORAGE_FAILED_TEXT}. ${STORAGE_FAILED_HINT}`, multiline: true, w: 260, children: _jsxs(Text, { size: "xs", lh: 1.2, c: "var(--canopy-danger-text)", "data-testid": "unsaved-indicator", children: [_jsx(Box, { component: "span", visibleFrom: "lg", children: STORAGE_FAILED_TEXT }), _jsx(Box, { component: "span", hiddenFrom: "lg", display: "inline-flex", "aria-hidden": "true", children: _jsx(IconAlertTriangle, { size: 18 }) }), _jsx(VisuallyHidden, { component: "span", hiddenFrom: "lg", children: STORAGE_FAILED_TEXT }), _jsxs(VisuallyHidden, { component: "span", children: [". ", STORAGE_FAILED_HINT] })] }) })) }), _jsx(VisuallyHidden, { role: "status", children: saveAllProgress
101
- ? `Saving ${saveAllProgress.total} ${saveAllProgress.total === 1 ? 'entry' : 'entries'}…`
102
- : label === 'saved'
103
- ? 'Saved'
104
- : '' }), keptHint && (_jsx("span", { hidden: true, id: keptHintId, children: keptHint })), _jsx("style", { children: LABEL_STYLES }), _jsxs(Button.Group, { style: { flexShrink: 0 }, children: [_jsx(Tooltip, { label: saveTooltip,
105
- // Not beside "Saving…" or "Saved", which already say what happened.
106
- disabled: !saveTooltip || busy || label !== 'save', multiline: true, maw: 280, children: _jsx(Button, { "data-testid": "save-button", variant: "filled", size: "sm", leftSection: saveAllProgress && !reducedMotion ? (_jsx(Loader, { size: 14, color: "currentColor" })) : undefined, onClick: () => {
107
- if (!saveBlocked)
108
- onSave();
109
- }, "data-disabled": saveGreyed ? true : undefined, "aria-disabled": saveBlocked ? true : undefined, ...(keptHint ? { 'aria-describedby': keptHintId } : {}), children: saveAllProgress ? (`Saving ${Math.min(saveAllProgress.done + 1, saveAllProgress.total)} of ${saveAllProgress.total}…`) : (_jsx(SaveLabels, { shown: label, spinner: !reducedMotion })) }) }), _jsxs(Menu, { withinPortal: true, shadow: "sm", position: "bottom-end", ...menuFocus.menu, children: [_jsx(Tooltip, { label: "Save options", events: { hover: true, focus: true, touch: false }, children: _jsx(Menu.Target, { children: _jsx(Button, { ref: caret,
110
- // Light beside a greyed Save, so a blocked split button shows no filled half.
111
- variant: saveGreyed ? 'light' : 'filled', size: "sm", px: 8, "aria-label": "Save options", "data-testid": "save-options-button", style: { borderInlineStart: '1px solid var(--mantine-color-body)' }, children: _jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }) }) }) }), _jsx(Menu.Dropdown, { ...menuFocus.dropdown, children: _jsx(ReasonedMenuItem, { "data-testid": "save-all-menu-item", onClick: onSaveAll, blockedReason: saveAllBlockedReason ??
112
- (saveAllProgress
113
- ? `${EDITOR_ACTIONS.saveAll} is already running.`
114
- : unsavedCount === 0
115
- ? 'No entry on this branch has unsaved changes.'
116
- : busy
117
- ? 'Wait for the current save or load to finish.'
118
- : undefined), children: unsavedCount > 0
119
- ? `${EDITOR_ACTIONS.saveAll} (${unsavedCount})`
120
- : EDITOR_ACTIONS.saveAll }) })] })] })] }));
121
- }