@bendyline/docblocks 2.6.1 → 2.7.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/THIRD_PARTY_NOTICES.txt +33 -33
- package/dist/capabilities-CdQAc745.d.ts +1233 -0
- package/dist/catalog-BQK05tne.d.ts +122 -0
- package/dist/chunk-245W57QP.js +31 -0
- package/dist/{chunk-2WDJ27EH.js → chunk-46AGYHDY.js} +8 -0
- package/dist/{chunk-4437YWLE.js → chunk-6UPO2GE6.js} +4 -2
- package/dist/{chunk-UMWCTEXW.js → chunk-7SN7YQL4.js} +211 -40
- package/dist/{chunk-OQCPBOQA.js → chunk-DAPFSEKY.js} +35 -0
- package/dist/{chunk-M62ZGJ5I.js → chunk-HABG2JM3.js} +0 -17
- package/dist/{chunk-YM7O3PWO.js → chunk-HKOOHM67.js} +20 -12
- package/dist/chunk-IDYE73TU.js +77 -0
- package/dist/{chunk-KLQKU74Q.js → chunk-IGB2VSRE.js} +124 -330
- package/dist/{chunk-BVY7GGNV.js → chunk-L6PRMR7T.js} +137 -95
- package/dist/chunk-LPIJ6HKL.js +19 -0
- package/dist/{chunk-SJBB5OD7.js → chunk-QS5WTLUZ.js} +10 -8
- package/dist/chunk-R2JX5N3F.js +382 -0
- package/dist/{chunk-ZO7G53QP.js → chunk-UDEWLHQI.js} +4 -2
- package/dist/chunk-UK7R3RC7.js +514 -0
- package/dist/chunk-XTNWCNSK.js +464 -0
- package/dist/chunk-YHSFRPBS.js +42 -0
- package/dist/chunk-YNUQG4GF.js +1173 -0
- package/dist/document/index.d.ts +31 -1
- package/dist/document/index.js +7 -6
- package/dist/filesystem/electron.d.ts +8 -34
- package/dist/filesystem/electron.js +7 -4
- package/dist/filesystem/host.d.ts +28 -0
- package/dist/filesystem/host.js +103 -0
- package/dist/filesystem/index.d.ts +52 -6
- package/dist/filesystem/index.js +36 -15
- package/dist/filesystem/indexeddb.d.ts +8 -1
- package/dist/filesystem/indexeddb.js +7 -6
- package/dist/filesystem/memory.d.ts +2 -1
- package/dist/filesystem/memory.js +3 -2
- package/dist/filesystem/native.d.ts +2 -1
- package/dist/filesystem/native.js +3 -2
- package/dist/filesystem-v2-C8AdSXUN.d.ts +74 -0
- package/dist/host/index.d.ts +127 -5
- package/dist/host/index.js +100 -4
- package/dist/host/mobile.d.ts +389 -0
- package/dist/host/mobile.js +315 -0
- package/dist/host-provider-v2-CyLTRKJF.d.ts +51 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.js +153 -34
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/zod.d.ts +23 -1
- package/dist/mcp/zod.js +1 -0
- package/dist/settings-HrrCikVX.d.ts +170 -0
- package/dist/types-BvDnr3YU.d.ts +88 -0
- package/dist/{types-Oy9U5FST.d.ts → types-d0yvHUJz.d.ts} +6 -0
- package/dist/{types-Bb3yccTH.d.ts → v2-BVnZZyd4.d.ts} +1 -86
- package/dist/vscode/index.d.ts +43 -1
- package/dist/vscode/index.js +67 -1
- package/dist/workspace/index.d.ts +10 -6
- package/dist/workspace/index.js +4 -2
- package/dist/workspace-index/index.d.ts +240 -0
- package/dist/workspace-index/index.js +1287 -0
- package/dist/workspace-settings/index.d.ts +301 -0
- package/dist/workspace-settings/index.js +820 -0
- package/package.json +22 -2
- package/dist/chunk-6XHOOBYF.js +0 -36
- package/dist/chunk-QI6YKTQV.js +0 -110
- package/dist/types-DBYraXxE.d.ts +0 -557
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
2
|
+
import { C as CatalogSortOrder } from './settings-HrrCikVX.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Synchronous outside-in path layout.
|
|
6
|
+
*
|
|
7
|
+
* The canonical contract is `@bendyline/squisq-formats/outside-in`, but that
|
|
8
|
+
* entry also loads every format runtime. DocBlocks surfaces that only need
|
|
9
|
+
* the path rules — the shell's navigation, the workspace catalog walker, and
|
|
10
|
+
* the VS Code extension host — share this one copy instead.
|
|
11
|
+
*/
|
|
12
|
+
declare const OUTSIDE_IN_FORMAT_IDS: readonly ["html", "docx", "pdf", "pptx", "xlsx", "csv"];
|
|
13
|
+
type OutsideInFormatId = (typeof OUTSIDE_IN_FORMAT_IDS)[number];
|
|
14
|
+
interface OutsideInLayout {
|
|
15
|
+
targetPath: string;
|
|
16
|
+
format: OutsideInFormatId;
|
|
17
|
+
parentDirectory: string;
|
|
18
|
+
stem: string;
|
|
19
|
+
companionName: string;
|
|
20
|
+
companionDirectory: string;
|
|
21
|
+
markdownFilename: string;
|
|
22
|
+
markdownPath: string;
|
|
23
|
+
relativeTargetPath: string;
|
|
24
|
+
backupDirectory: string;
|
|
25
|
+
backupFilename: string;
|
|
26
|
+
backupPath: string;
|
|
27
|
+
}
|
|
28
|
+
declare function resolveOutsideInLayout(path: string): OutsideInLayout | null;
|
|
29
|
+
/** Move the companion root while preserving the authored Markdown slug. */
|
|
30
|
+
declare function relocateOutsideInLayout(layout: OutsideInLayout, targetPath: string): OutsideInLayout;
|
|
31
|
+
declare function chooseOutsideInMarkdownPath(layout: OutsideInLayout, paths: readonly string[]): string | null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Workspace catalog model — the data behind the generated catalog JSON and
|
|
35
|
+
* catalog HTML page.
|
|
36
|
+
*
|
|
37
|
+
* Everything here is deterministic: identical inputs produce identical bytes
|
|
38
|
+
* on every surface (shell, VS Code extension host, CLI). There are no
|
|
39
|
+
* timestamps, no filesystem mtimes, and no locale-sensitive ordering, so a
|
|
40
|
+
* catalog committed to git only changes when the documents do.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
declare const CATALOG_GENERATOR = "docblocks-workspace-catalog";
|
|
44
|
+
declare const CATALOG_FORMAT_VERSION = 1;
|
|
45
|
+
declare const CATALOG_LIMITS: {
|
|
46
|
+
readonly maxOutlineEntries: 100;
|
|
47
|
+
readonly maxOutlineTitleCharacters: 256;
|
|
48
|
+
readonly maxExcerptCharacters: 280;
|
|
49
|
+
readonly maxDescriptionCharacters: 1024;
|
|
50
|
+
readonly maxMetadataCharacters: 256;
|
|
51
|
+
readonly maxTags: 32;
|
|
52
|
+
};
|
|
53
|
+
type CatalogDocumentFormat = 'markdown' | OutsideInFormatId;
|
|
54
|
+
interface CatalogOutlineEntry {
|
|
55
|
+
readonly level: number;
|
|
56
|
+
readonly title: string;
|
|
57
|
+
}
|
|
58
|
+
interface CatalogEntry {
|
|
59
|
+
/** The document people open — the rendered file for outside-in documents. */
|
|
60
|
+
readonly path: string;
|
|
61
|
+
/** The Markdown source when it differs from `path` (outside-in companion). */
|
|
62
|
+
readonly source?: string;
|
|
63
|
+
readonly format: CatalogDocumentFormat;
|
|
64
|
+
readonly title: string;
|
|
65
|
+
readonly description?: string;
|
|
66
|
+
readonly author?: string;
|
|
67
|
+
readonly date?: string;
|
|
68
|
+
readonly tags?: readonly string[];
|
|
69
|
+
readonly theme?: string;
|
|
70
|
+
readonly outline: readonly CatalogOutlineEntry[];
|
|
71
|
+
readonly wordCount: number;
|
|
72
|
+
readonly excerpt?: string;
|
|
73
|
+
/** Present only when the catalog is configured with `content: "markdown"`. */
|
|
74
|
+
readonly markdown?: string;
|
|
75
|
+
}
|
|
76
|
+
interface WorkspaceCatalog {
|
|
77
|
+
readonly generator: typeof CATALOG_GENERATOR;
|
|
78
|
+
readonly version: typeof CATALOG_FORMAT_VERSION;
|
|
79
|
+
readonly title: string;
|
|
80
|
+
readonly documents: readonly CatalogEntry[];
|
|
81
|
+
}
|
|
82
|
+
interface CatalogSource {
|
|
83
|
+
readonly path: string;
|
|
84
|
+
readonly source?: string;
|
|
85
|
+
readonly format: CatalogDocumentFormat;
|
|
86
|
+
}
|
|
87
|
+
interface CatalogEntryHints {
|
|
88
|
+
/** Title used when the document has neither a frontmatter title nor a heading. */
|
|
89
|
+
readonly fallbackTitle?: string;
|
|
90
|
+
readonly description?: string;
|
|
91
|
+
}
|
|
92
|
+
/** Build a catalog entry from an already-parsed Markdown document. */
|
|
93
|
+
declare function catalogEntryFromDocument(source: CatalogSource, document: MarkdownDocument, hints?: CatalogEntryHints): Promise<CatalogEntry>;
|
|
94
|
+
/** Parse Markdown source and build its catalog entry. */
|
|
95
|
+
declare function catalogEntryFromMarkdown(source: CatalogSource, markdown: string, options?: {
|
|
96
|
+
readonly includeMarkdown?: boolean;
|
|
97
|
+
}): Promise<CatalogEntry>;
|
|
98
|
+
/** A document whose content cannot be read (e.g. an un-imported DOCX): listed by name only. */
|
|
99
|
+
declare function catalogEntryForUnreadable(source: CatalogSource): CatalogEntry;
|
|
100
|
+
declare function sortCatalogEntries(entries: readonly CatalogEntry[], order: CatalogSortOrder): CatalogEntry[];
|
|
101
|
+
declare function createWorkspaceCatalog(title: string, entries: readonly CatalogEntry[], order: CatalogSortOrder): WorkspaceCatalog;
|
|
102
|
+
/**
|
|
103
|
+
* Deterministic catalog JSON. The fixed leading `"generator"` key doubles as
|
|
104
|
+
* the marker that lets DocBlocks recognize — and only ever replace — a file
|
|
105
|
+
* it generated.
|
|
106
|
+
*/
|
|
107
|
+
declare function serializeCatalogJson(catalog: WorkspaceCatalog): string;
|
|
108
|
+
declare function isGeneratedCatalogJson(text: string): boolean;
|
|
109
|
+
/** Insert the generator marker directly after the doctype so it cannot affect quirks mode. */
|
|
110
|
+
declare function stampCatalogHtml(html: string, inputsDigest: string): string;
|
|
111
|
+
declare function readCatalogHtmlDigest(text: string): string | null;
|
|
112
|
+
declare function isGeneratedCatalogHtml(text: string): boolean;
|
|
113
|
+
/** Relative, per-segment-encoded URL from the catalog's directory to a document. */
|
|
114
|
+
declare function relativeCatalogLink(fromDirectory: string, toPath: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* Build the catalog page as a Markdown AST rather than Markdown text, so a
|
|
117
|
+
* document title or description can never inject Markdown or HTML: every
|
|
118
|
+
* value reaches the renderer as an escaped text node.
|
|
119
|
+
*/
|
|
120
|
+
declare function buildCatalogMarkdownDocument(catalog: WorkspaceCatalog, outputPath: string): MarkdownDocument;
|
|
121
|
+
|
|
122
|
+
export { type CatalogEntry as C, OUTSIDE_IN_FORMAT_IDS as O, type WorkspaceCatalog as W, CATALOG_FORMAT_VERSION as a, CATALOG_GENERATOR as b, CATALOG_LIMITS as c, type CatalogDocumentFormat as d, type CatalogEntryHints as e, type CatalogOutlineEntry as f, type CatalogSource as g, type OutsideInFormatId as h, type OutsideInLayout as i, buildCatalogMarkdownDocument as j, catalogEntryForUnreadable as k, catalogEntryFromDocument as l, catalogEntryFromMarkdown as m, chooseOutsideInMarkdownPath as n, createWorkspaceCatalog as o, isGeneratedCatalogHtml as p, isGeneratedCatalogJson as q, readCatalogHtmlDigest as r, relativeCatalogLink as s, relocateOutsideInLayout as t, resolveOutsideInLayout as u, serializeCatalogJson as v, sortCatalogEntries as w, stampCatalogHtml as x };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/filesystem/capabilities.ts
|
|
2
|
+
function parseFileSystemCapabilities(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4
|
+
throw new TypeError("Invalid filesystem capabilities.");
|
|
5
|
+
}
|
|
6
|
+
const record = value;
|
|
7
|
+
const choices = {
|
|
8
|
+
writeAtomicity: ["none", "process", "cross-context"],
|
|
9
|
+
moveAtomicity: ["none", "process", "cross-context"],
|
|
10
|
+
snapshotAtomicity: ["none", "process", "cross-context"],
|
|
11
|
+
conditionalWrite: ["none", "process", "cross-context", "storage-atomic"],
|
|
12
|
+
recursiveRemove: [true, false],
|
|
13
|
+
watch: [true, false],
|
|
14
|
+
caseSensitivity: ["sensitive", "insensitive", "platform"],
|
|
15
|
+
symlinkPolicy: ["unsupported", "reject", "follow-contained", "preserve"],
|
|
16
|
+
durability: ["volatile", "best-effort", "durable"]
|
|
17
|
+
};
|
|
18
|
+
if (Object.keys(record).length !== Object.keys(choices).length) {
|
|
19
|
+
throw new TypeError("Unexpected filesystem capability fields.");
|
|
20
|
+
}
|
|
21
|
+
for (const [key, values] of Object.entries(choices)) {
|
|
22
|
+
if (!values.includes(record[key])) {
|
|
23
|
+
throw new TypeError(`Invalid filesystem capability: ${key}.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return Object.freeze({ ...record });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
parseFileSystemCapabilities
|
|
31
|
+
};
|
|
@@ -11,6 +11,13 @@ var HOST_WIRE_LIMITS = Object.freeze({
|
|
|
11
11
|
base64Characters: 140 * 1024 * 1024,
|
|
12
12
|
arrayEntries: 1e4
|
|
13
13
|
});
|
|
14
|
+
var EXPORT_TRANSFER_LIMITS = Object.freeze({
|
|
15
|
+
fileBytes: 16 * 1024 * 1024 * 1024,
|
|
16
|
+
chunkBytes: 4 * 1024 * 1024,
|
|
17
|
+
transfers: 4,
|
|
18
|
+
idleMs: 12e4,
|
|
19
|
+
lifetimeMs: 60 * 6e4
|
|
20
|
+
});
|
|
14
21
|
function hasExactKeys(value, keys) {
|
|
15
22
|
const actual = Object.keys(value);
|
|
16
23
|
return actual.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
@@ -91,6 +98,7 @@ function isTrustedRendererUrl(value, developmentOrigin) {
|
|
|
91
98
|
export {
|
|
92
99
|
MAX_HOST_PINNED_DOCUMENTS,
|
|
93
100
|
HOST_WIRE_LIMITS,
|
|
101
|
+
EXPORT_TRANSFER_LIMITS,
|
|
94
102
|
isBoundedString,
|
|
95
103
|
isBoundedBytePayload,
|
|
96
104
|
parseOpenRequest,
|
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
bytesEqual,
|
|
9
9
|
compareSnapshotsByName,
|
|
10
10
|
compareText,
|
|
11
|
-
copyBytes
|
|
11
|
+
copyBytes
|
|
12
|
+
} from "./chunk-HABG2JM3.js";
|
|
13
|
+
import {
|
|
12
14
|
parseFileSystemVersion
|
|
13
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-LPIJ6HKL.js";
|
|
14
16
|
import {
|
|
15
17
|
decodeUtf8Text
|
|
16
18
|
} from "./chunk-M5ZNQGR5.js";
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
guessMimeType
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HKOOHM67.js";
|
|
4
4
|
import {
|
|
5
5
|
bytesEqual,
|
|
6
6
|
compareSnapshotsByName,
|
|
7
7
|
compareText,
|
|
8
|
-
copyBytes
|
|
8
|
+
copyBytes
|
|
9
|
+
} from "./chunk-HABG2JM3.js";
|
|
10
|
+
import {
|
|
9
11
|
parseFileSystemVersion
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LPIJ6HKL.js";
|
|
11
13
|
import {
|
|
14
|
+
DocumentSession,
|
|
12
15
|
FileSystemMoveRecoveryError,
|
|
13
16
|
FileSystemPartialMoveError,
|
|
17
|
+
createFileSystemDocumentTarget,
|
|
14
18
|
describeEntryMove,
|
|
15
19
|
describePartialMove
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-IGB2VSRE.js";
|
|
21
|
+
import {
|
|
22
|
+
getFileSystemProviderV2
|
|
23
|
+
} from "./chunk-XAMILHQP.js";
|
|
17
24
|
import {
|
|
18
25
|
FsError,
|
|
19
26
|
WORKSPACE_ROOT,
|
|
@@ -25,9 +32,6 @@ import {
|
|
|
25
32
|
workspacePathJoin,
|
|
26
33
|
workspacePathToLegacy
|
|
27
34
|
} from "./chunk-AYWKIZLD.js";
|
|
28
|
-
import {
|
|
29
|
-
getFileSystemProviderV2
|
|
30
|
-
} from "./chunk-XAMILHQP.js";
|
|
31
35
|
|
|
32
36
|
// src/filesystem/legacy-v2-adapter.ts
|
|
33
37
|
var EPOCH = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
@@ -578,6 +582,93 @@ function newestLastModified(children) {
|
|
|
578
582
|
return newest ? new Date(newest).toISOString() : EPOCH;
|
|
579
583
|
}
|
|
580
584
|
|
|
585
|
+
// src/filesystem/companion-references.ts
|
|
586
|
+
async function rewriteCompanionReferences(content, oldPath, newPath) {
|
|
587
|
+
const companionName = (path) => path.split("/").pop().replace(/\.[^.]+$/, "") + "_files";
|
|
588
|
+
const oldName = companionName(oldPath);
|
|
589
|
+
const newName = companionName(newPath);
|
|
590
|
+
if (oldName === newName || !content.includes(oldName) && !content.includes(encodeURI(oldName)))
|
|
591
|
+
return content;
|
|
592
|
+
const { parseMarkdown, stringifyMarkdown } = await import("@bendyline/squisq/markdown");
|
|
593
|
+
const document = parseMarkdown(content);
|
|
594
|
+
let changed = false;
|
|
595
|
+
const rewrite = (value) => {
|
|
596
|
+
const relative = value.startsWith("./") ? "./" : "";
|
|
597
|
+
const path = value.slice(relative.length);
|
|
598
|
+
for (const [before, after] of [
|
|
599
|
+
[oldName, newName],
|
|
600
|
+
[encodeURI(oldName), encodeURI(newName)]
|
|
601
|
+
]) {
|
|
602
|
+
if (path.startsWith(`${before}/`)) {
|
|
603
|
+
changed = true;
|
|
604
|
+
return `${relative}${after}/${path.slice(before.length + 1)}`;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return value;
|
|
608
|
+
};
|
|
609
|
+
const mediaFields = /* @__PURE__ */ new Set([
|
|
610
|
+
"src",
|
|
611
|
+
"href",
|
|
612
|
+
"poster",
|
|
613
|
+
"audio",
|
|
614
|
+
"video",
|
|
615
|
+
"image",
|
|
616
|
+
"backgroundImage"
|
|
617
|
+
]);
|
|
618
|
+
const visit = (value) => {
|
|
619
|
+
if (!value || typeof value !== "object") return;
|
|
620
|
+
if (Array.isArray(value)) {
|
|
621
|
+
value.forEach(visit);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const node = value;
|
|
625
|
+
if (["image", "link", "definition"].includes(String(node.type)) && typeof node.url === "string") {
|
|
626
|
+
node.url = rewrite(node.url);
|
|
627
|
+
}
|
|
628
|
+
if ((node.type === "htmlBlock" || node.type === "htmlInline") && typeof node.rawHtml === "string") {
|
|
629
|
+
node.rawHtml = node.rawHtml.replace(
|
|
630
|
+
/<(?:img|audio|video|source|a)\b[^>]*>/gi,
|
|
631
|
+
(tag) => tag.replace(
|
|
632
|
+
/(\s)(src|href|poster)(\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi,
|
|
633
|
+
(_match, space, name, equals, doubleQuoted, singleQuoted, unquoted) => {
|
|
634
|
+
const quote = doubleQuoted !== void 0 ? '"' : singleQuoted !== void 0 ? "'" : "";
|
|
635
|
+
return `${space}${name}${equals}${quote}${rewrite(doubleQuoted ?? singleQuoted ?? unquoted ?? "")}${quote}`;
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
for (const field of ["attributes", "params"]) {
|
|
641
|
+
const attributes = node[field];
|
|
642
|
+
if (attributes && typeof attributes === "object" && !Array.isArray(attributes)) {
|
|
643
|
+
const values = attributes;
|
|
644
|
+
for (const key of mediaFields)
|
|
645
|
+
if (typeof values[key] === "string") values[key] = rewrite(values[key]);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
for (const [key, child] of Object.entries(node)) {
|
|
649
|
+
if (key !== "frontmatter" && key !== "position" && key !== "htmlChildren") visit(child);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
const replacements = [];
|
|
653
|
+
for (const block of document.children) {
|
|
654
|
+
changed = false;
|
|
655
|
+
visit(block);
|
|
656
|
+
if (!changed) continue;
|
|
657
|
+
const start = block.position?.start.offset;
|
|
658
|
+
const end = block.position?.end.offset;
|
|
659
|
+
if (start === void 0 || end === void 0) {
|
|
660
|
+
throw new Error("Cannot locate a media reference in the Markdown source.");
|
|
661
|
+
}
|
|
662
|
+
let text = stringifyMarkdown({ type: "document", children: [block] }).replace(/\n+$/, "");
|
|
663
|
+
if (content.includes("\r\n")) text = text.replace(/\n/g, "\r\n");
|
|
664
|
+
replacements.push({ start, end, text });
|
|
665
|
+
}
|
|
666
|
+
for (const replacement of replacements.reverse()) {
|
|
667
|
+
content = content.slice(0, replacement.start) + replacement.text + content.slice(replacement.end);
|
|
668
|
+
}
|
|
669
|
+
return content;
|
|
670
|
+
}
|
|
671
|
+
|
|
581
672
|
// src/filesystem/move-entry.ts
|
|
582
673
|
function normalisePath(path) {
|
|
583
674
|
return parseWorkspacePath(path);
|
|
@@ -600,9 +691,9 @@ function documentCompanionPath(documentPath) {
|
|
|
600
691
|
return rooted ? `/${name}_files` : `${name}_files`;
|
|
601
692
|
}
|
|
602
693
|
function hasDocumentCompanion(path) {
|
|
603
|
-
return /\.(?:md|html?|docx|pdf|pptx|xlsx)$/i.test(basename(path));
|
|
694
|
+
return /\.(?:md|html?|docx|pdf|pptx|xlsx|csv)$/i.test(basename(path));
|
|
604
695
|
}
|
|
605
|
-
async function moveFileSystemEntry(provider, oldPath, newPath, kind) {
|
|
696
|
+
async function moveFileSystemEntry(provider, oldPath, newPath, kind, options = {}) {
|
|
606
697
|
const providerV2 = getFileSystemProviderV2(provider);
|
|
607
698
|
const exists = (candidate) => providerV2 ? providerV2.stat(parseWorkspacePath(candidate)).then((entry) => entry !== null) : provider.exists(candidate);
|
|
608
699
|
const move = (source, destination) => providerV2 ? providerV2.move(parseWorkspacePath(source), parseWorkspacePath(destination), {
|
|
@@ -629,6 +720,16 @@ async function moveFileSystemEntry(provider, oldPath, newPath, kind) {
|
|
|
629
720
|
}
|
|
630
721
|
}
|
|
631
722
|
}
|
|
723
|
+
let markdown;
|
|
724
|
+
if (oldCompanion && options.rewriteMarkdown !== false && /\.md$/i.test(oldPath)) {
|
|
725
|
+
const source = providerV2 ? await providerV2.readFile(parseWorkspacePath(oldPath)).then(
|
|
726
|
+
(read) => read ? new TextDecoder("utf-8", { fatal: true }).decode(read.data) : null
|
|
727
|
+
) : await provider.readFile(oldPath);
|
|
728
|
+
if (source !== null) {
|
|
729
|
+
const next = await rewriteCompanionReferences(source, oldPath, newPath);
|
|
730
|
+
if (source !== next) markdown = { previous: source, next };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
632
733
|
try {
|
|
633
734
|
await move(oldPath, newPath);
|
|
634
735
|
} catch (error) {
|
|
@@ -675,6 +776,26 @@ async function moveFileSystemEntry(provider, oldPath, newPath, kind) {
|
|
|
675
776
|
}
|
|
676
777
|
throw error;
|
|
677
778
|
}
|
|
779
|
+
if (markdown) {
|
|
780
|
+
const session = new DocumentSession({ autoSaveEnabled: false });
|
|
781
|
+
try {
|
|
782
|
+
const state = await session.transitionTo(
|
|
783
|
+
createFileSystemDocumentTarget(provider, newPath),
|
|
784
|
+
markdown.previous
|
|
785
|
+
);
|
|
786
|
+
session.edit(markdown.next, { targetKey: state.targetKey, generation: state.generation });
|
|
787
|
+
await session.flush("transition");
|
|
788
|
+
} catch (error) {
|
|
789
|
+
throw new FileSystemMoveRecoveryError(
|
|
790
|
+
oldPath,
|
|
791
|
+
newPath,
|
|
792
|
+
await describeEntryMove(provider, oldPath, newPath),
|
|
793
|
+
error
|
|
794
|
+
);
|
|
795
|
+
} finally {
|
|
796
|
+
await session.cancel();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
678
799
|
}
|
|
679
800
|
|
|
680
801
|
// src/filesystem/dbk-workspace.ts
|
|
@@ -734,6 +855,7 @@ async function createDbkWorkspaceSnapshot(source, options) {
|
|
|
734
855
|
throw new Error(`The external DBK contains duplicate paths: ${sourcePath}`);
|
|
735
856
|
}
|
|
736
857
|
seenSourcePaths.add(sourcePath);
|
|
858
|
+
if (sourcePath.startsWith(".docblocks/")) continue;
|
|
737
859
|
if (!/\.md$/iu.test(sourcePath)) {
|
|
738
860
|
throw new Error(
|
|
739
861
|
`The external DBK is corrupt: only Markdown (.md) files are allowed (${sourcePath}).`
|
|
@@ -779,13 +901,33 @@ import { findDocumentPath } from "@bendyline/squisq/storage";
|
|
|
779
901
|
function joinPrefix(prefix, p) {
|
|
780
902
|
return workspacePathJoin(parseWorkspacePath(prefix), p);
|
|
781
903
|
}
|
|
904
|
+
function canonicalPrefixFolder(prefix) {
|
|
905
|
+
const folder = prefix?.includes("/") ? prefix.slice(0, prefix.lastIndexOf("/")) : "";
|
|
906
|
+
try {
|
|
907
|
+
return folder && parseWorkspacePath(folder) === folder ? folder : "";
|
|
908
|
+
} catch {
|
|
909
|
+
return "";
|
|
910
|
+
}
|
|
911
|
+
}
|
|
782
912
|
var FileSystemContentContainer = class {
|
|
783
|
-
constructor(provider, prefix = ".docblocks/media") {
|
|
913
|
+
constructor(provider, prefix = ".docblocks/media", options = {}) {
|
|
784
914
|
this.provider = provider;
|
|
785
915
|
this.prefix = parseWorkspacePath(prefix);
|
|
916
|
+
this.documentMediaPrefix = parseWorkspacePath(options.documentMediaPrefix ?? "");
|
|
917
|
+
}
|
|
918
|
+
/** A document-scoped container also accepts the media references written
|
|
919
|
+
* into its Markdown, e.g. notes_files/audio/take.webm. Version paths and
|
|
920
|
+
* container-relative discovery still live directly under the scoped root. */
|
|
921
|
+
resolve(path) {
|
|
922
|
+
const canonical = parseWorkspacePath(path);
|
|
923
|
+
const alias = this.documentMediaPrefix;
|
|
924
|
+
return joinPrefix(
|
|
925
|
+
this.prefix,
|
|
926
|
+
alias && canonical.startsWith(`${alias}/`) ? canonical.slice(alias.length + 1) : canonical
|
|
927
|
+
);
|
|
786
928
|
}
|
|
787
929
|
async readFile(path) {
|
|
788
|
-
const full =
|
|
930
|
+
const full = this.resolve(path);
|
|
789
931
|
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
790
932
|
if (providerV2) return (await providerV2.readFile(parseWorkspacePath(full)))?.data ?? null;
|
|
791
933
|
const binary = await this.provider.readBinary(full);
|
|
@@ -795,7 +937,7 @@ var FileSystemContentContainer = class {
|
|
|
795
937
|
return new TextEncoder().encode(text).buffer;
|
|
796
938
|
}
|
|
797
939
|
async writeFile(path, data, _mimeType) {
|
|
798
|
-
const full =
|
|
940
|
+
const full = this.resolve(path);
|
|
799
941
|
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
800
942
|
if (providerV2) {
|
|
801
943
|
await providerV2.writeFile(parseWorkspacePath(full), data, {
|
|
@@ -807,7 +949,7 @@ var FileSystemContentContainer = class {
|
|
|
807
949
|
await this.provider.writeBinary(full, data);
|
|
808
950
|
}
|
|
809
951
|
async removeFile(path) {
|
|
810
|
-
const full =
|
|
952
|
+
const full = this.resolve(path);
|
|
811
953
|
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
812
954
|
if (providerV2) {
|
|
813
955
|
await providerV2.remove(parseWorkspacePath(full), { missing: "ignore" });
|
|
@@ -819,22 +961,26 @@ var FileSystemContentContainer = class {
|
|
|
819
961
|
const entries = [];
|
|
820
962
|
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
821
963
|
const root = parseWorkspacePath(this.prefix);
|
|
964
|
+
const relativePath = (childPath) => root ? childPath === root ? "" : childPath.slice(root.length + 1) : childPath;
|
|
965
|
+
const prefixFolder = canonicalPrefixFolder(prefix);
|
|
966
|
+
const start = prefixFolder ? workspacePathJoin(root, prefixFolder) : root;
|
|
822
967
|
const walk = async (dir) => {
|
|
823
968
|
let children;
|
|
824
969
|
try {
|
|
825
970
|
children = providerV2 ? await providerV2.readDirectory(parseWorkspacePath(dir)) : await this.provider.readDirectory(dir);
|
|
826
971
|
} catch (error) {
|
|
827
|
-
|
|
972
|
+
const absent = error instanceof FsError && (error.code === "not-found" || dir !== root && error.code === "type-mismatch");
|
|
973
|
+
if (dir === start && absent) return;
|
|
828
974
|
throw error;
|
|
829
975
|
}
|
|
830
976
|
for (const child of children) {
|
|
977
|
+
const childPath = parseWorkspacePath(child.path);
|
|
831
978
|
if (child.kind === "directory") {
|
|
979
|
+
if (prefix && !`${relativePath(childPath)}/`.startsWith(prefix)) continue;
|
|
832
980
|
await walk(child.path);
|
|
833
981
|
} else {
|
|
834
|
-
|
|
835
|
-
const
|
|
836
|
-
if (!workspacePathContains(prefixPath, childPath)) continue;
|
|
837
|
-
const rel = prefixPath ? childPath === prefixPath ? "" : childPath.slice(prefixPath.length + 1) : childPath;
|
|
982
|
+
if (!workspacePathContains(root, childPath)) continue;
|
|
983
|
+
const rel = relativePath(childPath);
|
|
838
984
|
if (prefix && !rel.startsWith(prefix)) continue;
|
|
839
985
|
const size = child.size ?? (await this.provider.stat(child.path))?.size ?? 0;
|
|
840
986
|
entries.push({
|
|
@@ -845,11 +991,11 @@ var FileSystemContentContainer = class {
|
|
|
845
991
|
}
|
|
846
992
|
}
|
|
847
993
|
};
|
|
848
|
-
await walk(
|
|
994
|
+
await walk(start);
|
|
849
995
|
return entries;
|
|
850
996
|
}
|
|
851
997
|
async exists(path) {
|
|
852
|
-
const full =
|
|
998
|
+
const full = this.resolve(path);
|
|
853
999
|
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
854
1000
|
return providerV2 ? await providerV2.stat(parseWorkspacePath(full)) !== null : this.provider.exists(full);
|
|
855
1001
|
}
|
|
@@ -878,24 +1024,46 @@ function createFileMediaProvider(container, markdownBasename) {
|
|
|
878
1024
|
const folder = stripExt(markdownBasename) + "_files";
|
|
879
1025
|
const prefix = folder + "/";
|
|
880
1026
|
const blobUrlCache = /* @__PURE__ */ new Map();
|
|
1027
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1028
|
+
const generations = /* @__PURE__ */ new Map();
|
|
1029
|
+
let disposed = false;
|
|
881
1030
|
function toKey(ref) {
|
|
882
1031
|
const clean = ref.replace(/^\/+/, "");
|
|
883
1032
|
return clean.startsWith(prefix) ? clean : prefix + clean;
|
|
884
1033
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1034
|
+
function invalidate(key) {
|
|
1035
|
+
generations.set(key, (generations.get(key) ?? 0) + 1);
|
|
1036
|
+
pending.delete(key);
|
|
1037
|
+
const cached = blobUrlCache.get(key);
|
|
1038
|
+
if (cached) URL.revokeObjectURL(cached);
|
|
1039
|
+
blobUrlCache.delete(key);
|
|
1040
|
+
}
|
|
1041
|
+
function resolveUrl(ref) {
|
|
1042
|
+
if (disposed) return Promise.resolve(ref);
|
|
1043
|
+
const key = toKey(ref);
|
|
1044
|
+
const cached = blobUrlCache.get(key);
|
|
1045
|
+
if (cached) return Promise.resolve(cached);
|
|
1046
|
+
const existing = pending.get(key);
|
|
1047
|
+
if (existing) {
|
|
1048
|
+
return existing.promise.then((url) => url === existing.ref ? ref : url);
|
|
1049
|
+
}
|
|
1050
|
+
const generation = generations.get(key) ?? 0;
|
|
1051
|
+
const work = (async () => {
|
|
890
1052
|
const data = await container.readFile(key);
|
|
1053
|
+
if (disposed) return ref;
|
|
1054
|
+
if ((generations.get(key) ?? 0) !== generation) return resolveUrl(ref);
|
|
891
1055
|
if (!data) return ref;
|
|
892
|
-
const
|
|
893
|
-
const entry = entries.find((e) => e.path === key);
|
|
894
|
-
const mimeType = entry?.mimeType ?? "application/octet-stream";
|
|
895
|
-
const url = URL.createObjectURL(new Blob([data], { type: mimeType }));
|
|
1056
|
+
const url = URL.createObjectURL(new Blob([data], { type: guessMimeType(key) }));
|
|
896
1057
|
blobUrlCache.set(key, url);
|
|
897
1058
|
return url;
|
|
898
|
-
}
|
|
1059
|
+
})().finally(() => {
|
|
1060
|
+
if (pending.get(key)?.promise === work) pending.delete(key);
|
|
1061
|
+
});
|
|
1062
|
+
pending.set(key, { ref, promise: work });
|
|
1063
|
+
return work;
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
resolveUrl,
|
|
899
1067
|
async listMedia() {
|
|
900
1068
|
const entries = await container.listFiles(prefix);
|
|
901
1069
|
return entries.filter((e) => !e.path.toLowerCase().endsWith(".md")).map((e) => ({
|
|
@@ -906,25 +1074,27 @@ function createFileMediaProvider(container, markdownBasename) {
|
|
|
906
1074
|
},
|
|
907
1075
|
async addMedia(name, data, mimeType) {
|
|
908
1076
|
const key = toKey(name);
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
URL.revokeObjectURL(cached);
|
|
912
|
-
blobUrlCache.delete(key);
|
|
913
|
-
}
|
|
1077
|
+
invalidate(key);
|
|
1078
|
+
const generation = generations.get(key);
|
|
914
1079
|
const buffer = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data;
|
|
915
1080
|
await container.writeFile(key, buffer, mimeType);
|
|
1081
|
+
const canCacheUpload = generations.get(key) === generation;
|
|
1082
|
+
invalidate(key);
|
|
1083
|
+
if (!disposed && canCacheUpload) {
|
|
1084
|
+
const bytes = ArrayBuffer.isView(buffer) ? new Uint8Array(buffer).slice().buffer : buffer;
|
|
1085
|
+
blobUrlCache.set(key, URL.createObjectURL(new Blob([bytes], { type: mimeType })));
|
|
1086
|
+
}
|
|
916
1087
|
return key;
|
|
917
1088
|
},
|
|
918
1089
|
async removeMedia(ref) {
|
|
919
1090
|
const key = toKey(ref);
|
|
920
|
-
|
|
921
|
-
if (cached) {
|
|
922
|
-
URL.revokeObjectURL(cached);
|
|
923
|
-
blobUrlCache.delete(key);
|
|
924
|
-
}
|
|
1091
|
+
invalidate(key);
|
|
925
1092
|
await container.removeFile(key);
|
|
1093
|
+
invalidate(key);
|
|
926
1094
|
},
|
|
927
1095
|
dispose() {
|
|
1096
|
+
disposed = true;
|
|
1097
|
+
pending.clear();
|
|
928
1098
|
for (const url of blobUrlCache.values()) {
|
|
929
1099
|
URL.revokeObjectURL(url);
|
|
930
1100
|
}
|
|
@@ -935,6 +1105,7 @@ function createFileMediaProvider(container, markdownBasename) {
|
|
|
935
1105
|
|
|
936
1106
|
export {
|
|
937
1107
|
LegacyFileSystemProviderV2Adapter,
|
|
1108
|
+
rewriteCompanionReferences,
|
|
938
1109
|
documentCompanionPath,
|
|
939
1110
|
moveFileSystemEntry,
|
|
940
1111
|
createDbkWorkspaceSnapshot,
|
|
@@ -106,7 +106,42 @@ var RawIndexedDBFileSystemTransaction = class {
|
|
|
106
106
|
const cursor = await requestResult(this.store.openKeyCursor(IDBKeyRange.lowerBound(prefix)));
|
|
107
107
|
return cursor !== null && typeof cursor.key === "string" && cursor.key.startsWith(prefix);
|
|
108
108
|
}
|
|
109
|
+
childKeys(prefix) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const keys = [];
|
|
112
|
+
const request = this.store.openKeyCursor(IDBKeyRange.lowerBound(prefix));
|
|
113
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed."));
|
|
114
|
+
request.onsuccess = () => {
|
|
115
|
+
const cursor = request.result;
|
|
116
|
+
if (!cursor) {
|
|
117
|
+
resolve(keys);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const key = cursor.key;
|
|
121
|
+
if (typeof key !== "string") {
|
|
122
|
+
reject(new Error("The DocBlocks filesystem store contains a non-string key."));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const step = childKeyStep(prefix, key);
|
|
126
|
+
if (step.kind === "done") {
|
|
127
|
+
resolve(keys);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (step.kind === "child") keys.push(key);
|
|
131
|
+
if (step.kind === "skip") cursor.continue(step.seek);
|
|
132
|
+
else cursor.continue();
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
109
136
|
};
|
|
137
|
+
function childKeyStep(prefix, key) {
|
|
138
|
+
if (!key.startsWith(prefix)) return { kind: "done" };
|
|
139
|
+
const rest = key.slice(prefix.length);
|
|
140
|
+
if (!rest) return { kind: "self" };
|
|
141
|
+
const slash = rest.indexOf("/");
|
|
142
|
+
if (slash === -1) return { kind: "child" };
|
|
143
|
+
return { kind: "skip", seek: `${prefix}${rest.slice(0, slash)}0` };
|
|
144
|
+
}
|
|
110
145
|
async function openDatabaseWithStore(databaseName, storeName) {
|
|
111
146
|
const database = await openDatabase(databaseName, void 0, (upgradeDatabase) => {
|
|
112
147
|
if (!upgradeDatabase.objectStoreNames.contains(storeName)) {
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
// src/filesystem/v2.ts
|
|
2
|
-
function parseFileSystemVersion(value) {
|
|
3
|
-
if (typeof value !== "string" || !value || value.length > 1024 || value.includes("\0")) {
|
|
4
|
-
throw new TypeError("Filesystem version tokens must be non-empty bounded strings.");
|
|
5
|
-
}
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
function tryParseFileSystemVersion(value) {
|
|
9
|
-
try {
|
|
10
|
-
return parseFileSystemVersion(value);
|
|
11
|
-
} catch {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
1
|
// src/filesystem/internal/bytes.ts
|
|
17
2
|
function copyBytes(data) {
|
|
18
3
|
const source = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
@@ -41,8 +26,6 @@ function compareSnapshotsByPath(left, right) {
|
|
|
41
26
|
}
|
|
42
27
|
|
|
43
28
|
export {
|
|
44
|
-
parseFileSystemVersion,
|
|
45
|
-
tryParseFileSystemVersion,
|
|
46
29
|
copyBytes,
|
|
47
30
|
bytesEqual,
|
|
48
31
|
compareText,
|