@bendyline/docblocks-react 0.1.0 → 1.1.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.
- package/LICENSE +21 -21
- package/dist/AppMenu/AppMenu.d.ts +6 -1
- package/dist/AppMenu/AppMenu.d.ts.map +1 -1
- package/dist/AppMenu/AppMenu.js +3 -2
- package/dist/AppMenu/AppMenu.js.map +1 -1
- package/dist/AppMenu/index.d.ts +1 -1
- package/dist/AppMenu/index.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.js +410 -73
- package/dist/DocBlocksShell/DocBlocksShell.js.map +1 -1
- package/dist/FileExplorer/FileTreeNode.d.ts.map +1 -1
- package/dist/FileExplorer/FileTreeNode.js +34 -8
- package/dist/FileExplorer/FileTreeNode.js.map +1 -1
- package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +1 -1
- package/dist/WorkspacePicker/WorkspacePicker.js +9 -3
- package/dist/WorkspacePicker/WorkspacePicker.js.map +1 -1
- package/dist/icons.d.ts +1 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +3 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/AppMenu/AppMenu.tsx +78 -1
- package/src/AppMenu/index.ts +1 -1
- package/src/DocBlocksShell/DocBlocksShell.tsx +480 -87
- package/src/FileExplorer/FileTreeNode.tsx +68 -26
- package/src/WorkspacePicker/WorkspacePicker.tsx +18 -7
- package/src/fonts/fonts.css +362 -0
- package/src/fonts/licenses/LICENSE-cormorant-garamond.txt +93 -0
- package/src/fonts/licenses/LICENSE-crimson-text.txt +93 -0
- package/src/fonts/licenses/LICENSE-dm-sans.txt +93 -0
- package/src/fonts/licenses/LICENSE-dm-serif-display.txt +93 -0
- package/src/fonts/licenses/LICENSE-hanken-grotesk.txt +94 -0
- package/src/fonts/licenses/LICENSE-ibm-plex-sans.txt +93 -0
- package/src/fonts/licenses/LICENSE-inter.txt +93 -0
- package/src/fonts/licenses/LICENSE-jetbrains-mono.txt +93 -0
- package/src/fonts/licenses/LICENSE-lora.txt +93 -0
- package/src/fonts/licenses/LICENSE-merriweather.txt +93 -0
- package/src/fonts/licenses/LICENSE-oswald.txt +93 -0
- package/src/fonts/licenses/LICENSE-playfair-display.txt +93 -0
- package/src/fonts/licenses/LICENSE-pt-serif.txt +93 -0
- package/src/fonts/licenses/LICENSE-roboto.txt +93 -0
- package/src/fonts/licenses/LICENSE-source-serif-4.txt +93 -0
- package/src/fonts/woff2/cormorant-garamond-400.woff2 +0 -0
- package/src/fonts/woff2/cormorant-garamond-700.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-400.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-400i.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-700.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-400.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-400i.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-500.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-700.woff2 +0 -0
- package/src/fonts/woff2/dm-serif-display-400.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-400.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-400i.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-500.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-600.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-700.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-400.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-400i.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-500.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-600.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-700.woff2 +0 -0
- package/src/fonts/woff2/inter-400.woff2 +0 -0
- package/src/fonts/woff2/inter-400i.woff2 +0 -0
- package/src/fonts/woff2/inter-500.woff2 +0 -0
- package/src/fonts/woff2/inter-600.woff2 +0 -0
- package/src/fonts/woff2/inter-700.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-400.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-400i.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-700.woff2 +0 -0
- package/src/fonts/woff2/lora-400.woff2 +0 -0
- package/src/fonts/woff2/lora-400i.woff2 +0 -0
- package/src/fonts/woff2/lora-700.woff2 +0 -0
- package/src/fonts/woff2/merriweather-400.woff2 +0 -0
- package/src/fonts/woff2/merriweather-700.woff2 +0 -0
- package/src/fonts/woff2/oswald-400.woff2 +0 -0
- package/src/fonts/woff2/oswald-700.woff2 +0 -0
- package/src/fonts/woff2/playfair-display-400.woff2 +0 -0
- package/src/fonts/woff2/playfair-display-700.woff2 +0 -0
- package/src/fonts/woff2/pt-serif-400.woff2 +0 -0
- package/src/fonts/woff2/pt-serif-700.woff2 +0 -0
- package/src/fonts/woff2/roboto-400.woff2 +0 -0
- package/src/fonts/woff2/roboto-400i.woff2 +0 -0
- package/src/fonts/woff2/roboto-700.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-400.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-400i.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-700.woff2 +0 -0
- package/src/icons.tsx +10 -0
- package/src/index.ts +1 -1
- package/src/styles/docblocks.css +128 -13
|
@@ -9,8 +9,8 @@ import { useState, useCallback, useEffect, useRef } from 'react';
|
|
|
9
9
|
import { EditorShell } from '@bendyline/squisq-editor-react';
|
|
10
10
|
import '@bendyline/squisq-editor-react/styles';
|
|
11
11
|
import { MediaContext } from '@bendyline/squisq-react';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { IndexedDBFileSystemProvider, ElectronFileSystemProvider, FileSystemContentContainer, createFileMediaProvider, openNativeFolder, restoreNativeFolder, removeDirectoryHandle, } from '@bendyline/docblocks/filesystem';
|
|
13
|
+
import { isElectronHost, getDocblocksHost } from '@bendyline/docblocks/host';
|
|
14
14
|
import { ensureDefaultWorkspace, getWorkspace, listWorkspaces, removeWorkspace, saveWorkspace, touchWorkspace, } from '@bendyline/docblocks/workspace';
|
|
15
15
|
import { AppMenu } from '../AppMenu/AppMenu.js';
|
|
16
16
|
import { FileExplorer } from '../FileExplorer/FileExplorer.js';
|
|
@@ -50,6 +50,56 @@ function parseHash() {
|
|
|
50
50
|
filePath: '/' + decodeURIComponent(raw.slice(slashIdx + 1)),
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
+
const LAST_STATE_KEY = 'docblocks:lastState';
|
|
54
|
+
function saveLastState(state) {
|
|
55
|
+
try {
|
|
56
|
+
localStorage.setItem(LAST_STATE_KEY, JSON.stringify(state));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// ignore quota errors
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function loadLastState() {
|
|
63
|
+
try {
|
|
64
|
+
const raw = localStorage.getItem(LAST_STATE_KEY);
|
|
65
|
+
if (!raw)
|
|
66
|
+
return null;
|
|
67
|
+
return JSON.parse(raw);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const THEME_PREF_KEY = 'docblocks:themePreference';
|
|
74
|
+
function loadThemePreference() {
|
|
75
|
+
try {
|
|
76
|
+
const raw = localStorage.getItem(THEME_PREF_KEY);
|
|
77
|
+
if (raw === 'light' || raw === 'dark' || raw === 'auto')
|
|
78
|
+
return raw;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// ignore
|
|
82
|
+
}
|
|
83
|
+
return 'auto';
|
|
84
|
+
}
|
|
85
|
+
function saveThemePreference(pref) {
|
|
86
|
+
try {
|
|
87
|
+
localStorage.setItem(THEME_PREF_KEY, pref);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// ignore quota errors
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function dirnameOf(p) {
|
|
94
|
+
const clean = p.replace(/^\/+/, '');
|
|
95
|
+
const idx = clean.lastIndexOf('/');
|
|
96
|
+
return idx === -1 ? '' : clean.slice(0, idx);
|
|
97
|
+
}
|
|
98
|
+
function basenameOf(p) {
|
|
99
|
+
const clean = p.replace(/^\/+/, '');
|
|
100
|
+
const idx = clean.lastIndexOf('/');
|
|
101
|
+
return idx === -1 ? clean : clean.slice(idx + 1);
|
|
102
|
+
}
|
|
53
103
|
function useIsMobile(breakpoint = 768) {
|
|
54
104
|
const [isMobile, setIsMobile] = useState(() => typeof window !== 'undefined' && window.matchMedia(`(max-width: ${breakpoint}px)`).matches);
|
|
55
105
|
useEffect(() => {
|
|
@@ -60,9 +110,22 @@ function useIsMobile(breakpoint = 768) {
|
|
|
60
110
|
}, [breakpoint]);
|
|
61
111
|
return isMobile;
|
|
62
112
|
}
|
|
63
|
-
|
|
113
|
+
function FolderGlyph() {
|
|
114
|
+
return (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M3 7a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z" }) }));
|
|
115
|
+
}
|
|
116
|
+
function FileGlyph() {
|
|
117
|
+
return (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M6 3h8l5 5v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" }), _jsx("path", { d: "M14 3v5h5" })] }));
|
|
118
|
+
}
|
|
119
|
+
export function DocBlocksShell({ theme: _themeProp = 'auto', logoUrl }) {
|
|
64
120
|
const osTheme = useOsTheme();
|
|
65
|
-
const
|
|
121
|
+
const [themePreference, setThemePreference] = useState(loadThemePreference);
|
|
122
|
+
// "System default" (auto) always follows the OS — the host's theme prop
|
|
123
|
+
// is kept only for API back-compat and does not override OS detection.
|
|
124
|
+
const resolvedTheme = themePreference === 'light' || themePreference === 'dark' ? themePreference : osTheme;
|
|
125
|
+
const handleThemeChange = useCallback((pref) => {
|
|
126
|
+
setThemePreference(pref);
|
|
127
|
+
saveThemePreference(pref);
|
|
128
|
+
}, []);
|
|
66
129
|
const isMobile = useIsMobile();
|
|
67
130
|
const [mobileShowEditor, setMobileShowEditor] = useState(false);
|
|
68
131
|
const [provider, setProvider] = useState(null);
|
|
@@ -76,15 +139,14 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
76
139
|
const [initialView, setInitialView] = useState('wysiwyg');
|
|
77
140
|
/** Suppress popstate handling during programmatic navigation. */
|
|
78
141
|
const skipPopState = useRef(false);
|
|
79
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Per-file media container: for `notes.md`, images live in
|
|
144
|
+
* `notes_files/` next to the markdown. Created lazily on first write,
|
|
145
|
+
* picked up automatically if it already exists. Set up by the effect
|
|
146
|
+
* below whenever the selected file (or backing provider) changes.
|
|
147
|
+
*/
|
|
80
148
|
const mediaContainerRef = useRef(null);
|
|
81
149
|
const [mediaProvider, setMediaProvider] = useState(null);
|
|
82
|
-
/** Set up persistent media container for a workspace. */
|
|
83
|
-
const setupMediaContainer = useCallback((workspaceId) => {
|
|
84
|
-
const mc = new IndexedDBContentContainer(workspaceId);
|
|
85
|
-
mediaContainerRef.current = mc;
|
|
86
|
-
setMediaProvider(createMediaProviderFromContainer(mc));
|
|
87
|
-
}, []);
|
|
88
150
|
/** Push a new history entry with the given hash. */
|
|
89
151
|
const pushHash = useCallback((wsId, filePath) => {
|
|
90
152
|
const hash = buildHash(wsId, filePath);
|
|
@@ -93,17 +155,28 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
93
155
|
window.history.pushState(null, '', hash);
|
|
94
156
|
}
|
|
95
157
|
}, []);
|
|
96
|
-
/** Open a workspace (and optionally a file) from identifiers.
|
|
97
|
-
|
|
158
|
+
/** Open a workspace (and optionally a file) from identifiers.
|
|
159
|
+
* Returns the FileSystemProvider on success, or null on failure. */
|
|
160
|
+
const openFromIds = useCallback(async (wsId, filePath, push, view) => {
|
|
98
161
|
const workspaces = await listWorkspaces();
|
|
99
162
|
const ws = workspaces.find((w) => w.id === wsId);
|
|
100
163
|
if (!ws)
|
|
101
|
-
return
|
|
164
|
+
return null;
|
|
102
165
|
let fsProvider = null;
|
|
103
|
-
if (ws.type === 'native') {
|
|
166
|
+
if (ws.type === 'electron-native') {
|
|
167
|
+
if (!ws.rootPath)
|
|
168
|
+
return null;
|
|
169
|
+
await getDocblocksHost().workspaces.register({
|
|
170
|
+
id: ws.id,
|
|
171
|
+
name: ws.name,
|
|
172
|
+
rootPath: ws.rootPath,
|
|
173
|
+
});
|
|
174
|
+
fsProvider = new ElectronFileSystemProvider(ws.id, ws.name, ws.rootPath);
|
|
175
|
+
}
|
|
176
|
+
else if (ws.type === 'native') {
|
|
104
177
|
const restored = await restoreNativeFolder(ws.id);
|
|
105
178
|
if (!restored)
|
|
106
|
-
return
|
|
179
|
+
return null;
|
|
107
180
|
fsProvider = restored;
|
|
108
181
|
}
|
|
109
182
|
else {
|
|
@@ -112,7 +185,6 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
112
185
|
await touchWorkspace(ws.id);
|
|
113
186
|
setProvider(fsProvider);
|
|
114
187
|
setActiveWorkspaceId(ws.id);
|
|
115
|
-
setupMediaContainer(ws.id);
|
|
116
188
|
if (filePath) {
|
|
117
189
|
const content = await fsProvider.readFile(filePath);
|
|
118
190
|
if (content !== null) {
|
|
@@ -120,8 +192,10 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
120
192
|
setSelectedFolder(null);
|
|
121
193
|
setFolderEntries([]);
|
|
122
194
|
setEditorContent(content);
|
|
123
|
-
|
|
195
|
+
const effectiveView = view ?? 'wysiwyg';
|
|
196
|
+
setInitialView(effectiveView);
|
|
124
197
|
setEditorKey((k) => k + 1);
|
|
198
|
+
saveLastState({ workspaceId: wsId, filePath, view: effectiveView });
|
|
125
199
|
}
|
|
126
200
|
else {
|
|
127
201
|
setSelectedFile(null);
|
|
@@ -141,22 +215,24 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
141
215
|
if (push) {
|
|
142
216
|
pushHash(ws.id, filePath);
|
|
143
217
|
}
|
|
144
|
-
return
|
|
145
|
-
}, [pushHash
|
|
218
|
+
return fsProvider;
|
|
219
|
+
}, [pushHash]);
|
|
146
220
|
const seedWelcomeFile = useCallback(async (fs) => {
|
|
147
221
|
const entries = await fs.readDirectory('/');
|
|
148
222
|
// If the only file is the welcome doc, auto-select it
|
|
149
223
|
if (entries.length === 1 &&
|
|
150
224
|
entries[0].kind === 'file' &&
|
|
151
|
-
entries[0].path === '
|
|
152
|
-
const
|
|
225
|
+
entries[0].path.replace(/^\//, '') === 'aboutDocblocks.md') {
|
|
226
|
+
const aboutPath = entries[0].path;
|
|
227
|
+
const content = await fs.readFile(aboutPath);
|
|
153
228
|
if (content !== null) {
|
|
154
|
-
setSelectedFile(
|
|
229
|
+
setSelectedFile(aboutPath);
|
|
155
230
|
setEditorContent(content);
|
|
156
231
|
setInitialView('preview');
|
|
157
232
|
setEditorKey((k) => k + 1);
|
|
158
233
|
setExplorerKey((k) => k + 1);
|
|
159
|
-
pushHash(fs.id,
|
|
234
|
+
pushHash(fs.id, aboutPath);
|
|
235
|
+
saveLastState({ workspaceId: fs.id, filePath: aboutPath, view: 'preview' });
|
|
160
236
|
}
|
|
161
237
|
return;
|
|
162
238
|
}
|
|
@@ -193,6 +269,7 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
193
269
|
setEditorKey((k) => k + 1);
|
|
194
270
|
setExplorerKey((k) => k + 1);
|
|
195
271
|
pushHash(fs.id, welcomePath);
|
|
272
|
+
saveLastState({ workspaceId: fs.id, filePath: welcomePath, view: 'preview' });
|
|
196
273
|
}, [pushHash]);
|
|
197
274
|
// Initialise workspace on mount — restore from hash or last-used
|
|
198
275
|
useEffect(() => {
|
|
@@ -200,23 +277,55 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
200
277
|
// Try restoring from URL hash first
|
|
201
278
|
const hashState = parseHash();
|
|
202
279
|
if (hashState) {
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
280
|
+
const restoredProvider = await openFromIds(hashState.workspaceId, hashState.filePath, false);
|
|
281
|
+
if (restoredProvider) {
|
|
282
|
+
// If the hash had no file, check whether we should auto-select the welcome doc
|
|
283
|
+
if (!hashState.filePath) {
|
|
284
|
+
await seedWelcomeFile(restoredProvider);
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Try restoring last viewed document from localStorage
|
|
290
|
+
const lastState = loadLastState();
|
|
291
|
+
if (lastState) {
|
|
292
|
+
const restoredProvider = await openFromIds(lastState.workspaceId, lastState.filePath, true, lastState.view);
|
|
293
|
+
if (restoredProvider)
|
|
205
294
|
return;
|
|
206
295
|
}
|
|
207
296
|
let fsProvider = null;
|
|
297
|
+
const electron = isElectronHost();
|
|
208
298
|
const workspaces = await listWorkspaces();
|
|
299
|
+
// On desktop, hide web-only workspaces (indexeddb/native) — only
|
|
300
|
+
// folder-based workspaces are valid.
|
|
301
|
+
const candidates = electron
|
|
302
|
+
? workspaces.filter((w) => w.type === 'electron-native')
|
|
303
|
+
: workspaces.filter((w) => w.type !== 'electron-native');
|
|
209
304
|
// Pick the most recently opened workspace
|
|
210
|
-
const sorted = [...
|
|
305
|
+
const sorted = [...candidates].sort((a, b) => (b.lastOpened ?? '').localeCompare(a.lastOpened ?? ''));
|
|
211
306
|
for (const ws of sorted) {
|
|
212
|
-
if (ws.type === 'native') {
|
|
307
|
+
if (ws.type === 'electron-native') {
|
|
308
|
+
if (!ws.rootPath)
|
|
309
|
+
continue;
|
|
310
|
+
await getDocblocksHost().workspaces.register({
|
|
311
|
+
id: ws.id,
|
|
312
|
+
name: ws.name,
|
|
313
|
+
rootPath: ws.rootPath,
|
|
314
|
+
});
|
|
315
|
+
const p = new ElectronFileSystemProvider(ws.id, ws.name, ws.rootPath);
|
|
316
|
+
await touchWorkspace(ws.id);
|
|
317
|
+
fsProvider = p;
|
|
318
|
+
setProvider(p);
|
|
319
|
+
setActiveWorkspaceId(ws.id);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
else if (ws.type === 'native') {
|
|
213
323
|
const restored = await restoreNativeFolder(ws.id);
|
|
214
324
|
if (restored) {
|
|
215
325
|
await touchWorkspace(ws.id);
|
|
216
326
|
fsProvider = restored;
|
|
217
327
|
setProvider(restored);
|
|
218
328
|
setActiveWorkspaceId(ws.id);
|
|
219
|
-
setupMediaContainer(ws.id);
|
|
220
329
|
break;
|
|
221
330
|
}
|
|
222
331
|
}
|
|
@@ -226,25 +335,42 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
226
335
|
fsProvider = p;
|
|
227
336
|
setProvider(p);
|
|
228
337
|
setActiveWorkspaceId(ws.id);
|
|
229
|
-
setupMediaContainer(ws.id);
|
|
230
338
|
break;
|
|
231
339
|
}
|
|
232
340
|
}
|
|
233
341
|
if (!fsProvider) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
342
|
+
if (electron) {
|
|
343
|
+
// Desktop: ask the host for the default folder workspace
|
|
344
|
+
// (creates ~/Documents/DocBlocks on first launch).
|
|
345
|
+
const info = await getDocblocksHost().workspaces.getDefault();
|
|
346
|
+
const descriptor = {
|
|
347
|
+
id: info.id,
|
|
348
|
+
name: info.name,
|
|
349
|
+
type: 'electron-native',
|
|
350
|
+
rootPath: info.rootPath,
|
|
351
|
+
lastOpened: new Date().toISOString(),
|
|
352
|
+
};
|
|
353
|
+
await saveWorkspace(descriptor);
|
|
354
|
+
const p = new ElectronFileSystemProvider(info.id, info.name, info.rootPath);
|
|
355
|
+
fsProvider = p;
|
|
356
|
+
setProvider(p);
|
|
357
|
+
setActiveWorkspaceId(info.id);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
// Web: create the IndexedDB default workspace
|
|
361
|
+
const defaultWs = await ensureDefaultWorkspace();
|
|
362
|
+
const p = new IndexedDBFileSystemProvider(defaultWs.id, defaultWs.name);
|
|
363
|
+
fsProvider = p;
|
|
364
|
+
setProvider(p);
|
|
365
|
+
setActiveWorkspaceId(defaultWs.id);
|
|
366
|
+
}
|
|
241
367
|
}
|
|
242
368
|
// Set initial hash
|
|
243
369
|
pushHash(fsProvider.id, null);
|
|
244
370
|
// Seed welcome file if workspace is empty
|
|
245
371
|
await seedWelcomeFile(fsProvider);
|
|
246
372
|
})();
|
|
247
|
-
}, [openFromIds, pushHash, seedWelcomeFile
|
|
373
|
+
}, [openFromIds, pushHash, seedWelcomeFile]);
|
|
248
374
|
// Handle browser back/forward
|
|
249
375
|
useEffect(() => {
|
|
250
376
|
const onPopState = () => {
|
|
@@ -260,23 +386,86 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
260
386
|
window.addEventListener('popstate', onPopState);
|
|
261
387
|
return () => window.removeEventListener('popstate', onPopState);
|
|
262
388
|
}, [openFromIds]);
|
|
389
|
+
// Track view mode changes (Editor/Raw/Play tabs) and persist to localStorage
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
const handler = (e) => {
|
|
392
|
+
const target = e.target.closest?.('[data-view]');
|
|
393
|
+
if (target) {
|
|
394
|
+
const view = target.getAttribute('data-view');
|
|
395
|
+
if (view && activeWorkspaceId && selectedFile) {
|
|
396
|
+
saveLastState({ workspaceId: activeWorkspaceId, filePath: selectedFile, view });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
window.addEventListener('click', handler, true);
|
|
401
|
+
return () => window.removeEventListener('click', handler, true);
|
|
402
|
+
}, [activeWorkspaceId, selectedFile]);
|
|
263
403
|
// Auto-save current file
|
|
264
404
|
useAutoSave(provider, selectedFile, editorContent);
|
|
405
|
+
// Per-file media: for `notes.md` images live in `notes_files/` beside it.
|
|
406
|
+
// Rebuilds whenever the provider or selected file changes.
|
|
407
|
+
useEffect(() => {
|
|
408
|
+
if (!provider || !selectedFile) {
|
|
409
|
+
mediaContainerRef.current = null;
|
|
410
|
+
setMediaProvider(null);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const parentDir = dirnameOf(selectedFile);
|
|
414
|
+
const base = basenameOf(selectedFile);
|
|
415
|
+
const container = new FileSystemContentContainer(provider, parentDir);
|
|
416
|
+
const mp = createFileMediaProvider(container, base);
|
|
417
|
+
mediaContainerRef.current = container;
|
|
418
|
+
setMediaProvider(mp);
|
|
419
|
+
return () => {
|
|
420
|
+
mp.dispose();
|
|
421
|
+
};
|
|
422
|
+
}, [provider, selectedFile]);
|
|
423
|
+
// React to external file changes watched by the Electron host (chokidar).
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
if (!isElectronHost())
|
|
426
|
+
return;
|
|
427
|
+
if (!provider || !(provider instanceof ElectronFileSystemProvider))
|
|
428
|
+
return;
|
|
429
|
+
const unwatch = provider.watch(() => {
|
|
430
|
+
setExplorerKey((k) => k + 1);
|
|
431
|
+
// If the open file's contents changed on disk, reload it (best-effort).
|
|
432
|
+
if (selectedFile) {
|
|
433
|
+
(async () => {
|
|
434
|
+
const content = await provider.readFile(selectedFile);
|
|
435
|
+
if (content !== null && content !== editorContent) {
|
|
436
|
+
setEditorContent(content);
|
|
437
|
+
setEditorKey((k) => k + 1);
|
|
438
|
+
}
|
|
439
|
+
})();
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
return unwatch;
|
|
443
|
+
}, [provider, selectedFile, editorContent]);
|
|
265
444
|
const handleWorkspaceSelect = useCallback(async (ws) => {
|
|
266
445
|
await touchWorkspace(ws.id);
|
|
267
|
-
|
|
446
|
+
let nextProvider = null;
|
|
447
|
+
if (ws.type === 'electron-native') {
|
|
448
|
+
if (!ws.rootPath)
|
|
449
|
+
return;
|
|
450
|
+
await getDocblocksHost().workspaces.register({
|
|
451
|
+
id: ws.id,
|
|
452
|
+
name: ws.name,
|
|
453
|
+
rootPath: ws.rootPath,
|
|
454
|
+
});
|
|
455
|
+
nextProvider = new ElectronFileSystemProvider(ws.id, ws.name, ws.rootPath);
|
|
456
|
+
}
|
|
457
|
+
else if (ws.type === 'native') {
|
|
268
458
|
const restored = await restoreNativeFolder(ws.id);
|
|
269
|
-
if (restored) {
|
|
270
|
-
setProvider(restored);
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
459
|
+
if (!restored) {
|
|
273
460
|
// Permission denied or handle lost — fall through without changing provider
|
|
274
461
|
return;
|
|
275
462
|
}
|
|
463
|
+
nextProvider = restored;
|
|
276
464
|
}
|
|
277
465
|
else {
|
|
278
|
-
|
|
466
|
+
nextProvider = new IndexedDBFileSystemProvider(ws.id, ws.name);
|
|
279
467
|
}
|
|
468
|
+
setProvider(nextProvider);
|
|
280
469
|
setActiveWorkspaceId(ws.id);
|
|
281
470
|
setSelectedFile(null);
|
|
282
471
|
setSelectedFolder(null);
|
|
@@ -284,10 +473,32 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
284
473
|
setEditorContent('');
|
|
285
474
|
setEditorKey((k) => k + 1);
|
|
286
475
|
pushHash(ws.id, null);
|
|
287
|
-
|
|
288
|
-
}, [pushHash, setupMediaContainer]);
|
|
476
|
+
}, [pushHash]);
|
|
289
477
|
const handleOpenFolder = useCallback(async () => {
|
|
290
478
|
try {
|
|
479
|
+
if (isElectronHost()) {
|
|
480
|
+
const info = await getDocblocksHost().workspaces.pickFolder();
|
|
481
|
+
if (!info)
|
|
482
|
+
return; // user cancelled
|
|
483
|
+
const descriptor = {
|
|
484
|
+
id: info.id,
|
|
485
|
+
name: info.name,
|
|
486
|
+
type: 'electron-native',
|
|
487
|
+
rootPath: info.rootPath,
|
|
488
|
+
lastOpened: new Date().toISOString(),
|
|
489
|
+
};
|
|
490
|
+
await saveWorkspace(descriptor);
|
|
491
|
+
const provider = new ElectronFileSystemProvider(info.id, info.name, info.rootPath);
|
|
492
|
+
setProvider(provider);
|
|
493
|
+
setActiveWorkspaceId(descriptor.id);
|
|
494
|
+
setSelectedFile(null);
|
|
495
|
+
setSelectedFolder(null);
|
|
496
|
+
setFolderEntries([]);
|
|
497
|
+
setEditorContent('');
|
|
498
|
+
setEditorKey((k) => k + 1);
|
|
499
|
+
pushHash(descriptor.id, null);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
291
502
|
const nativeProvider = await openNativeFolder();
|
|
292
503
|
const descriptor = {
|
|
293
504
|
id: nativeProvider.id,
|
|
@@ -304,12 +515,104 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
304
515
|
setEditorContent('');
|
|
305
516
|
setEditorKey((k) => k + 1);
|
|
306
517
|
pushHash(descriptor.id, null);
|
|
307
|
-
setupMediaContainer(descriptor.id);
|
|
308
518
|
}
|
|
309
519
|
catch {
|
|
310
520
|
// User cancelled or API not supported
|
|
311
521
|
}
|
|
312
|
-
}, [pushHash
|
|
522
|
+
}, [pushHash]);
|
|
523
|
+
const handleNewFile = useCallback(async () => {
|
|
524
|
+
if (!provider)
|
|
525
|
+
return;
|
|
526
|
+
const name = prompt('New document name:', 'Untitled.md');
|
|
527
|
+
if (!name)
|
|
528
|
+
return;
|
|
529
|
+
const filename = /\.[a-zA-Z0-9]+$/.test(name) ? name : `${name}.md`;
|
|
530
|
+
await provider.writeFile(filename, '# ' + filename.replace(/\.[^.]+$/, '') + '\n');
|
|
531
|
+
setSelectedFile('/' + filename);
|
|
532
|
+
setEditorContent('# ' + filename.replace(/\.[^.]+$/, '') + '\n');
|
|
533
|
+
setInitialView('wysiwyg');
|
|
534
|
+
setEditorKey((k) => k + 1);
|
|
535
|
+
setExplorerKey((k) => k + 1);
|
|
536
|
+
if (activeWorkspaceId) {
|
|
537
|
+
pushHash(activeWorkspaceId, '/' + filename);
|
|
538
|
+
}
|
|
539
|
+
}, [provider, activeWorkspaceId, pushHash]);
|
|
540
|
+
const handleRevealWorkspace = useCallback(async () => {
|
|
541
|
+
if (!isElectronHost() || !activeWorkspaceId)
|
|
542
|
+
return;
|
|
543
|
+
const ws = await getWorkspace(activeWorkspaceId);
|
|
544
|
+
if (ws?.type === 'electron-native' && ws.rootPath) {
|
|
545
|
+
await getDocblocksHost().shell.revealInFolder(ws.rootPath);
|
|
546
|
+
}
|
|
547
|
+
}, [activeWorkspaceId]);
|
|
548
|
+
// Subscribe to native menu commands (Electron host).
|
|
549
|
+
useEffect(() => {
|
|
550
|
+
if (!isElectronHost())
|
|
551
|
+
return;
|
|
552
|
+
const host = getDocblocksHost();
|
|
553
|
+
return host.onMenuCommand((cmd) => {
|
|
554
|
+
switch (cmd) {
|
|
555
|
+
case 'file:new':
|
|
556
|
+
handleNewFile();
|
|
557
|
+
break;
|
|
558
|
+
case 'file:openFolder':
|
|
559
|
+
handleOpenFolder();
|
|
560
|
+
break;
|
|
561
|
+
case 'file:revealWorkspace':
|
|
562
|
+
handleRevealWorkspace();
|
|
563
|
+
break;
|
|
564
|
+
case 'help:viewOnGitHub':
|
|
565
|
+
host.shell.openExternal('https://github.com/bendyline/docblocks');
|
|
566
|
+
break;
|
|
567
|
+
case 'help:checkForUpdates':
|
|
568
|
+
host.updater.checkForUpdates().catch(() => {
|
|
569
|
+
// errors surface via updater.onStatus
|
|
570
|
+
});
|
|
571
|
+
break;
|
|
572
|
+
case 'help:about':
|
|
573
|
+
(async () => {
|
|
574
|
+
const version = await host.updater.getVersion();
|
|
575
|
+
alert(`DocBlocks ${version}`);
|
|
576
|
+
})();
|
|
577
|
+
break;
|
|
578
|
+
default:
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
}, [handleNewFile, handleOpenFolder, handleRevealWorkspace]);
|
|
583
|
+
// Subscribe to open-file / deep-link requests from the OS.
|
|
584
|
+
useEffect(() => {
|
|
585
|
+
if (!isElectronHost())
|
|
586
|
+
return;
|
|
587
|
+
const host = getDocblocksHost();
|
|
588
|
+
return host.onOpenRequest(async (req) => {
|
|
589
|
+
if (req.filePath) {
|
|
590
|
+
const workspaces = (await listWorkspaces()).filter((w) => w.type === 'electron-native' && w.rootPath);
|
|
591
|
+
const match = workspaces.find((w) => req.filePath.startsWith((w.rootPath ?? '') + '/') || req.filePath === w.rootPath);
|
|
592
|
+
if (match && match.rootPath) {
|
|
593
|
+
const rel = '/' + req.filePath.slice(match.rootPath.length).replace(/^\/+/, '');
|
|
594
|
+
await openFromIds(match.id, rel, true);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
else if (req.url) {
|
|
598
|
+
try {
|
|
599
|
+
const u = new URL(req.url);
|
|
600
|
+
const path = u.searchParams.get('path');
|
|
601
|
+
if (path) {
|
|
602
|
+
const workspaces = (await listWorkspaces()).filter((w) => w.type === 'electron-native' && w.rootPath);
|
|
603
|
+
const match = workspaces.find((w) => path.startsWith((w.rootPath ?? '') + '/') || path === w.rootPath);
|
|
604
|
+
if (match && match.rootPath) {
|
|
605
|
+
const rel = '/' + path.slice(match.rootPath.length).replace(/^\/+/, '');
|
|
606
|
+
await openFromIds(match.id, rel, true);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
// bad URL, ignore
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}, [openFromIds]);
|
|
313
616
|
const handleSelect = useCallback(async (path, kind) => {
|
|
314
617
|
if (!provider || !activeWorkspaceId)
|
|
315
618
|
return;
|
|
@@ -331,6 +634,7 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
331
634
|
setInitialView('wysiwyg');
|
|
332
635
|
setEditorKey((k) => k + 1);
|
|
333
636
|
pushHash(activeWorkspaceId, path);
|
|
637
|
+
saveLastState({ workspaceId: activeWorkspaceId, filePath: path, view: 'wysiwyg' });
|
|
334
638
|
if (isMobile)
|
|
335
639
|
setMobileShowEditor(true);
|
|
336
640
|
}
|
|
@@ -354,19 +658,24 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
354
658
|
setFolderEntries(entries);
|
|
355
659
|
}
|
|
356
660
|
}, [provider, selectedFile, selectedFolder, activeWorkspaceId, pushHash]);
|
|
357
|
-
/**
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Copy non-markdown files from an imported container into the target
|
|
663
|
+
* markdown file's `_files/` sibling folder, preserving the source's
|
|
664
|
+
* relative structure.
|
|
665
|
+
*/
|
|
666
|
+
const persistImportedMedia = useCallback(async (source, target, importedMarkdownPath) => {
|
|
667
|
+
const parentDir = dirnameOf(importedMarkdownPath);
|
|
668
|
+
const folder = basenameOf(importedMarkdownPath).replace(/\.[^.]+$/, '') + '_files';
|
|
669
|
+
const mediaRoot = parentDir ? `${parentDir}/${folder}` : folder;
|
|
362
670
|
const entries = await source.listFiles();
|
|
363
671
|
for (const entry of entries) {
|
|
364
672
|
if (entry.path.endsWith('.md'))
|
|
365
673
|
continue;
|
|
366
674
|
const data = await source.readFile(entry.path);
|
|
367
|
-
if (data)
|
|
368
|
-
|
|
369
|
-
|
|
675
|
+
if (!data)
|
|
676
|
+
continue;
|
|
677
|
+
const cleanPath = entry.path.replace(/^\/+/, '');
|
|
678
|
+
await target.writeBinary(`${mediaRoot}/${cleanPath}`, new Uint8Array(data));
|
|
370
679
|
}
|
|
371
680
|
}, []);
|
|
372
681
|
const handleImportFiles = useCallback(async (files) => {
|
|
@@ -385,19 +694,19 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
385
694
|
const { docxToContainer } = await import('@bendyline/squisq-formats/docx');
|
|
386
695
|
const container = await docxToContainer(await file.arrayBuffer());
|
|
387
696
|
markdown = (await container.readDocument()) ?? '';
|
|
388
|
-
await
|
|
697
|
+
await persistImportedMedia(container, provider, destPath);
|
|
389
698
|
}
|
|
390
699
|
else if (ext === '.pdf') {
|
|
391
700
|
const { pdfToContainer } = await import('@bendyline/squisq-formats/pdf');
|
|
392
701
|
const container = await pdfToContainer(await file.arrayBuffer());
|
|
393
702
|
markdown = (await container.readDocument()) ?? '';
|
|
394
|
-
await
|
|
703
|
+
await persistImportedMedia(container, provider, destPath);
|
|
395
704
|
}
|
|
396
705
|
else if (ext === '.dbk' || ext === '.zip') {
|
|
397
706
|
const { zipToContainer } = await import('@bendyline/squisq-formats/container');
|
|
398
707
|
const container = await zipToContainer(await file.arrayBuffer());
|
|
399
708
|
markdown = (await container.readDocument()) ?? '';
|
|
400
|
-
await
|
|
709
|
+
await persistImportedMedia(container, provider, destPath);
|
|
401
710
|
}
|
|
402
711
|
else {
|
|
403
712
|
continue;
|
|
@@ -408,14 +717,9 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
408
717
|
console.error(`Failed to import ${file.name}:`, err);
|
|
409
718
|
}
|
|
410
719
|
}
|
|
411
|
-
// Refresh
|
|
412
|
-
const mc = mediaContainerRef.current;
|
|
413
|
-
if (mc) {
|
|
414
|
-
setMediaProvider(createMediaProviderFromContainer(mc));
|
|
415
|
-
}
|
|
416
|
-
// Refresh file tree
|
|
720
|
+
// Refresh file tree so the new markdown files + _files folders show up.
|
|
417
721
|
setExplorerKey((k) => k + 1);
|
|
418
|
-
}, [provider,
|
|
722
|
+
}, [provider, persistImportedMedia]);
|
|
419
723
|
const handleEditorChange = useCallback((source) => {
|
|
420
724
|
setEditorContent(source);
|
|
421
725
|
}, []);
|
|
@@ -431,7 +735,7 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
431
735
|
await saveWorkspace({ ...ws, name: newName });
|
|
432
736
|
// Bump key to trigger re-render — workspace name is read from the descriptor, not the provider
|
|
433
737
|
setEditorKey((k) => k + 1);
|
|
434
|
-
}, [activeWorkspaceId
|
|
738
|
+
}, [activeWorkspaceId]);
|
|
435
739
|
const handleDownloadWorkspace = useCallback(async () => {
|
|
436
740
|
if (!provider)
|
|
437
741
|
return;
|
|
@@ -459,16 +763,50 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
459
763
|
const handleRemoveWorkspace = useCallback(async () => {
|
|
460
764
|
if (!activeWorkspaceId)
|
|
461
765
|
return;
|
|
462
|
-
|
|
766
|
+
const confirmMsg = isElectronHost()
|
|
767
|
+
? 'Remove this workspace from DocBlocks? The files on disk will not be deleted.'
|
|
768
|
+
: 'Remove this workspace? This cannot be undone.';
|
|
769
|
+
if (!confirm(confirmMsg))
|
|
463
770
|
return;
|
|
464
|
-
await
|
|
771
|
+
const ws = await getWorkspace(activeWorkspaceId);
|
|
772
|
+
if (ws?.type === 'electron-native') {
|
|
773
|
+
try {
|
|
774
|
+
await getDocblocksHost().workspaces.unregister(activeWorkspaceId);
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
// ignore — host cleanup is best-effort
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
await removeDirectoryHandle(activeWorkspaceId);
|
|
782
|
+
}
|
|
465
783
|
await removeWorkspace(activeWorkspaceId);
|
|
784
|
+
const electron = isElectronHost();
|
|
466
785
|
// Switch to most recent remaining workspace or create default
|
|
467
|
-
const remaining = await listWorkspaces();
|
|
786
|
+
const remaining = (await listWorkspaces()).filter((w) => electron ? w.type === 'electron-native' : w.type !== 'electron-native');
|
|
468
787
|
if (remaining.length > 0) {
|
|
469
788
|
const next = remaining[0];
|
|
470
789
|
await handleWorkspaceSelect(next);
|
|
471
790
|
}
|
|
791
|
+
else if (electron) {
|
|
792
|
+
const info = await getDocblocksHost().workspaces.getDefault();
|
|
793
|
+
const descriptor = {
|
|
794
|
+
id: info.id,
|
|
795
|
+
name: info.name,
|
|
796
|
+
type: 'electron-native',
|
|
797
|
+
rootPath: info.rootPath,
|
|
798
|
+
lastOpened: new Date().toISOString(),
|
|
799
|
+
};
|
|
800
|
+
await saveWorkspace(descriptor);
|
|
801
|
+
const p = new ElectronFileSystemProvider(info.id, info.name, info.rootPath);
|
|
802
|
+
setProvider(p);
|
|
803
|
+
setActiveWorkspaceId(info.id);
|
|
804
|
+
setSelectedFile(null);
|
|
805
|
+
setSelectedFolder(null);
|
|
806
|
+
setFolderEntries([]);
|
|
807
|
+
setEditorContent('');
|
|
808
|
+
setEditorKey((k) => k + 1);
|
|
809
|
+
}
|
|
472
810
|
else {
|
|
473
811
|
const defaultWs = await ensureDefaultWorkspace();
|
|
474
812
|
const fsProvider = new IndexedDBFileSystemProvider(defaultWs.id, defaultWs.name);
|
|
@@ -479,9 +817,8 @@ export function DocBlocksShell({ theme = 'auto', logoUrl }) {
|
|
|
479
817
|
setFolderEntries([]);
|
|
480
818
|
setEditorContent('');
|
|
481
819
|
setEditorKey((k) => k + 1);
|
|
482
|
-
setupMediaContainer(defaultWs.id);
|
|
483
820
|
}
|
|
484
|
-
}, [activeWorkspaceId, handleWorkspaceSelect
|
|
485
|
-
return (_jsx("div", { className: `db-shell${isMobile ? ' db-shell--mobile' : ''}`, "data-theme": resolvedTheme, children: _jsxs("div", { style: { display: 'flex', flex: 1, overflow: 'hidden' }, children: [(!isMobile || !mobileShowEditor) && (_jsxs("div", { className: "db-shell-sidebar", children: [_jsxs("div", { className: "db-shell-sidebar-header", children: [_jsx(AppMenu, { logoUrl: logoUrl }), _jsx(WorkspacePicker, { activeWorkspaceId: activeWorkspaceId, onSelect: handleWorkspaceSelect, onOpenFolder: handleOpenFolder }), _jsx(WorkspaceSettingsButton, { onRename: handleRenameWorkspace, onDownload: handleDownloadWorkspace, onRemove: handleRemoveWorkspace })] }), _jsx(FileExplorer, { provider: provider, onSelect: handleSelect, onTreeChange: handleTreeChange, onImportFiles: handleImportFiles }, explorerKey), _jsx("div", { className: "db-shell-sidebar-footer", children: _jsx("a", { href: "https://github.com/bendyline/docblocks/blob/main/LICENSE", target: "_blank", rel: "noopener noreferrer", children: "Terms of Use" }) })] })), (!isMobile || mobileShowEditor) && (_jsx("div", { style: { flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }, children: selectedFile && mediaProvider ? (_jsx(MediaContext.Provider, { value: mediaProvider, children: _jsx(EditorShell, { initialMarkdown: editorContent, initialView: initialView, articleId: selectedFile, onChange: handleEditorChange, theme: resolvedTheme, height: "100%", mediaProvider: mediaProvider, container: mediaContainerRef.current ?? undefined, toolbarSlotLeft: isMobile ? (_jsx("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: _jsx("span", { className: "db-mobile-back-arrow", children: "\u2190" }) })) : undefined, toolbarSlotRight: _jsx(ExportToolbarControls, { selectedFile: selectedFile, mediaContainer: mediaContainerRef.current }) }, `${selectedFile}-${editorKey}`) })) : selectedFolder ? (_jsxs("div", { className: "db-folder-view", children: [isMobile && (_jsxs("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [_jsx("span", { className: "db-mobile-back-arrow", children: "\u2190" }), "Back to files"] })), _jsxs("div", { className: "db-folder-view-header", children: [_jsx("span", { className: "db-folder-view-icon", children:
|
|
821
|
+
}, [activeWorkspaceId, handleWorkspaceSelect]);
|
|
822
|
+
return (_jsx("div", { className: `db-shell${isMobile ? ' db-shell--mobile' : ''}`, "data-theme": resolvedTheme, children: _jsxs("div", { style: { display: 'flex', flex: 1, overflow: 'hidden' }, children: [(!isMobile || !mobileShowEditor) && (_jsxs("div", { className: "db-shell-sidebar", children: [_jsxs("div", { className: "db-shell-sidebar-header", children: [_jsx(AppMenu, { logoUrl: logoUrl, themePreference: themePreference, onThemeChange: handleThemeChange }), _jsx(WorkspacePicker, { activeWorkspaceId: activeWorkspaceId, onSelect: handleWorkspaceSelect, onOpenFolder: handleOpenFolder }), _jsx(WorkspaceSettingsButton, { onRename: handleRenameWorkspace, onDownload: handleDownloadWorkspace, onRemove: handleRemoveWorkspace })] }), _jsx(FileExplorer, { provider: provider, onSelect: handleSelect, onTreeChange: handleTreeChange, onImportFiles: handleImportFiles }, explorerKey), _jsx("div", { className: "db-shell-sidebar-footer", children: _jsx("a", { href: "https://github.com/bendyline/docblocks/blob/main/LICENSE", target: "_blank", rel: "noopener noreferrer", children: "Terms of Use" }) })] })), (!isMobile || mobileShowEditor) && (_jsx("div", { style: { flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }, children: selectedFile && mediaProvider ? (_jsx(MediaContext.Provider, { value: mediaProvider, children: _jsx(EditorShell, { initialMarkdown: editorContent, initialView: initialView, articleId: selectedFile, onChange: handleEditorChange, theme: resolvedTheme, height: "100%", mediaProvider: mediaProvider, container: mediaContainerRef.current ?? undefined, toolbarSlotLeft: isMobile ? (_jsx("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: _jsx("span", { className: "db-mobile-back-arrow", children: "\u2190" }) })) : undefined, toolbarSlotRight: _jsx(ExportToolbarControls, { selectedFile: selectedFile, mediaContainer: mediaContainerRef.current }) }, `${selectedFile}-${editorKey}`) })) : selectedFolder ? (_jsxs("div", { className: "db-folder-view", children: [isMobile && (_jsxs("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [_jsx("span", { className: "db-mobile-back-arrow", children: "\u2190" }), "Back to files"] })), _jsxs("div", { className: "db-folder-view-header", children: [_jsx("span", { className: "db-folder-view-icon", children: _jsx(FolderGlyph, {}) }), _jsx("span", { className: "db-folder-view-path", children: selectedFolder })] }), folderEntries.length === 0 ? (_jsx("p", { className: "db-folder-view-empty", children: "This folder is empty." })) : (_jsx("ul", { className: "db-folder-view-list", children: folderEntries.map((entry) => (_jsxs("li", { className: "db-folder-view-item", onClick: () => handleSelect(entry.path, entry.kind), children: [_jsx("span", { className: "db-folder-view-item-icon", children: entry.kind === 'directory' ? _jsx(FolderGlyph, {}) : _jsx(FileGlyph, {}) }), entry.name] }, entry.path))) }))] })) : (_jsx("div", { className: "db-shell-empty", children: _jsx("p", { children: "Select a file to start editing, or create a new one." }) })) }))] }) }));
|
|
486
823
|
}
|
|
487
824
|
//# sourceMappingURL=DocBlocksShell.js.map
|