diff --git a/packages/base/markdown-file-def.gts b/packages/base/markdown-file-def.gts index cd170a319e..574003808f 100644 --- a/packages/base/markdown-file-def.gts +++ b/packages/base/markdown-file-def.gts @@ -4,7 +4,6 @@ import { extractFileReferenceUrls, FRONTMATTER_PARSE_ERROR_SYMBOL, identifyCard, - VirtualNetwork, type FrontmatterParseError, } from '@cardstack/runtime-common'; import MarkdownIcon from '@cardstack/boxel-icons/align-box-left-middle'; @@ -17,7 +16,6 @@ import { containsMany, field, linksToMany, - virtualNetworkFor, } from './card-api'; import MarkdownTemplate from './default-templates/markdown'; import { @@ -497,11 +495,7 @@ export class MarkdownDef extends FileDef { if (!this.content) { return []; } - return extractCardReferenceUrls( - this.content, - this.id ?? '', - virtualNetworkFor(this) ?? new VirtualNetwork(), - ); + return extractCardReferenceUrls(this.content, this.id ?? ''); }, }); @@ -518,11 +512,7 @@ export class MarkdownDef extends FileDef { if (!this.content) { return []; } - return extractFileReferenceUrls( - this.content, - this.id ?? '', - virtualNetworkFor(this) ?? new VirtualNetwork(), - ); + return extractFileReferenceUrls(this.content, this.id ?? ''); }, }); @@ -629,16 +619,8 @@ export class MarkdownDef extends FileDef { // `frontmatter.rawContent`, and the verbatim file is always served from // the realm, so nothing is lost. content: body, - cardReferenceUrls: extractCardReferenceUrls( - body, - url, - new VirtualNetwork(), - ), - fileReferenceUrls: extractFileReferenceUrls( - body, - url, - new VirtualNetwork(), - ), + cardReferenceUrls: extractCardReferenceUrls(body, url), + fileReferenceUrls: extractFileReferenceUrls(body, url), }; // Boxel-specific frontmatter is namespaced under `boxel:`; generic diff --git a/packages/base/rich-markdown.gts b/packages/base/rich-markdown.gts index ec32ddc446..bfff78b96f 100644 --- a/packages/base/rich-markdown.gts +++ b/packages/base/rich-markdown.gts @@ -3,7 +3,6 @@ import { extractFileReferenceUrls, fieldSerializer, relativeTo, - VirtualNetwork, } from '@cardstack/runtime-common'; import { TrackedObject } from 'tracked-built-ins'; import { eq } from '@cardstack/boxel-ui/helpers'; @@ -62,22 +61,18 @@ export class RichMarkdownField extends FieldDef { if (!rel) { return ''; } - return typeof rel === 'string' - ? (virtualNetworkFor(this)?.toURL(rel).href ?? rel) - : rel.href; + // `relativeTo` is already a canonical RRI; references resolve against it in + // RRI space (no VirtualNetwork). + return typeof rel === 'string' ? rel : rel.href; } - /** Resolved absolute URLs of `:card[URL]` and `::card[URL]` references. */ + /** Resolved canonical-RRI references of `:card[URL]` and `::card[URL]`. */ @field cardReferenceUrls = containsMany(StringField, { computeVia: function (this: RichMarkdownField) { if (!this.content) { return []; } - return extractCardReferenceUrls( - this.content, - this.refBaseUrl, - virtualNetworkFor(this) ?? new VirtualNetwork(), - ); + return extractCardReferenceUrls(this.content, this.refBaseUrl); }, }); @@ -97,11 +92,7 @@ export class RichMarkdownField extends FieldDef { if (!this.content) { return []; } - return extractFileReferenceUrls( - this.content, - this.refBaseUrl, - virtualNetworkFor(this) ?? new VirtualNetwork(), - ); + return extractFileReferenceUrls(this.content, this.refBaseUrl); }, }); @@ -235,7 +226,10 @@ export class RichMarkdownField extends FieldDef { {{! Preview has no CodeMirrorEditor, so the sticky mode selector lives in its own docked bar above the rendered markdown. }}
- +
<:leadingControls> - + diff --git a/packages/host/app/components/operator-mode/preview-panel/rendered-markdown.gts b/packages/host/app/components/operator-mode/preview-panel/rendered-markdown.gts index 37863c9d72..e6667a4653 100644 --- a/packages/host/app/components/operator-mode/preview-panel/rendered-markdown.gts +++ b/packages/host/app/components/operator-mode/preview-panel/rendered-markdown.gts @@ -29,15 +29,14 @@ import { extractFileReferenceUrls, fileNameFromUrl, isCardErrorJSONAPI, + resolveRRIReference, rri, trimJsonExtension, - type VirtualNetwork, } from '@cardstack/runtime-common'; import { markdownToHtml } from '@cardstack/runtime-common/marked-sync'; import CardRenderer from '@cardstack/host/components/card-renderer'; -import type NetworkService from '@cardstack/host/services/network'; import type StoreService from '@cardstack/host/services/store'; import type { @@ -70,14 +69,14 @@ interface RenderSlot { typeName?: string; // present when state === 'unresolved' } -function resolveUrl( - raw: string, - baseUrl: string | undefined, - virtualNetwork: VirtualNetwork, -): string { +function resolveUrl(raw: string, baseUrl: string | undefined): string { try { + // Resolve in RRI space (no VirtualNetwork), the same way + // `extractCardReferenceUrls`/`extractFileReferenceUrls` resolve the refs + // that key `loadedCards`/`loadedFiles` — so a slot's resolved key matches + // the loaded instance's map key. return trimJsonExtension( - virtualNetwork.resolveRRI(raw, baseUrl ? rri(baseUrl) : undefined), + resolveRRIReference(raw, baseUrl ? rri(baseUrl) : undefined), ); } catch { return trimJsonExtension(raw); @@ -116,7 +115,6 @@ const DEFAULT_CARD_CONTEXT: Partial = { }; export default class RenderedMarkdown extends Component { - @service declare private network: NetworkService; @service declare private store: StoreService; @consume(CardContextName) declare private dynamicCardContext: CardContext; @@ -163,7 +161,6 @@ export default class RenderedMarkdown extends Component { return extractCardReferenceUrls( this.args.content, this.args.cardReferenceBaseUrl ?? '', - this.network.virtualNetwork, ); } @@ -173,7 +170,6 @@ export default class RenderedMarkdown extends Component { return extractFileReferenceUrls( this.args.content, this.args.cardReferenceBaseUrl ?? '', - this.network.virtualNetwork, ); } @@ -277,11 +273,7 @@ export default class RenderedMarkdown extends Component { ); } - let resolvedUrl = resolveUrl( - rawUrl, - baseUrl, - this.network.virtualNetwork, - ); + let resolvedUrl = resolveUrl(rawUrl, baseUrl); if (refType === 'file') { let file = filesByUrl.get(resolvedUrl); diff --git a/packages/host/tests/unit/bfm-card-references-test.ts b/packages/host/tests/unit/bfm-card-references-test.ts index 8e16f658c4..a2ad01c2c7 100644 --- a/packages/host/tests/unit/bfm-card-references-test.ts +++ b/packages/host/tests/unit/bfm-card-references-test.ts @@ -15,29 +15,18 @@ import { type BfmSizeSpec, } from '@cardstack/runtime-common/bfm-card-references'; import { markdownToHtml } from '@cardstack/runtime-common/marked-sync'; -import { VirtualNetwork } from '@cardstack/runtime-common/virtual-network'; - -const virtualNetwork = new VirtualNetwork(); module('Unit | bfm-card-references', function () { module('extractCardReferenceUrls', function () { test('extracts inline card references', function (assert) { let markdown = 'See :card[https://example.com/cards/1] for details.'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); test('extracts block card references', function (assert) { let markdown = '::card[https://example.com/cards/1]\n'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); @@ -47,11 +36,7 @@ module('Unit | bfm-card-references', function () { '', 'Text with :card[https://example.com/cards/2] inline.', ].join('\n'); - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, [ 'https://example.com/cards/1', 'https://example.com/cards/2', @@ -61,11 +46,7 @@ module('Unit | bfm-card-references', function () { test('extracts the URL from an inline ref with a size spec', function (assert) { let markdown = 'See :card[https://example.com/cards/1 | embedded] for details.'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual( urls, ['https://example.com/cards/1'], @@ -78,7 +59,6 @@ module('Unit | bfm-card-references', function () { let urls = extractCardReferenceUrls( markdown, 'https://realm.example/docs/file.md', - virtualNetwork, ); assert.deepEqual(urls, ['https://realm.example/docs/my-card']); }); @@ -89,11 +69,7 @@ module('Unit | bfm-card-references', function () { '', '::card[https://example.com/cards/2.json]', ].join('\n'); - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, [ 'https://example.com/cards/1', 'https://example.com/cards/2', @@ -105,11 +81,7 @@ module('Unit | bfm-card-references', function () { ':card[https://example.com/cards/1]', ':card[https://example.com/cards/1.json]', ].join('\n'); - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); @@ -118,11 +90,7 @@ module('Unit | bfm-card-references', function () { ':card[https://example.com/cards/1]', ':card[https://example.com/cards/1]', ].join('\n'); - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); @@ -130,56 +98,36 @@ module('Unit | bfm-card-references', function () { let markdown = ['```', ':card[https://example.com/cards/1]', '```'].join( '\n', ); - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, []); }); test('ignores references inside inline code', function (assert) { let markdown = 'Use `:card[url]` syntax.'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, []); }); test('ignores references inside multi-backtick inline code', function (assert) { let markdown = 'Use ``:card[https://example.com/cards/1]`` syntax.'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, []); }); test('skips malformed URLs with empty base', function (assert) { let markdown = ':card[not a valid url at all]'; - let urls = extractCardReferenceUrls(markdown, '', virtualNetwork); + let urls = extractCardReferenceUrls(markdown, ''); assert.deepEqual(urls, []); }); test('returns empty array for markdown without references', function (assert) { let markdown = '# Hello World\n\nNo card references here.'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, []); }); test('returns empty array for empty markdown', function (assert) { - let urls = extractCardReferenceUrls( - '', - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls('', 'https://base.com/'); assert.deepEqual(urls, []); }); }); @@ -191,11 +139,7 @@ module('Unit | bfm-card-references', function () { ':file[https://example.com/files/1.pdf]', '::file[https://example.com/files/2.pdf]', ].join('\n'); - let urls = extractFileReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractFileReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, [ 'https://example.com/files/1.pdf', 'https://example.com/files/2.pdf', @@ -207,18 +151,13 @@ module('Unit | bfm-card-references', function () { let urls = extractFileReferenceUrls( markdown, 'https://realm.example/notes/file.md', - virtualNetwork, ); assert.deepEqual(urls, ['https://realm.example/notes/docs/report.pdf']); }); test('returns empty array when there are no file references', function (assert) { let markdown = ':card[https://example.com/cards/1]'; - let urls = extractFileReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractFileReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, []); }); }); @@ -229,12 +168,10 @@ module('Unit | bfm-card-references', function () { ':card[https://example.com/cards/1]', ':file[https://example.com/files/1]', ].join('\n'); - let refs = extractBfmReferences( - markdown, - 'https://base.com/', - ['card', 'file'], - virtualNetwork, - ); + let refs = extractBfmReferences(markdown, 'https://base.com/', [ + 'card', + 'file', + ]); assert.deepEqual(refs, [ { url: 'https://example.com/cards/1', keyword: 'card' }, { url: 'https://example.com/files/1', keyword: 'file' }, @@ -246,12 +183,10 @@ module('Unit | bfm-card-references', function () { ':card[https://example.com/thing]', ':file[https://example.com/thing]', ].join('\n'); - let refs = extractBfmReferences( - markdown, - 'https://base.com/', - ['card', 'file'], - virtualNetwork, - ); + let refs = extractBfmReferences(markdown, 'https://base.com/', [ + 'card', + 'file', + ]); assert.strictEqual(refs.length, 1); assert.strictEqual(refs[0].url, 'https://example.com/thing'); }); @@ -984,31 +919,19 @@ module('Unit | bfm-card-references', function () { module('extractCardReferenceUrls with pipe syntax', function () { test('extracts URL from block ref with size specifier', function (assert) { let markdown = '::card[https://example.com/cards/1 | strip]\n'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); test('extracts URL from block ref with custom dimensions', function (assert) { let markdown = '::card[https://example.com/cards/1 | 400x200]\n'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); test('extracts URL from block ref with isolated', function (assert) { let markdown = '::card[https://example.com/cards/1 | isolated]\n'; - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); @@ -1017,7 +940,6 @@ module('Unit | bfm-card-references', function () { let urls = extractCardReferenceUrls( markdown, 'https://realm.example/docs/file.md', - virtualNetwork, ); assert.deepEqual(urls, ['https://realm.example/docs/my-card']); }); @@ -1027,11 +949,7 @@ module('Unit | bfm-card-references', function () { '::card[https://example.com/cards/1 | strip]', '::card[https://example.com/cards/1 | tile]', ].join('\n'); - let urls = extractCardReferenceUrls( - markdown, - 'https://base.com/', - virtualNetwork, - ); + let urls = extractCardReferenceUrls(markdown, 'https://base.com/'); assert.deepEqual(urls, ['https://example.com/cards/1']); }); }); diff --git a/packages/runtime-common/bfm-card-references.ts b/packages/runtime-common/bfm-card-references.ts index f1b11d5474..5cae81d5a5 100644 --- a/packages/runtime-common/bfm-card-references.ts +++ b/packages/runtime-common/bfm-card-references.ts @@ -1,6 +1,6 @@ import { escapeHtml } from './helpers/html.ts'; -import type { VirtualNetwork } from './virtual-network.ts'; -import { trimJsonExtension } from './url.ts'; +import { resolveRRIReference, trimJsonExtension } from './url.ts'; +import type { RealmResourceIdentifier } from './realm-identifiers.ts'; import { FITTED_FORMATS } from './formats.ts'; import type { TokenizerAndRendererExtension } from './marked.mts'; @@ -11,16 +11,28 @@ const FENCED_CODE_RE = /```[\s\S]*?```/g; // (e.g. `code`, ``code``, ```code```). const INLINE_CODE_RE = new RegExp('(`+)([\\s\\S]*?)\\1', 'g'); -function resolveUrl( - ref: string, - baseUrl: string | undefined, - virtualNetwork: VirtualNetwork, -): string | null { +function resolveUrl(ref: string, baseUrl: string | undefined): string | null { + let resolved: string; try { - return virtualNetwork.resolveURL(ref, baseUrl || undefined).href; + // Identifiers are canonical RRI; resolve the reference against the base in + // RRI space (no VirtualNetwork). The search index tolerates the resulting + // canonical-RRI value for the `in:{id}` / `in:{url}` reference queries. + resolved = resolveRRIReference( + ref, + baseUrl ? (baseUrl as RealmResourceIdentifier) : undefined, + ); } catch { return null; } + // Keep only references that resolved to an absolute identifier — a URL or a + // prefix-form RRI. A reference that couldn't be made absolute (e.g. a + // relative ref with no base) is dropped rather than emitted as a bare, + // unmatchable query value. + return resolved.startsWith('http://') || + resolved.startsWith('https://') || + resolved.startsWith('@') + ? resolved + : null; } // ── BFM size spec parsing ── @@ -274,7 +286,6 @@ export function extractBfmReferences( markdown: string, baseUrl: string, keywords: string[], - virtualNetwork: VirtualNetwork, ): BfmReference[] { // Strip code blocks so references inside them are not extracted let stripped = markdown @@ -291,7 +302,7 @@ export function extractBfmReferences( for (let match of stripped.matchAll(blockRe)) { let { url: rawUrl } = splitBfmContent(match[1]); - let resolved = resolveUrl(rawUrl, baseUrl, virtualNetwork); + let resolved = resolveUrl(rawUrl, baseUrl); if (resolved) { matches.push({ index: match.index!, @@ -302,7 +313,7 @@ export function extractBfmReferences( } for (let match of stripped.matchAll(inlineRe)) { let { url: rawUrl } = splitBfmContent(match[1]); - let resolved = resolveUrl(rawUrl, baseUrl, virtualNetwork); + let resolved = resolveUrl(rawUrl, baseUrl); if (resolved) { matches.push({ index: match.index!, @@ -335,11 +346,8 @@ export function extractBfmReferences( export function extractCardReferenceUrls( markdown: string, baseUrl: string, - virtualNetwork: VirtualNetwork, ): string[] { - return extractBfmReferences(markdown, baseUrl, ['card'], virtualNetwork).map( - (r) => r.url, - ); + return extractBfmReferences(markdown, baseUrl, ['card']).map((r) => r.url); } /** @@ -349,11 +357,8 @@ export function extractCardReferenceUrls( export function extractFileReferenceUrls( markdown: string, baseUrl: string, - virtualNetwork: VirtualNetwork, ): string[] { - return extractBfmReferences(markdown, baseUrl, ['file'], virtualNetwork).map( - (r) => r.url, - ); + return extractBfmReferences(markdown, baseUrl, ['file']).map((r) => r.url); } /**