@seed-hypermedia/client 0.0.43 → 0.0.45
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/dist/blocks-to-markdown.d.ts +13 -1
- package/dist/change.d.ts +7 -7
- package/dist/index.d.ts +4 -1
- package/dist/index.mjs +438 -4
- package/dist/resource-read.d.ts +23 -0
- package/package.json +1 -1
- package/src/index.ts +13 -1
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* Round-trip compatible: output can be piped back through `parseMarkdown()`
|
|
14
14
|
* to recreate the same document with block IDs preserved.
|
|
15
15
|
*/
|
|
16
|
-
import type {
|
|
16
|
+
import type { SeedClient } from './client';
|
|
17
|
+
import type { HMBlockNode, HMComment, HMDocument, HMMetadata } from './hm-types';
|
|
17
18
|
export type BlocksToMarkdownOptions = {
|
|
18
19
|
/** Format ipfs:// URLs as https gateway URLs. Default: true. */
|
|
19
20
|
ipfsGateway?: boolean;
|
|
@@ -58,3 +59,14 @@ export declare function parseDraftFilename(filename: string): {
|
|
|
58
59
|
id: string;
|
|
59
60
|
ext: string;
|
|
60
61
|
};
|
|
62
|
+
/** Options for Seed-client-backed markdown rendering that resolves embeds and mentions. */
|
|
63
|
+
export type ResolvedMarkdownOptions = {
|
|
64
|
+
client: Pick<SeedClient, 'request'>;
|
|
65
|
+
maxDepth?: number;
|
|
66
|
+
};
|
|
67
|
+
/** Convert a document to markdown, resolving inline mentions and block embeds through a Seed client. */
|
|
68
|
+
export declare function documentToResolvedMarkdown(doc: HMDocument, options: ResolvedMarkdownOptions): Promise<string>;
|
|
69
|
+
/** Convert comment content to markdown, resolving inline mentions and block embeds through a Seed client. */
|
|
70
|
+
export declare function commentToResolvedMarkdown(comment: HMComment, options: ResolvedMarkdownOptions): Promise<string>;
|
|
71
|
+
/** Convert Seed block content to markdown, resolving inline mentions and block embeds through a Seed client. */
|
|
72
|
+
export declare function contentToResolvedMarkdown(content: HMBlockNode[], options: ResolvedMarkdownOptions): Promise<string>;
|
package/dist/change.d.ts
CHANGED
|
@@ -29,12 +29,12 @@ export type DocumentOperation = {
|
|
|
29
29
|
export type CreateChangeOpsInput = {
|
|
30
30
|
/** Native CBOR ops */
|
|
31
31
|
ops: DocumentOperation[];
|
|
32
|
-
/** CID of the genesis change blob */
|
|
33
|
-
genesisCid
|
|
34
|
-
/** CIDs of dependency changes */
|
|
35
|
-
deps
|
|
36
|
-
/** Depth of the change (max depth of deps + 1) */
|
|
37
|
-
depth
|
|
32
|
+
/** CID of the genesis change blob. Omit when creating the first content change for a document. */
|
|
33
|
+
genesisCid?: CID;
|
|
34
|
+
/** CIDs of dependency changes. Omit when creating the first content change for a document. */
|
|
35
|
+
deps?: CID[];
|
|
36
|
+
/** Depth of the change (max depth of deps + 1). Omit when creating the first content change for a document. */
|
|
37
|
+
depth?: number;
|
|
38
38
|
/** Timestamp (defaults to Date.now()) */
|
|
39
39
|
ts?: bigint;
|
|
40
40
|
};
|
|
@@ -68,7 +68,7 @@ export declare const signPreparedChange: (unsignedBytes: Uint8Array, signer: HMS
|
|
|
68
68
|
}>;
|
|
69
69
|
/**
|
|
70
70
|
* Create a signed genesis Change blob (empty, ts=0).
|
|
71
|
-
* This is
|
|
71
|
+
* This deterministic sentinel is only intended for home documents.
|
|
72
72
|
*/
|
|
73
73
|
export declare function createGenesisChange(signer: HMSigner): Promise<{
|
|
74
74
|
bytes: Uint8Array;
|
package/dist/index.d.ts
CHANGED
|
@@ -24,11 +24,14 @@ export { codePointLength, entityQueryPathToHmIdPath, getHMQueryString, hmIdPathT
|
|
|
24
24
|
export type { HMRequest, HMSigner, UnpackedHypermediaId } from './hm-types';
|
|
25
25
|
export { resolveHypermediaUrl, resolveId } from './hm-resolver';
|
|
26
26
|
export type { DomainResolverFn, DomainIdChangedCallback, ResolveOptions, ResolvedUrl } from './hm-resolver';
|
|
27
|
+
export { resolveIdWithClient } from './resource-read';
|
|
28
|
+
export type { ResolveIdWithClientOptions, ResolvedIdWithClient } from './resource-read';
|
|
27
29
|
export { fileToIpfsBlobs, filesToIpfsBlobs, resolveFileLinksInBlocks, hasFileLinks } from './file-to-ipfs';
|
|
28
30
|
export type { CollectedBlob } from './file-to-ipfs';
|
|
29
31
|
export { parseMarkdown, flattenToOperations, parseInlineFormatting, parseFrontmatter, markdownBlockNodesToHMBlockNodes, } from './markdown-to-blocks';
|
|
30
32
|
export type { BlockNode, SeedBlock, Annotation } from './markdown-to-blocks';
|
|
31
|
-
export { blocksToMarkdown, emitFrontmatter, slugify, draftFilename, parseDraftFilename } from './blocks-to-markdown';
|
|
33
|
+
export { blocksToMarkdown, emitFrontmatter, slugify, draftFilename, parseDraftFilename, documentToResolvedMarkdown, commentToResolvedMarkdown, contentToResolvedMarkdown, } from './blocks-to-markdown';
|
|
34
|
+
export type { ResolvedMarkdownOptions } from './blocks-to-markdown';
|
|
32
35
|
export type { BlocksToMarkdownOptions } from './blocks-to-markdown';
|
|
33
36
|
export { createBlocksMap, matchBlockIds, computeReplaceOps, hmBlockNodeToBlockNode } from './block-diff';
|
|
34
37
|
export type { APIBlockNode, APIBlock } from './block-diff';
|
package/dist/index.mjs
CHANGED
|
@@ -209,11 +209,13 @@ function createChangeOps(input) {
|
|
|
209
209
|
},
|
|
210
210
|
signer: null,
|
|
211
211
|
ts,
|
|
212
|
-
sig: null
|
|
213
|
-
genesis: input.genesisCid,
|
|
214
|
-
deps: input.deps,
|
|
215
|
-
depth: input.depth
|
|
212
|
+
sig: null
|
|
216
213
|
};
|
|
214
|
+
if (input.genesisCid) {
|
|
215
|
+
unsigned.genesis = input.genesisCid;
|
|
216
|
+
unsigned.deps = input.deps ?? [];
|
|
217
|
+
unsigned.depth = input.depth ?? 1;
|
|
218
|
+
}
|
|
217
219
|
return { unsignedBytes: cborEncode4(unsigned), ts };
|
|
218
220
|
}
|
|
219
221
|
async function createChange(unsignedBytes, signer) {
|
|
@@ -2349,6 +2351,68 @@ function resolveProfilePath(siteUid, pathSegments) {
|
|
|
2349
2351
|
};
|
|
2350
2352
|
}
|
|
2351
2353
|
|
|
2354
|
+
// src/resource-read.ts
|
|
2355
|
+
async function resolveIdWithClient(rawId, options = {}) {
|
|
2356
|
+
const commentId = extractCommentIdFromReadableUrl(rawId);
|
|
2357
|
+
const parsed = commentId ? commentIdToHmId(commentId) : unpackHmId(rawId);
|
|
2358
|
+
if (parsed) {
|
|
2359
|
+
if (isWebUrl(rawId)) {
|
|
2360
|
+
const origin2 = new URL(rawId).origin;
|
|
2361
|
+
return { id: parsed, client: createSeedClient(origin2), serverUrl: origin2 };
|
|
2362
|
+
}
|
|
2363
|
+
const client = options.client ?? createSeedClient(normalizeServerUrl(options.serverUrl || "https://hyper.media"));
|
|
2364
|
+
return { id: parsed, client, serverUrl: client.baseUrl };
|
|
2365
|
+
}
|
|
2366
|
+
const id = await resolveId(rawId, { domainResolver: options.domainResolver });
|
|
2367
|
+
const origin = new URL(rawId).origin;
|
|
2368
|
+
return { id, client: createSeedClient(origin), serverUrl: origin };
|
|
2369
|
+
}
|
|
2370
|
+
var COMMENT_VIEW_TERMS = /* @__PURE__ */ new Set([":comments", ":comment", ":discussions"]);
|
|
2371
|
+
function extractCommentIdFromReadableUrl(rawId) {
|
|
2372
|
+
if (isWebUrl(rawId)) {
|
|
2373
|
+
const url = new URL(rawId);
|
|
2374
|
+
const panelCommentId = extractCommentIdFromPanel(url.searchParams.get("panel"));
|
|
2375
|
+
if (panelCommentId) return panelCommentId;
|
|
2376
|
+
return extractCommentIdFromPath(url.pathname.split("/").filter(Boolean));
|
|
2377
|
+
}
|
|
2378
|
+
const [withoutHash] = rawId.split("#");
|
|
2379
|
+
const [withoutQuery] = withoutHash.split("?");
|
|
2380
|
+
const parsed = unpackHmId(withoutQuery);
|
|
2381
|
+
return (parsed == null ? void 0 : parsed.path) ? extractCommentIdFromPath(parsed.path) : null;
|
|
2382
|
+
}
|
|
2383
|
+
function extractCommentIdFromPanel(panel) {
|
|
2384
|
+
if (!panel) return null;
|
|
2385
|
+
const [viewTerm, first, second] = panel.split("/");
|
|
2386
|
+
if (viewTerm !== "comments" && viewTerm !== "comment" && viewTerm !== "discussions") return null;
|
|
2387
|
+
if (first && second) return `${first}/${second}`;
|
|
2388
|
+
return first || null;
|
|
2389
|
+
}
|
|
2390
|
+
function extractCommentIdFromPath(pathParts) {
|
|
2391
|
+
if (pathParts.length >= 3) {
|
|
2392
|
+
const thirdToLast = pathParts[pathParts.length - 3];
|
|
2393
|
+
if (thirdToLast && COMMENT_VIEW_TERMS.has(thirdToLast)) {
|
|
2394
|
+
return `${pathParts[pathParts.length - 2]}/${pathParts[pathParts.length - 1]}`;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
if (pathParts.length >= 2) {
|
|
2398
|
+
const secondToLast = pathParts[pathParts.length - 2];
|
|
2399
|
+
if (secondToLast && COMMENT_VIEW_TERMS.has(secondToLast)) return pathParts[pathParts.length - 1] || null;
|
|
2400
|
+
}
|
|
2401
|
+
return null;
|
|
2402
|
+
}
|
|
2403
|
+
function commentIdToHmId(commentId) {
|
|
2404
|
+
const parsed = unpackHmId(`hm://${commentId}`);
|
|
2405
|
+
if (!parsed) throw new Error(`Invalid comment ID: ${commentId}`);
|
|
2406
|
+
return parsed;
|
|
2407
|
+
}
|
|
2408
|
+
function isWebUrl(rawId) {
|
|
2409
|
+
return rawId.startsWith("http://") || rawId.startsWith("https://");
|
|
2410
|
+
}
|
|
2411
|
+
function normalizeServerUrl(serverUrl) {
|
|
2412
|
+
const withScheme = serverUrl.startsWith("http://") || serverUrl.startsWith("https://") ? serverUrl : `https://${serverUrl}`;
|
|
2413
|
+
return withScheme.replace(/\/+$/, "");
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2352
2416
|
// src/file-to-ipfs.ts
|
|
2353
2417
|
import { MemoryBlockstore } from "blockstore-core/memory";
|
|
2354
2418
|
import { importer as unixFSImporter } from "ipfs-unixfs-importer";
|
|
@@ -3235,6 +3299,372 @@ function parseDraftFilename(filename) {
|
|
|
3235
3299
|
}
|
|
3236
3300
|
return { id: base, ext };
|
|
3237
3301
|
}
|
|
3302
|
+
async function documentToResolvedMarkdown(doc, options) {
|
|
3303
|
+
const ctx = {
|
|
3304
|
+
client: options.client,
|
|
3305
|
+
maxDepth: options.maxDepth ?? 2,
|
|
3306
|
+
currentDepth: 0,
|
|
3307
|
+
cache: /* @__PURE__ */ new Map()
|
|
3308
|
+
};
|
|
3309
|
+
return documentToResolvedMarkdownWithContext(doc, ctx);
|
|
3310
|
+
}
|
|
3311
|
+
async function commentToResolvedMarkdown(comment, options) {
|
|
3312
|
+
return contentToResolvedMarkdown(comment.content || [], options);
|
|
3313
|
+
}
|
|
3314
|
+
async function contentToResolvedMarkdown(content, options) {
|
|
3315
|
+
const ctx = {
|
|
3316
|
+
client: options.client,
|
|
3317
|
+
maxDepth: options.maxDepth ?? 2,
|
|
3318
|
+
currentDepth: 0,
|
|
3319
|
+
cache: /* @__PURE__ */ new Map()
|
|
3320
|
+
};
|
|
3321
|
+
return blockNodesToResolvedMarkdown(content, ctx);
|
|
3322
|
+
}
|
|
3323
|
+
async function documentToResolvedMarkdownWithContext(doc, ctx) {
|
|
3324
|
+
const lines = [emitFrontmatter(doc.metadata || {})];
|
|
3325
|
+
const body = await blockNodesToResolvedMarkdown(doc.content || [], ctx);
|
|
3326
|
+
if (body) lines.push(body);
|
|
3327
|
+
return lines.join("\n");
|
|
3328
|
+
}
|
|
3329
|
+
async function blockNodesToResolvedMarkdown(nodes, ctx) {
|
|
3330
|
+
const lines = [];
|
|
3331
|
+
for (const node of nodes) {
|
|
3332
|
+
const blockMd = await resolvedBlockNodeToMarkdown(node, 0, ctx);
|
|
3333
|
+
if (blockMd) lines.push(blockMd);
|
|
3334
|
+
}
|
|
3335
|
+
return lines.join("\n\n");
|
|
3336
|
+
}
|
|
3337
|
+
async function resolvedBlockNodeToMarkdown(node, depth, ctx) {
|
|
3338
|
+
var _a;
|
|
3339
|
+
const block = node.block;
|
|
3340
|
+
const children = node.children || [];
|
|
3341
|
+
const childrenType = (_a = block.attributes) == null ? void 0 : _a.childrenType;
|
|
3342
|
+
const isListContainer = block.type === "Paragraph" && !block.text && (childrenType === "Ordered" || childrenType === "Unordered" || childrenType === "Blockquote");
|
|
3343
|
+
let result = isListContainer ? resolvedIdComment(block.id) : await resolvedBlockToMarkdown(block, depth, ctx);
|
|
3344
|
+
for (const child of children) {
|
|
3345
|
+
const childMd = await resolvedBlockNodeToMarkdown(child, depth + 1, ctx);
|
|
3346
|
+
if (!childMd) continue;
|
|
3347
|
+
if (childrenType === "Ordered") result += "\n" + resolvedIndent(depth + 1) + "1. " + childMd.trim();
|
|
3348
|
+
else if (childrenType === "Unordered") result += "\n" + resolvedIndent(depth + 1) + "- " + childMd.trim();
|
|
3349
|
+
else if (childrenType === "Blockquote") result += "\n" + resolvedIndent(depth + 1) + "> " + childMd.trim();
|
|
3350
|
+
else result += "\n\n" + childMd;
|
|
3351
|
+
}
|
|
3352
|
+
return result;
|
|
3353
|
+
}
|
|
3354
|
+
async function resolvedBlockToMarkdown(block, depth, ctx) {
|
|
3355
|
+
var _a, _b;
|
|
3356
|
+
const ind = resolvedIndent(depth);
|
|
3357
|
+
const b = block;
|
|
3358
|
+
const text = b.text || "";
|
|
3359
|
+
const link = b.link || "";
|
|
3360
|
+
const annotations = b.annotations;
|
|
3361
|
+
const id = b.id;
|
|
3362
|
+
switch (block.type) {
|
|
3363
|
+
case "Paragraph":
|
|
3364
|
+
return appendResolvedIdToFirstLine(ind + await applyResolvedAnnotations(text, annotations, ctx), id);
|
|
3365
|
+
case "Heading": {
|
|
3366
|
+
const hashes = "#".repeat(Math.min(depth + 1, 6));
|
|
3367
|
+
return appendResolvedIdToFirstLine(`${hashes} ${await applyResolvedAnnotations(text, annotations, ctx)}`, id);
|
|
3368
|
+
}
|
|
3369
|
+
case "Code": {
|
|
3370
|
+
const lang = ((_a = b.attributes) == null ? void 0 : _a.language) || "";
|
|
3371
|
+
return ind + "```" + lang + " " + resolvedIdComment(id) + "\n" + ind + text + "\n" + ind + "```";
|
|
3372
|
+
}
|
|
3373
|
+
case "Math":
|
|
3374
|
+
return ind + "$$ " + resolvedIdComment(id) + "\n" + ind + text + "\n" + ind + "$$";
|
|
3375
|
+
case "Image":
|
|
3376
|
+
return ind + `}) ${resolvedIdComment(id)}`;
|
|
3377
|
+
case "Video":
|
|
3378
|
+
return ind + `[Video](${formatResolvedMediaUrl(link)}) ${resolvedIdComment(id)}`;
|
|
3379
|
+
case "File": {
|
|
3380
|
+
const fileName = ((_b = b.attributes) == null ? void 0 : _b.name) || "file";
|
|
3381
|
+
return ind + `[${fileName}](${formatResolvedMediaUrl(link)}) ${resolvedIdComment(id)}`;
|
|
3382
|
+
}
|
|
3383
|
+
case "Embed":
|
|
3384
|
+
return appendResolvedIdToFirstLine(await resolveBlockEmbed(block, depth, ctx), id);
|
|
3385
|
+
case "WebEmbed":
|
|
3386
|
+
return ind + `[Web Embed](${link}) ${resolvedIdComment(id)}`;
|
|
3387
|
+
case "Button":
|
|
3388
|
+
return ind + `[${text || "Button"}](${link}) ${resolvedIdComment(id)}`;
|
|
3389
|
+
case "Query":
|
|
3390
|
+
return appendResolvedIdToFirstLine(await resolveQuery(block, depth, ctx), id);
|
|
3391
|
+
case "Nostr":
|
|
3392
|
+
return ind + `[Nostr: ${link}](${link}) ${resolvedIdComment(id)}`;
|
|
3393
|
+
default:
|
|
3394
|
+
return text ? appendResolvedIdToFirstLine(ind + text, id) : "";
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
async function resolveBlockEmbed(block, depth, ctx) {
|
|
3398
|
+
var _a;
|
|
3399
|
+
const ind = resolvedIndent(depth);
|
|
3400
|
+
const link = block.link || "";
|
|
3401
|
+
if (ctx.currentDepth >= ctx.maxDepth) return ind + `> [Embed: ${link}](${link})`;
|
|
3402
|
+
const id = unpackHmId(link);
|
|
3403
|
+
if (!id) return ind + `> [Embed: ${link}](${link})`;
|
|
3404
|
+
try {
|
|
3405
|
+
const resolved = await resolveEmbeddedResource(link, ctx);
|
|
3406
|
+
if (!((_a = resolved.content) == null ? void 0 : _a.length)) return ind + `> [Embed: ${resolved.label}](${link})`;
|
|
3407
|
+
const content = selectEmbeddedContent(resolved.content, id);
|
|
3408
|
+
if (!content.length) return ind + `> [Embed: ${resolved.label}](${link})`;
|
|
3409
|
+
const lines = [];
|
|
3410
|
+
const metadata = [`embed: ${link}`, `title: ${resolved.label}`];
|
|
3411
|
+
if (id.version) metadata.push(`version: ${id.version}`);
|
|
3412
|
+
if (id.blockRef) metadata.push(`block: ${id.blockRef}${formatBlockRange(id)}`);
|
|
3413
|
+
lines.push(ind + `<!-- ${metadata.join("; ")} -->`);
|
|
3414
|
+
const nestedCtx = { ...ctx, currentDepth: ctx.currentDepth + 1 };
|
|
3415
|
+
for (const node of content) {
|
|
3416
|
+
const blockMd = await resolvedBlockNodeToMarkdown(node, depth, nestedCtx);
|
|
3417
|
+
if (!blockMd) continue;
|
|
3418
|
+
lines.push(blockMd);
|
|
3419
|
+
}
|
|
3420
|
+
lines.push(ind + `<!-- /embed: ${link} -->`);
|
|
3421
|
+
return lines.join("\n");
|
|
3422
|
+
} catch {
|
|
3423
|
+
return ind + `> [Embed: ${link}](${link})`;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
async function resolveQuery(block, depth, ctx) {
|
|
3427
|
+
var _a, _b;
|
|
3428
|
+
const ind = resolvedIndent(depth);
|
|
3429
|
+
try {
|
|
3430
|
+
const attrs = block.attributes;
|
|
3431
|
+
const queryConfig = attrs == null ? void 0 : attrs.query;
|
|
3432
|
+
let includes;
|
|
3433
|
+
let sort;
|
|
3434
|
+
let limit;
|
|
3435
|
+
if (queryConfig == null ? void 0 : queryConfig.includes) {
|
|
3436
|
+
includes = queryConfig.includes.map((inc) => ({
|
|
3437
|
+
space: inc.space,
|
|
3438
|
+
path: inc.path,
|
|
3439
|
+
mode: inc.mode || "Children"
|
|
3440
|
+
}));
|
|
3441
|
+
sort = (_a = queryConfig.sort) == null ? void 0 : _a.map((s) => ({ term: s.term, reverse: s.reverse ?? false }));
|
|
3442
|
+
limit = queryConfig.limit;
|
|
3443
|
+
} else {
|
|
3444
|
+
const space = (attrs == null ? void 0 : attrs.space) || "";
|
|
3445
|
+
if (!space) return ind + "<!-- Query: no space specified -->";
|
|
3446
|
+
limit = typeof (attrs == null ? void 0 : attrs.limit) === "number" ? attrs.limit : void 0;
|
|
3447
|
+
includes = [
|
|
3448
|
+
{
|
|
3449
|
+
space,
|
|
3450
|
+
path: attrs == null ? void 0 : attrs.path,
|
|
3451
|
+
mode: (attrs == null ? void 0 : attrs.mode) || "Children"
|
|
3452
|
+
}
|
|
3453
|
+
];
|
|
3454
|
+
}
|
|
3455
|
+
const results = await ctx.client.request("Query", {
|
|
3456
|
+
includes,
|
|
3457
|
+
sort: sort || [{ term: "UpdateTime", reverse: true }],
|
|
3458
|
+
limit: limit || 10
|
|
3459
|
+
});
|
|
3460
|
+
if (!((_b = results == null ? void 0 : results.results) == null ? void 0 : _b.length)) return ind + "<!-- Query: no results -->";
|
|
3461
|
+
return results.results.map((doc) => {
|
|
3462
|
+
var _a2, _b2;
|
|
3463
|
+
const name = ((_a2 = doc.metadata) == null ? void 0 : _a2.name) || ((_b2 = doc.id.path) == null ? void 0 : _b2.join("/")) || doc.id.uid;
|
|
3464
|
+
return ind + `- [${name}](${doc.id.id})`;
|
|
3465
|
+
}).join("\n");
|
|
3466
|
+
} catch (error) {
|
|
3467
|
+
return ind + `<!-- Query error: ${error instanceof Error ? error.message : String(error)} -->`;
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
function selectEmbeddedContent(content, id) {
|
|
3471
|
+
if (!id.blockRef) return content;
|
|
3472
|
+
const target = findBlockById(content, id.blockRef);
|
|
3473
|
+
if (!target) return [];
|
|
3474
|
+
if (id.blockRange && "start" in id.blockRange && typeof id.blockRange.start === "number") {
|
|
3475
|
+
return [sliceBlockText(target, id.blockRange.start, id.blockRange.end ?? id.blockRange.start)];
|
|
3476
|
+
}
|
|
3477
|
+
if (id.blockRange && "expanded" in id.blockRange && !id.blockRange.expanded) return [{ ...target, children: [] }];
|
|
3478
|
+
return [target];
|
|
3479
|
+
}
|
|
3480
|
+
function sliceBlockText(node, start, end) {
|
|
3481
|
+
const block = node.block;
|
|
3482
|
+
if (typeof block.text !== "string") return { ...node, children: [] };
|
|
3483
|
+
return { ...node, block: { ...block, text: block.text.slice(start, end) }, children: [] };
|
|
3484
|
+
}
|
|
3485
|
+
async function applyResolvedAnnotations(text, annotations, ctx) {
|
|
3486
|
+
if (!(annotations == null ? void 0 : annotations.length)) return text;
|
|
3487
|
+
const markers = [];
|
|
3488
|
+
for (const ann of annotations) {
|
|
3489
|
+
const starts = ann.starts || [];
|
|
3490
|
+
const ends = ann.ends || [];
|
|
3491
|
+
for (let i = 0; i < starts.length; i++) {
|
|
3492
|
+
markers.push({ pos: starts[i], type: "open", annotation: ann });
|
|
3493
|
+
if (ends[i] !== void 0) markers.push({ pos: ends[i], type: "close", annotation: ann });
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
markers.sort((a, b) => a.pos !== b.pos ? a.pos - b.pos : a.type === "open" ? -1 : 1);
|
|
3497
|
+
let result = "";
|
|
3498
|
+
let lastPos = 0;
|
|
3499
|
+
for (const marker of markers) {
|
|
3500
|
+
result += text.slice(lastPos, marker.pos);
|
|
3501
|
+
lastPos = marker.pos;
|
|
3502
|
+
result += await getResolvedAnnotationMarker(marker.annotation, marker.type, ctx);
|
|
3503
|
+
}
|
|
3504
|
+
result += text.slice(lastPos);
|
|
3505
|
+
return result.replace(/\uFFFC/g, "");
|
|
3506
|
+
}
|
|
3507
|
+
async function getResolvedAnnotationMarker(ann, type, ctx) {
|
|
3508
|
+
switch (ann.type) {
|
|
3509
|
+
case "Bold":
|
|
3510
|
+
return "**";
|
|
3511
|
+
case "Italic":
|
|
3512
|
+
return "_";
|
|
3513
|
+
case "Strike":
|
|
3514
|
+
return "~~";
|
|
3515
|
+
case "Code":
|
|
3516
|
+
return "`";
|
|
3517
|
+
case "Underline":
|
|
3518
|
+
return type === "open" ? "<u>" : "</u>";
|
|
3519
|
+
case "Link":
|
|
3520
|
+
return type === "open" ? "[" : `](${ann.link || ""})`;
|
|
3521
|
+
case "Embed":
|
|
3522
|
+
return resolveInlineEmbed(ann, type, ctx);
|
|
3523
|
+
default:
|
|
3524
|
+
return "";
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
async function resolveInlineEmbed(ann, type, ctx) {
|
|
3528
|
+
const link = "link" in ann ? ann.link || "" : "";
|
|
3529
|
+
if (type === "close") return `](${link})`;
|
|
3530
|
+
if (!link) return "[\u2197 embed";
|
|
3531
|
+
try {
|
|
3532
|
+
const resolved = await resolveReference(link, ctx);
|
|
3533
|
+
const prefix = resolved.type === "account" ? "@" : "";
|
|
3534
|
+
return `[${prefix}${resolved.label}`;
|
|
3535
|
+
} catch {
|
|
3536
|
+
return `[\u2197 ${fallbackLabel(link)}`;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
async function resolveEmbeddedResource(link, ctx) {
|
|
3540
|
+
var _a, _b;
|
|
3541
|
+
const cacheKey = `embed:${link}`;
|
|
3542
|
+
const cached = ctx.cache.get(cacheKey);
|
|
3543
|
+
if (cached) return cached;
|
|
3544
|
+
const id = unpackHmId(link);
|
|
3545
|
+
if (!id) throw new Error("Invalid HM link");
|
|
3546
|
+
const resource = await ctx.client.request("Resource", id);
|
|
3547
|
+
let resolved;
|
|
3548
|
+
if (resource.type === "document") {
|
|
3549
|
+
resolved = {
|
|
3550
|
+
label: ((_a = resource.document.metadata) == null ? void 0 : _a.name) || fallbackDocLabel(id),
|
|
3551
|
+
type: "document",
|
|
3552
|
+
content: resource.document.content || [],
|
|
3553
|
+
version: id.version || resource.document.version,
|
|
3554
|
+
latestVersion: resource.document.version
|
|
3555
|
+
};
|
|
3556
|
+
} else if (resource.type === "comment") {
|
|
3557
|
+
let authorName = fallbackUid(resource.comment.author);
|
|
3558
|
+
try {
|
|
3559
|
+
const account = await ctx.client.request("Account", resource.comment.author);
|
|
3560
|
+
if (account.type === "account" && ((_b = account.metadata) == null ? void 0 : _b.name)) authorName = account.metadata.name;
|
|
3561
|
+
} catch {
|
|
3562
|
+
}
|
|
3563
|
+
resolved = {
|
|
3564
|
+
label: `Comment by ${authorName}`,
|
|
3565
|
+
type: "comment",
|
|
3566
|
+
content: resource.comment.content || [],
|
|
3567
|
+
version: id.version || resource.comment.version,
|
|
3568
|
+
latestVersion: resource.comment.version,
|
|
3569
|
+
author: resource.comment.author
|
|
3570
|
+
};
|
|
3571
|
+
} else {
|
|
3572
|
+
resolved = { label: fallbackLabel(link), type: "unknown" };
|
|
3573
|
+
}
|
|
3574
|
+
ctx.cache.set(cacheKey, resolved);
|
|
3575
|
+
return resolved;
|
|
3576
|
+
}
|
|
3577
|
+
async function resolveReference(link, ctx) {
|
|
3578
|
+
var _a, _b, _c, _d, _e;
|
|
3579
|
+
const cached = ctx.cache.get(link);
|
|
3580
|
+
if (cached) return cached;
|
|
3581
|
+
const id = unpackHmId(link);
|
|
3582
|
+
if (!id) throw new Error("Invalid HM link");
|
|
3583
|
+
let resolved;
|
|
3584
|
+
const profileAccountUid = ((_a = id.path) == null ? void 0 : _a[0]) === ":profile" ? id.path[1] || id.uid : !((_b = id.path) == null ? void 0 : _b.length) ? id.uid : null;
|
|
3585
|
+
if (profileAccountUid) {
|
|
3586
|
+
try {
|
|
3587
|
+
const account = await ctx.client.request("Account", profileAccountUid);
|
|
3588
|
+
if (account.type === "account") {
|
|
3589
|
+
resolved = { label: ((_c = account.metadata) == null ? void 0 : _c.name) || fallbackUid(profileAccountUid), type: "account" };
|
|
3590
|
+
ctx.cache.set(link, resolved);
|
|
3591
|
+
return resolved;
|
|
3592
|
+
}
|
|
3593
|
+
} catch {
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
const resource = await ctx.client.request("Resource", id);
|
|
3597
|
+
if (resource.type === "document") {
|
|
3598
|
+
resolved = {
|
|
3599
|
+
label: ((_d = resource.document.metadata) == null ? void 0 : _d.name) || fallbackDocLabel(id),
|
|
3600
|
+
type: "document",
|
|
3601
|
+
content: resource.document.content || [],
|
|
3602
|
+
version: id.version || resource.document.version,
|
|
3603
|
+
latestVersion: resource.document.version
|
|
3604
|
+
};
|
|
3605
|
+
} else if (resource.type === "comment") {
|
|
3606
|
+
let authorName = fallbackUid(resource.comment.author);
|
|
3607
|
+
try {
|
|
3608
|
+
const account = await ctx.client.request("Account", resource.comment.author);
|
|
3609
|
+
if (account.type === "account" && ((_e = account.metadata) == null ? void 0 : _e.name)) authorName = account.metadata.name;
|
|
3610
|
+
} catch {
|
|
3611
|
+
}
|
|
3612
|
+
resolved = {
|
|
3613
|
+
label: `Comment by ${authorName}`,
|
|
3614
|
+
type: "comment",
|
|
3615
|
+
content: resource.comment.content || [],
|
|
3616
|
+
version: id.version || resource.comment.version,
|
|
3617
|
+
latestVersion: resource.comment.version,
|
|
3618
|
+
author: resource.comment.author
|
|
3619
|
+
};
|
|
3620
|
+
} else {
|
|
3621
|
+
resolved = { label: fallbackLabel(link), type: "unknown" };
|
|
3622
|
+
}
|
|
3623
|
+
ctx.cache.set(link, resolved);
|
|
3624
|
+
return resolved;
|
|
3625
|
+
}
|
|
3626
|
+
function findBlockById(content, blockId) {
|
|
3627
|
+
for (const node of content) {
|
|
3628
|
+
if (node.block.id === blockId) return node;
|
|
3629
|
+
const found = findBlockById(node.children || [], blockId);
|
|
3630
|
+
if (found) return found;
|
|
3631
|
+
}
|
|
3632
|
+
return null;
|
|
3633
|
+
}
|
|
3634
|
+
function resolvedIdComment(id) {
|
|
3635
|
+
return `<!-- id:${id} -->`;
|
|
3636
|
+
}
|
|
3637
|
+
function appendResolvedIdToFirstLine(md, id) {
|
|
3638
|
+
const newline = md.indexOf("\n");
|
|
3639
|
+
if (newline === -1) return md ? `${md} ${resolvedIdComment(id)}` : resolvedIdComment(id);
|
|
3640
|
+
return `${md.slice(0, newline)} ${resolvedIdComment(id)}${md.slice(newline)}`;
|
|
3641
|
+
}
|
|
3642
|
+
function formatResolvedMediaUrl(url) {
|
|
3643
|
+
if (url.startsWith("ipfs://")) return `https://ipfs.io/ipfs/${url.slice(7)}`;
|
|
3644
|
+
return url;
|
|
3645
|
+
}
|
|
3646
|
+
function formatBlockRange(id) {
|
|
3647
|
+
const range = id.blockRange;
|
|
3648
|
+
if (!range) return "";
|
|
3649
|
+
if ("expanded" in range && range.expanded) return "+";
|
|
3650
|
+
if ("start" in range) return `[${range.start}:${range.end}]`;
|
|
3651
|
+
return "";
|
|
3652
|
+
}
|
|
3653
|
+
function fallbackDocLabel(id) {
|
|
3654
|
+
var _a;
|
|
3655
|
+
return ((_a = id.path) == null ? void 0 : _a.length) ? id.path.join("/") : fallbackUid(id.uid);
|
|
3656
|
+
}
|
|
3657
|
+
function fallbackLabel(link) {
|
|
3658
|
+
const id = unpackHmId(link);
|
|
3659
|
+
if (id) return fallbackDocLabel(id);
|
|
3660
|
+
return link;
|
|
3661
|
+
}
|
|
3662
|
+
function fallbackUid(uid) {
|
|
3663
|
+
return uid.length > 12 ? `${uid.slice(0, 12)}...` : uid;
|
|
3664
|
+
}
|
|
3665
|
+
function resolvedIndent(depth) {
|
|
3666
|
+
return " ".repeat(depth);
|
|
3667
|
+
}
|
|
3238
3668
|
|
|
3239
3669
|
// src/block-diff.ts
|
|
3240
3670
|
function createBlocksMap(nodes, parentId = "") {
|
|
@@ -4445,8 +4875,10 @@ export {
|
|
|
4445
4875
|
blocksToMarkdown,
|
|
4446
4876
|
codePointLength,
|
|
4447
4877
|
commentRecordIdFromBlob,
|
|
4878
|
+
commentToResolvedMarkdown,
|
|
4448
4879
|
computeReplaceOps,
|
|
4449
4880
|
contactRecordIdFromBlob,
|
|
4881
|
+
contentToResolvedMarkdown,
|
|
4450
4882
|
createAutoLinkOps,
|
|
4451
4883
|
createBlocksMap,
|
|
4452
4884
|
createCapability,
|
|
@@ -4465,6 +4897,7 @@ export {
|
|
|
4465
4897
|
deleteContact,
|
|
4466
4898
|
documentContainsLinkToChild,
|
|
4467
4899
|
documentHasSelfQuery,
|
|
4900
|
+
documentToResolvedMarkdown,
|
|
4468
4901
|
draftFilename,
|
|
4469
4902
|
editorBlockToHMBlock,
|
|
4470
4903
|
editorBlocksToHMBlockNodes,
|
|
@@ -4502,6 +4935,7 @@ export {
|
|
|
4502
4935
|
resolveFileLinksInBlocks,
|
|
4503
4936
|
resolveHypermediaUrl,
|
|
4504
4937
|
resolveId,
|
|
4938
|
+
resolveIdWithClient,
|
|
4505
4939
|
serializeBlockRange,
|
|
4506
4940
|
shouldAutoLinkParent,
|
|
4507
4941
|
signDocumentChange,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type SeedClient } from './client';
|
|
2
|
+
import { type DomainResolverFn } from './hm-resolver';
|
|
3
|
+
import { type UnpackedHypermediaId } from './hm-types';
|
|
4
|
+
/** Options for resolving a user-supplied hypermedia ID or web URL. */
|
|
5
|
+
export type ResolveIdWithClientOptions = {
|
|
6
|
+
client?: SeedClient;
|
|
7
|
+
serverUrl?: string;
|
|
8
|
+
domainResolver?: DomainResolverFn;
|
|
9
|
+
};
|
|
10
|
+
/** Resolved hypermedia ID with the Seed client that should be used to read it. */
|
|
11
|
+
export type ResolvedIdWithClient = {
|
|
12
|
+
id: UnpackedHypermediaId;
|
|
13
|
+
client: SeedClient;
|
|
14
|
+
serverUrl: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Resolves an hm:// ID, gateway URL, or Seed site URL and returns the matching Seed client.
|
|
18
|
+
*
|
|
19
|
+
* This is the shared implementation used by CLI-like document readers:
|
|
20
|
+
* - hm:// IDs use the caller's configured client/server.
|
|
21
|
+
* - web URLs resolve through the documented OPTIONS/header flow and then read from that URL's origin.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveIdWithClient(rawId: string, options?: ResolveIdWithClientOptions): Promise<ResolvedIdWithClient>;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -59,6 +59,8 @@ export type {HMRequest, HMSigner, UnpackedHypermediaId} from './hm-types'
|
|
|
59
59
|
|
|
60
60
|
export {resolveHypermediaUrl, resolveId} from './hm-resolver'
|
|
61
61
|
export type {DomainResolverFn, DomainIdChangedCallback, ResolveOptions, ResolvedUrl} from './hm-resolver'
|
|
62
|
+
export {resolveIdWithClient} from './resource-read'
|
|
63
|
+
export type {ResolveIdWithClientOptions, ResolvedIdWithClient} from './resource-read'
|
|
62
64
|
|
|
63
65
|
export {fileToIpfsBlobs, filesToIpfsBlobs, resolveFileLinksInBlocks, hasFileLinks} from './file-to-ipfs'
|
|
64
66
|
export type {CollectedBlob} from './file-to-ipfs'
|
|
@@ -71,7 +73,17 @@ export {
|
|
|
71
73
|
markdownBlockNodesToHMBlockNodes,
|
|
72
74
|
} from './markdown-to-blocks'
|
|
73
75
|
export type {BlockNode, SeedBlock, Annotation} from './markdown-to-blocks'
|
|
74
|
-
export {
|
|
76
|
+
export {
|
|
77
|
+
blocksToMarkdown,
|
|
78
|
+
emitFrontmatter,
|
|
79
|
+
slugify,
|
|
80
|
+
draftFilename,
|
|
81
|
+
parseDraftFilename,
|
|
82
|
+
documentToResolvedMarkdown,
|
|
83
|
+
commentToResolvedMarkdown,
|
|
84
|
+
contentToResolvedMarkdown,
|
|
85
|
+
} from './blocks-to-markdown'
|
|
86
|
+
export type {ResolvedMarkdownOptions} from './blocks-to-markdown'
|
|
75
87
|
export type {BlocksToMarkdownOptions} from './blocks-to-markdown'
|
|
76
88
|
|
|
77
89
|
export {createBlocksMap, matchBlockIds, computeReplaceOps, hmBlockNodeToBlockNode} from './block-diff'
|