Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 18 additions & 24 deletions packages/base/codemirror-editor.gts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
baseRealm,
trimJsonExtension,
maybeRelativeReference,
type VirtualNetwork,
resolveRRIReference,
rri,
} from '@cardstack/runtime-common';
import {
type BfmRefRange,
Expand Down Expand Up @@ -98,24 +99,17 @@ function isInline(kind: string): boolean {
return kind === 'inline';
}

function resolveUrl(
raw: string,
baseUrl: string | null | undefined,
virtualNetwork: VirtualNetwork | undefined,
): string {
// With a VN, resolve through it so prefix-form bases and registered
// prefix-form refs round-trip correctly. Without a VN, plain
// `new URL(raw, baseUrl)` still handles the common case — URL-form
// refs (with or without a base) and relative refs against a URL-form
// base. Prefix-form bases need a VN; `new URL()` throws on those and
// we fall back to the raw ref.
function resolveUrl(raw: string, baseUrl: string | null | undefined): string {
// Resolve in RRI space (no VirtualNetwork), matching the MarkDownTemplate
// display path. Instance ids are canonical (prefix form for mapped realms,
// URL for unmapped), so a prefix-form base resolves relative refs to RRI and
// a URL-form base to URL — either way the `in:{ id }` reference query below
// matches the indexed card (prefix ids are expanded to their URL forms
// index-side).
try {
if (virtualNetwork) {
return trimJsonExtension(
virtualNetwork.resolveURL(raw, baseUrl || undefined).href,
);
}
return trimJsonExtension(new URL(raw, baseUrl || undefined).href);
return trimJsonExtension(
resolveRRIReference(raw, baseUrl ? rri(baseUrl) : undefined),
);
} catch {
return trimJsonExtension(raw);
}
Expand Down Expand Up @@ -163,7 +157,6 @@ interface CodeMirrorEditorSignature {
linkedCards?: CardDef[] | null;
linkedFiles?: FileDef[] | null;
cardReferenceBaseUrl?: string | null;
cardReferenceVirtualNetwork?: VirtualNetwork;
/** When false, all syntax markers are visible (source mode). Default true. */
livePreview?: boolean;
getCards?: (
Expand Down Expand Up @@ -854,11 +847,10 @@ export default class CodeMirrorEditor extends GlimmerComponent<CodeMirrorEditorS

private resolvedUrlsForRefType(refType: 'card' | 'file'): string[] {
let baseUrl = this.args.cardReferenceBaseUrl;
let vn = this.args.cardReferenceVirtualNetwork;
let urls = new Set<string>();
for (let target of this._widgetTargets) {
if (target.refType === refType) {
urls.add(resolveUrl(target.cardId, baseUrl, vn));
urls.add(resolveUrl(target.cardId, baseUrl));
}
}
return [...urls];
Expand Down Expand Up @@ -911,7 +903,6 @@ export default class CodeMirrorEditor extends GlimmerComponent<CodeMirrorEditorS
get cardRenderTargets(): CardRenderTarget[] {
let targets = this._widgetTargets;
let baseUrl = this.args.cardReferenceBaseUrl;
let vn = this.args.cardReferenceVirtualNetwork;

// Resolve cards and files by URL from every available source. linkedCards /
// linkedFiles work when a store is present; the getCards resources resolve
Expand All @@ -936,7 +927,7 @@ export default class CodeMirrorEditor extends GlimmerComponent<CodeMirrorEditorS
addInstances(this.resolvedFiles);

return targets.map((target) => {
let resolvedUrl = resolveUrl(target.cardId, baseUrl, vn);
let resolvedUrl = resolveUrl(target.cardId, baseUrl);
return {
...target,
instance: instancesByUrl.get(resolvedUrl) ?? null,
Expand Down Expand Up @@ -1123,7 +1114,10 @@ export default class CodeMirrorEditor extends GlimmerComponent<CodeMirrorEditorS
{{on 'click' this._toggleEmbedPopover}}
><PlusIcon width='16' height='16' /></button>
{{#if this._embedPopoverOpen}}
<div class='toolbar-embed-popover' data-test-toolbar-embed-popover>
<div
class='toolbar-embed-popover'
data-test-toolbar-embed-popover
>
<button
type='button'
class='toolbar-embed-popover__item'
Expand Down
39 changes: 14 additions & 25 deletions packages/base/default-templates/markdown.gts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
extractMermaidBlocks,
processKatexPlaceholders,
replaceMermaidSvgs,
resolveRRIReference,
rri,
trimJsonExtension,
type VirtualNetwork,
} from '@cardstack/runtime-common';
import {
hasCodeBlocks,
Expand Down Expand Up @@ -79,24 +80,16 @@ interface RenderSlot {
typeName?: string; // present when state === 'unresolved'
}

function resolveUrl(
raw: string,
baseUrl: string | null | undefined,
virtualNetwork: VirtualNetwork | undefined,
): string {
// With a VN, resolve through it so prefix-form bases and registered
// prefix-form refs round-trip correctly. Without a VN, plain
// `new URL(raw, baseUrl)` still handles the common case — URL-form
// refs (with or without a base) and relative refs against a URL-form
// base. Prefix-form bases need a VN; `new URL()` throws on those and
// we fall back to the raw ref.
function resolveUrl(raw: string, baseUrl: string | null | undefined): string {
// Resolve in RRI space (no VirtualNetwork), the same way the reference
// extractors resolve the refs behind `linkedCards`/`linkedFiles`. Instance
// ids are canonical (the realm serves prefix form for mapped realms, URL for
// unmapped), so this produces the same form as a loaded card's `id` — the
// slot key (`card.id` / `file.id`) matches without a VirtualNetwork.
try {
if (virtualNetwork) {
return trimJsonExtension(
virtualNetwork.resolveURL(raw, baseUrl || undefined).href,
);
}
return trimJsonExtension(new URL(raw, baseUrl || undefined).href);
return trimJsonExtension(
resolveRRIReference(raw, baseUrl ? rri(baseUrl) : undefined),
);
} catch {
return trimJsonExtension(raw);
}
Expand All @@ -108,7 +101,6 @@ export default class MarkDownTemplate extends GlimmerComponent<{
linkedCards?: CardDef[] | null;
linkedFiles?: FileDef[] | null;
cardReferenceBaseUrl?: string | null;
cardReferenceVirtualNetwork?: VirtualNetwork;
};
}> {
@tracked monacoContextInternal: any = undefined;
Expand Down Expand Up @@ -208,7 +200,6 @@ export default class MarkDownTemplate extends GlimmerComponent<{
let linkedCards = this.args.linkedCards;
let linkedFiles = this.args.linkedFiles;
let baseUrl = this.args.cardReferenceBaseUrl;
let virtualNetwork = this.args.cardReferenceVirtualNetwork;
let pendingUpdate = false;
let pendingToken: unknown = undefined;
// On the very first modifier run the linked instances are likely still
Expand Down Expand Up @@ -278,7 +269,7 @@ export default class MarkDownTemplate extends GlimmerComponent<{
);
}

let resolvedUrl = resolveUrl(rawUrl, baseUrl, virtualNetwork);
let resolvedUrl = resolveUrl(rawUrl, baseUrl);

let instance =
refType === 'file'
Expand Down Expand Up @@ -569,8 +560,7 @@ export default class MarkDownTemplate extends GlimmerComponent<{
/>
{{else}}
<span
class='markdown-bfm-loading markdown-bfm-loading--inline-embed
markdown-bfm-loading--{{slot.format}}'
class='markdown-bfm-loading markdown-bfm-loading--inline-embed markdown-bfm-loading--{{slot.format}}'
style={{slot.style}}
aria-hidden='true'
data-test-markdown-bfm-loading-inline
Expand Down Expand Up @@ -599,8 +589,7 @@ export default class MarkDownTemplate extends GlimmerComponent<{
</span>
{{else}}
<span
class='markdown-bfm-broken markdown-bfm-broken--inline-embed
markdown-bfm-broken--{{slot.format}}'
class='markdown-bfm-broken markdown-bfm-broken--inline-embed markdown-bfm-broken--{{slot.format}}'
style={{slot.style}}
title={{slot.url}}
data-test-markdown-bfm-unresolved-inline
Expand Down
32 changes: 9 additions & 23 deletions packages/base/rich-markdown.gts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
containsMany,
field,
linksToMany,
virtualNetworkFor,
} from './card-api';
import MarkdownTemplate from './default-templates/markdown';
import CodeMirrorEditor from './codemirror-editor';
Expand Down Expand Up @@ -112,26 +111,22 @@ export class RichMarkdownField extends FieldDef {
get content() {
return this.args.model?.content ?? null;
}
get virtualNetwork() {
return this.args.model ? virtualNetworkFor(this.args.model) : undefined;
}
get baseUrl(): string | null {
let model = this.args.model;
let rel = model?.[relativeTo];
if (!model || !rel) {
return null;
}
return typeof rel === 'string'
? (virtualNetworkFor(model)?.toURL(rel).href ?? rel)
: rel.href;
// Instance ids are canonical (prefix form for mapped realms, URL for
// unmapped), so the reference base is the id as-is — no VirtualNetwork.
return typeof rel === 'string' ? rel : rel.href;
}
<template>
<MarkdownTemplate
@content={{this.content}}
@linkedCards={{@model.linkedCards}}
@linkedFiles={{@model.linkedFiles}}
@cardReferenceBaseUrl={{this.baseUrl}}
@cardReferenceVirtualNetwork={{this.virtualNetwork}}
/>
</template>
};
Expand All @@ -140,26 +135,22 @@ export class RichMarkdownField extends FieldDef {
get content() {
return this.args.model?.content ?? null;
}
get virtualNetwork() {
return this.args.model ? virtualNetworkFor(this.args.model) : undefined;
}
get baseUrl(): string | null {
let model = this.args.model;
let rel = model?.[relativeTo];
if (!model || !rel) {
return null;
}
return typeof rel === 'string'
? (virtualNetworkFor(model)?.toURL(rel).href ?? rel)
: rel.href;
// Instance ids are canonical (prefix form for mapped realms, URL for
// unmapped), so the reference base is the id as-is — no VirtualNetwork.
return typeof rel === 'string' ? rel : rel.href;
}
<template>
<MarkdownTemplate
@content={{this.content}}
@linkedCards={{@model.linkedCards}}
@linkedFiles={{@model.linkedFiles}}
@cardReferenceBaseUrl={{this.baseUrl}}
@cardReferenceVirtualNetwork={{this.virtualNetwork}}
/>
</template>
};
Expand All @@ -186,18 +177,15 @@ export class RichMarkdownField extends FieldDef {
updateContent = (markdown: string) => {
this.args.model.content = markdown;
};
get virtualNetwork() {
return this.args.model ? virtualNetworkFor(this.args.model) : undefined;
}
get baseUrl(): string | null {
let model = this.args.model;
let rel = model?.[relativeTo];
if (!model || !rel) {
return null;
}
return typeof rel === 'string'
? (virtualNetworkFor(model)?.toURL(rel).href ?? rel)
: rel.href;
// Instance ids are canonical (prefix form for mapped realms, URL for
// unmapped), so the reference base is the id as-is — no VirtualNetwork.
return typeof rel === 'string' ? rel : rel.href;
Comment on lines +186 to +188

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve relative refs in compose live preview

When this edit-view baseUrl is a prefix-form RRI (the case this change is targeting), CodeMirrorEditor still resolves widget targets with its old resolveUrl helper, which only handles prefix bases when @cardReferenceVirtualNetwork is provided (packages/base/codemirror-editor.gts:101-121, 855-864). Since this caller no longer passes the virtual network and now returns @scope/realm/... as-is, a relative embed such as :card[./Pet/mango] falls through to the catch path as the raw ./Pet/mango; the compose-mode getCards in:{id} query then cannot find the referenced card, so live preview shows unresolved widgets even though the preview/isolated MarkdownTemplate path resolves correctly. Either keep passing the VN here or update CodeMirrorEditor to use the same RRI resolver before dropping the prop.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in e05e909

}
get linkedCards(): CardDef[] | null {
try {
Expand Down Expand Up @@ -235,7 +223,6 @@ export class RichMarkdownField extends FieldDef {
@linkedCards={{@model.linkedCards}}
@linkedFiles={{@model.linkedFiles}}
@cardReferenceBaseUrl={{this.baseUrl}}
@cardReferenceVirtualNetwork={{this.virtualNetwork}}
/>
</div>
{{else}}
Expand All @@ -246,7 +233,6 @@ export class RichMarkdownField extends FieldDef {
@linkedCards={{this.linkedCards}}
@linkedFiles={{this.linkedFiles}}
@cardReferenceBaseUrl={{this.baseUrl}}
@cardReferenceVirtualNetwork={{this.virtualNetwork}}
@livePreview={{eq this._mode 'compose'}}
@getCards={{context.getCards}}
>
Expand Down
Loading