@bendyline/docblocks-react 1.0.0 → 1.1.1

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 (133) hide show
  1. package/dist/AppMenu/AppMenu.d.ts +16 -1
  2. package/dist/AppMenu/AppMenu.d.ts.map +1 -1
  3. package/dist/AppMenu/AppMenu.js +3 -2
  4. package/dist/AppMenu/AppMenu.js.map +1 -1
  5. package/dist/AppMenu/index.d.ts +1 -1
  6. package/dist/AppMenu/index.d.ts.map +1 -1
  7. package/dist/DocBlocksShell/DocBlocksShell.d.ts +21 -1
  8. package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
  9. package/dist/DocBlocksShell/DocBlocksShell.js +930 -98
  10. package/dist/DocBlocksShell/DocBlocksShell.js.map +1 -1
  11. package/dist/Export/ExportDialog.d.ts.map +1 -1
  12. package/dist/Export/ExportDialog.js +104 -5
  13. package/dist/Export/ExportDialog.js.map +1 -1
  14. package/dist/Export/ExportToolbarControls.d.ts.map +1 -1
  15. package/dist/Export/ExportToolbarControls.js +44 -3
  16. package/dist/Export/ExportToolbarControls.js.map +1 -1
  17. package/dist/Export/export-options.d.ts +35 -0
  18. package/dist/Export/export-options.d.ts.map +1 -1
  19. package/dist/Export/export-options.js +6 -1
  20. package/dist/Export/export-options.js.map +1 -1
  21. package/dist/Export/run-export.d.ts.map +1 -1
  22. package/dist/Export/run-export.js +163 -91
  23. package/dist/Export/run-export.js.map +1 -1
  24. package/dist/FileExplorer/FileExplorer.d.ts.map +1 -1
  25. package/dist/FileExplorer/FileExplorer.js +17 -2
  26. package/dist/FileExplorer/FileExplorer.js.map +1 -1
  27. package/dist/FileExplorer/FileTreeNode.d.ts.map +1 -1
  28. package/dist/FileExplorer/FileTreeNode.js +34 -8
  29. package/dist/FileExplorer/FileTreeNode.js.map +1 -1
  30. package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +1 -1
  31. package/dist/WorkspacePicker/WorkspacePicker.js +9 -3
  32. package/dist/WorkspacePicker/WorkspacePicker.js.map +1 -1
  33. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +2 -1
  34. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +1 -1
  35. package/dist/WorkspacePicker/WorkspaceSettingsButton.js +2 -2
  36. package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +1 -1
  37. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts +20 -0
  38. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts.map +1 -0
  39. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js +36 -0
  40. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js.map +1 -0
  41. package/dist/hooks/useAutoSave.d.ts +8 -2
  42. package/dist/hooks/useAutoSave.d.ts.map +1 -1
  43. package/dist/hooks/useAutoSave.js +65 -30
  44. package/dist/hooks/useAutoSave.js.map +1 -1
  45. package/dist/icons.d.ts +1 -0
  46. package/dist/icons.d.ts.map +1 -1
  47. package/dist/icons.js +3 -0
  48. package/dist/icons.js.map +1 -1
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/preferences/versioning.d.ts +27 -0
  52. package/dist/preferences/versioning.d.ts.map +1 -0
  53. package/dist/preferences/versioning.js +62 -0
  54. package/dist/preferences/versioning.js.map +1 -0
  55. package/package.json +10 -8
  56. package/src/AppMenu/AppMenu.tsx +142 -2
  57. package/src/AppMenu/index.ts +1 -1
  58. package/src/DocBlocksShell/DocBlocksShell.tsx +1194 -125
  59. package/src/Export/ExportDialog.tsx +208 -19
  60. package/src/Export/ExportToolbarControls.tsx +45 -3
  61. package/src/Export/export-options.ts +43 -1
  62. package/src/Export/run-export.ts +190 -91
  63. package/src/FileExplorer/FileExplorer.tsx +31 -9
  64. package/src/FileExplorer/FileTreeNode.tsx +68 -26
  65. package/src/WorkspacePicker/WorkspacePicker.tsx +18 -7
  66. package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +9 -0
  67. package/src/WorkspacePicker/WorkspaceSettingsDialog.tsx +121 -0
  68. package/src/fonts/fonts.css +362 -0
  69. package/src/fonts/licenses/LICENSE-cormorant-garamond.txt +93 -0
  70. package/src/fonts/licenses/LICENSE-crimson-text.txt +93 -0
  71. package/src/fonts/licenses/LICENSE-dm-sans.txt +93 -0
  72. package/src/fonts/licenses/LICENSE-dm-serif-display.txt +93 -0
  73. package/src/fonts/licenses/LICENSE-hanken-grotesk.txt +94 -0
  74. package/src/fonts/licenses/LICENSE-ibm-plex-sans.txt +93 -0
  75. package/src/fonts/licenses/LICENSE-inter.txt +93 -0
  76. package/src/fonts/licenses/LICENSE-jetbrains-mono.txt +93 -0
  77. package/src/fonts/licenses/LICENSE-lora.txt +93 -0
  78. package/src/fonts/licenses/LICENSE-merriweather.txt +93 -0
  79. package/src/fonts/licenses/LICENSE-oswald.txt +93 -0
  80. package/src/fonts/licenses/LICENSE-playfair-display.txt +93 -0
  81. package/src/fonts/licenses/LICENSE-pt-serif.txt +93 -0
  82. package/src/fonts/licenses/LICENSE-roboto.txt +93 -0
  83. package/src/fonts/licenses/LICENSE-source-serif-4.txt +93 -0
  84. package/src/fonts/woff2/cormorant-garamond-400.woff2 +0 -0
  85. package/src/fonts/woff2/cormorant-garamond-700.woff2 +0 -0
  86. package/src/fonts/woff2/crimson-text-400.woff2 +0 -0
  87. package/src/fonts/woff2/crimson-text-400i.woff2 +0 -0
  88. package/src/fonts/woff2/crimson-text-700.woff2 +0 -0
  89. package/src/fonts/woff2/dm-sans-400.woff2 +0 -0
  90. package/src/fonts/woff2/dm-sans-400i.woff2 +0 -0
  91. package/src/fonts/woff2/dm-sans-500.woff2 +0 -0
  92. package/src/fonts/woff2/dm-sans-700.woff2 +0 -0
  93. package/src/fonts/woff2/dm-serif-display-400.woff2 +0 -0
  94. package/src/fonts/woff2/hanken-grotesk-400.woff2 +0 -0
  95. package/src/fonts/woff2/hanken-grotesk-400i.woff2 +0 -0
  96. package/src/fonts/woff2/hanken-grotesk-500.woff2 +0 -0
  97. package/src/fonts/woff2/hanken-grotesk-600.woff2 +0 -0
  98. package/src/fonts/woff2/hanken-grotesk-700.woff2 +0 -0
  99. package/src/fonts/woff2/ibm-plex-sans-400.woff2 +0 -0
  100. package/src/fonts/woff2/ibm-plex-sans-400i.woff2 +0 -0
  101. package/src/fonts/woff2/ibm-plex-sans-500.woff2 +0 -0
  102. package/src/fonts/woff2/ibm-plex-sans-600.woff2 +0 -0
  103. package/src/fonts/woff2/ibm-plex-sans-700.woff2 +0 -0
  104. package/src/fonts/woff2/inter-400.woff2 +0 -0
  105. package/src/fonts/woff2/inter-400i.woff2 +0 -0
  106. package/src/fonts/woff2/inter-500.woff2 +0 -0
  107. package/src/fonts/woff2/inter-600.woff2 +0 -0
  108. package/src/fonts/woff2/inter-700.woff2 +0 -0
  109. package/src/fonts/woff2/jetbrains-mono-400.woff2 +0 -0
  110. package/src/fonts/woff2/jetbrains-mono-400i.woff2 +0 -0
  111. package/src/fonts/woff2/jetbrains-mono-700.woff2 +0 -0
  112. package/src/fonts/woff2/lora-400.woff2 +0 -0
  113. package/src/fonts/woff2/lora-400i.woff2 +0 -0
  114. package/src/fonts/woff2/lora-700.woff2 +0 -0
  115. package/src/fonts/woff2/merriweather-400.woff2 +0 -0
  116. package/src/fonts/woff2/merriweather-700.woff2 +0 -0
  117. package/src/fonts/woff2/oswald-400.woff2 +0 -0
  118. package/src/fonts/woff2/oswald-700.woff2 +0 -0
  119. package/src/fonts/woff2/playfair-display-400.woff2 +0 -0
  120. package/src/fonts/woff2/playfair-display-700.woff2 +0 -0
  121. package/src/fonts/woff2/pt-serif-400.woff2 +0 -0
  122. package/src/fonts/woff2/pt-serif-700.woff2 +0 -0
  123. package/src/fonts/woff2/roboto-400.woff2 +0 -0
  124. package/src/fonts/woff2/roboto-400i.woff2 +0 -0
  125. package/src/fonts/woff2/roboto-700.woff2 +0 -0
  126. package/src/fonts/woff2/source-serif-4-400.woff2 +0 -0
  127. package/src/fonts/woff2/source-serif-4-400i.woff2 +0 -0
  128. package/src/fonts/woff2/source-serif-4-700.woff2 +0 -0
  129. package/src/hooks/useAutoSave.ts +87 -29
  130. package/src/icons.tsx +10 -0
  131. package/src/index.ts +1 -1
  132. package/src/preferences/versioning.ts +69 -0
  133. package/src/styles/docblocks.css +1037 -55
Binary file
Binary file
@@ -2,57 +2,115 @@
2
2
  * useAutoSave — debounced auto-save hook.
3
3
  *
4
4
  * Writes content to the FileSystemProvider after a delay
5
- * whenever the content changes.
5
+ * whenever the content changes. Returns a `flush` function so callers
6
+ * (e.g. an explicit Ctrl/Cmd+S handler) can force an immediate write
7
+ * and await its completion.
6
8
  */
7
9
 
8
10
  import { useEffect, useRef, useCallback } from 'react';
9
11
  import type { FileSystemProvider } from '@bendyline/docblocks/filesystem';
10
12
 
13
+ export interface UseAutoSaveResult {
14
+ /** Cancel any pending debounce and write the current content immediately. */
15
+ flush: () => Promise<void>;
16
+ }
17
+
11
18
  export function useAutoSave(
12
19
  provider: FileSystemProvider | null,
13
20
  filePath: string | null,
14
21
  content: string,
15
22
  delayMs = 500,
16
- ): void {
23
+ onSaved?: (filePath: string, content: string) => void,
24
+ ): UseAutoSaveResult {
17
25
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
18
- const lastSavedRef = useRef<string>(content);
19
-
20
- const save = useCallback(async () => {
21
- if (!provider || !filePath) return;
22
- if (content === lastSavedRef.current) return;
23
- lastSavedRef.current = content;
24
- await provider.writeFile(filePath, content);
25
- }, [provider, filePath, content]);
26
-
27
- useEffect(() => {
28
- if (!provider || !filePath) return;
26
+ const contentRef = useRef(content);
27
+ const onSavedRef = useRef(onSaved);
28
+ const targetRef = useRef<{
29
+ provider: FileSystemProvider;
30
+ filePath: string;
31
+ lastSaved: string;
32
+ } | null>(null);
29
33
 
34
+ const clearTimer = useCallback(() => {
30
35
  if (timerRef.current) {
31
36
  clearTimeout(timerRef.current);
37
+ timerRef.current = null;
32
38
  }
39
+ }, []);
33
40
 
34
- timerRef.current = setTimeout(() => {
35
- save();
36
- }, delayMs);
41
+ const flushTarget = useCallback(
42
+ async (
43
+ target: {
44
+ provider: FileSystemProvider;
45
+ filePath: string;
46
+ lastSaved: string;
47
+ },
48
+ nextContent: string,
49
+ ) => {
50
+ if (nextContent === target.lastSaved) return;
51
+ const previous = target.lastSaved;
52
+ target.lastSaved = nextContent;
53
+ try {
54
+ await target.provider.writeFile(target.filePath, nextContent);
55
+ onSavedRef.current?.(target.filePath, nextContent);
56
+ } catch (err) {
57
+ target.lastSaved = previous;
58
+ throw err;
59
+ }
60
+ },
61
+ [],
62
+ );
63
+
64
+ useEffect(() => {
65
+ onSavedRef.current = onSaved;
66
+ }, [onSaved]);
67
+
68
+ useEffect(() => {
69
+ contentRef.current = content;
70
+ }, [content]);
71
+
72
+ useEffect(() => {
73
+ clearTimer();
74
+ targetRef.current = provider && filePath ? { provider, filePath, lastSaved: content } : null;
75
+ contentRef.current = content;
37
76
 
38
77
  return () => {
39
- if (timerRef.current) {
40
- clearTimeout(timerRef.current);
78
+ clearTimer();
79
+ const target = targetRef.current;
80
+ if (target) {
81
+ void flushTarget(target, contentRef.current).catch(() => undefined);
41
82
  }
83
+ targetRef.current = null;
42
84
  };
43
- }, [content, delayMs, save, provider, filePath]);
85
+ // `content` is intentionally captured only when the save target changes.
86
+ // Content edits are handled by the debounced effect below.
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ }, [provider, filePath, clearTimer, flushTarget]);
44
89
 
45
- // Flush on unmount
46
90
  useEffect(() => {
91
+ const target = targetRef.current;
92
+ if (!target) return;
93
+ clearTimer();
94
+
95
+ if (content !== target.lastSaved) {
96
+ timerRef.current = setTimeout(() => {
97
+ const currentTarget = targetRef.current;
98
+ if (!currentTarget) return;
99
+ void flushTarget(currentTarget, contentRef.current).catch(() => undefined);
100
+ }, delayMs);
101
+ }
102
+
47
103
  return () => {
48
- if (timerRef.current) {
49
- clearTimeout(timerRef.current);
50
- }
51
- // Sync save on unmount is best-effort
52
- if (provider && filePath && content !== lastSavedRef.current) {
53
- provider.writeFile(filePath, content);
54
- }
104
+ clearTimer();
55
105
  };
56
- // eslint-disable-next-line react-hooks/exhaustive-deps
57
- }, []);
106
+ }, [content, delayMs, clearTimer, flushTarget]);
107
+
108
+ const flush = useCallback(async () => {
109
+ clearTimer();
110
+ const target = targetRef.current;
111
+ if (!target) return;
112
+ await flushTarget(target, contentRef.current);
113
+ }, [clearTimer, flushTarget]);
114
+
115
+ return { flush };
58
116
  }
package/src/icons.tsx CHANGED
@@ -46,6 +46,16 @@ export function RefreshIcon(props: SVGProps<SVGSVGElement>) {
46
46
  );
47
47
  }
48
48
 
49
+ export function MoreIcon(props: SVGProps<SVGSVGElement>) {
50
+ return (
51
+ <svg {...defaults} {...props}>
52
+ <circle cx="3.5" cy="8" r="1" fill="currentColor" stroke="none" />
53
+ <circle cx="8" cy="8" r="1" fill="currentColor" stroke="none" />
54
+ <circle cx="12.5" cy="8" r="1" fill="currentColor" stroke="none" />
55
+ </svg>
56
+ );
57
+ }
58
+
49
59
  export function WorkspaceIcon(props: SVGProps<SVGSVGElement>) {
50
60
  return (
51
61
  <svg {...defaults} {...props}>
package/src/index.ts CHANGED
@@ -19,7 +19,7 @@ export type { DocBlocksShellProps } from './DocBlocksShell/index.js';
19
19
 
20
20
  // AppMenu
21
21
  export { AppMenu } from './AppMenu/index.js';
22
- export type { AppMenuProps } from './AppMenu/index.js';
22
+ export type { AppMenuProps, ThemePreference } from './AppMenu/index.js';
23
23
 
24
24
  // Export
25
25
  export { ExportToolbarControls } from './Export/index.js';
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Versioning preferences — global default + per-workspace resolution.
3
+ *
4
+ * The global preference controls whether DocBlocks writes version
5
+ * snapshots (under `<basename>_files/.versions/`) when editing. The
6
+ * default is `'browser-only'` so local-folder workspaces stay clean by
7
+ * default and only IndexedDB workspaces accumulate version history.
8
+ *
9
+ * Each workspace can override the global default with its own setting
10
+ * stored on the WorkspaceDescriptor. `'inherit'` (or absent) defers to
11
+ * the global preference; `'on'` / `'off'` force the behavior regardless.
12
+ */
13
+
14
+ import type { WorkspaceDescriptor } from '@bendyline/docblocks/workspace';
15
+
16
+ export type VersioningPreference = 'on' | 'browser-only' | 'off';
17
+
18
+ const STORAGE_KEY = 'docblocks:versioningPreference';
19
+ const DEFAULT_PREFERENCE: VersioningPreference = 'browser-only';
20
+
21
+ export function loadVersioningPreference(): VersioningPreference {
22
+ try {
23
+ const raw = localStorage.getItem(STORAGE_KEY);
24
+ if (raw === 'on' || raw === 'browser-only' || raw === 'off') return raw;
25
+ } catch {
26
+ // ignore
27
+ }
28
+ return DEFAULT_PREFERENCE;
29
+ }
30
+
31
+ export function saveVersioningPreference(value: VersioningPreference): void {
32
+ try {
33
+ localStorage.setItem(STORAGE_KEY, value);
34
+ } catch {
35
+ // ignore quota errors
36
+ }
37
+ }
38
+
39
+ /** Whether a workspace type is "local" (writes to a real folder on disk). */
40
+ export function isLocalWorkspaceType(type: WorkspaceDescriptor['type']): boolean {
41
+ return type === 'native' || type === 'electron-native';
42
+ }
43
+
44
+ /**
45
+ * Compute the effective versioning state for a workspace, honoring the
46
+ * per-workspace override first and falling back to the global preference.
47
+ *
48
+ * Returns `false` whenever no workspace is provided so callers can use
49
+ * this directly during initial render before a workspace loads.
50
+ */
51
+ export function resolveVersioningEnabled(
52
+ workspace: WorkspaceDescriptor | null,
53
+ globalPref: VersioningPreference,
54
+ ): boolean {
55
+ if (!workspace) return false;
56
+
57
+ const override = workspace.versioningOverride;
58
+ if (override === 'on') return true;
59
+ if (override === 'off') return false;
60
+
61
+ switch (globalPref) {
62
+ case 'on':
63
+ return true;
64
+ case 'off':
65
+ return false;
66
+ case 'browser-only':
67
+ return !isLocalWorkspaceType(workspace.type);
68
+ }
69
+ }