Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1420822
Add BFM card references for MarkdownDef (CS-10570)
lukemelia Apr 2, 2026
dd555c1
Fix lint errors and acceptance test approach
lukemelia Apr 2, 2026
1e31fce
Fix lint: import CardDef, fix modifier early-return for fallback
lukemelia Apr 2, 2026
817e723
Fix BFM fallback text: bake URL into HTML instead of modifier DOM writes
lukemelia Apr 2, 2026
e119521
Fix markdown file query field realm context
lukemelia Apr 2, 2026
0019f44
Hide resolved markdown card reference paths
lukemelia Apr 2, 2026
f45da04
Add navigation overlays to markdown card references
lukemelia Apr 2, 2026
2315c72
Prevent flash of raw URLs in markdown card references
lukemelia Apr 2, 2026
58f7bf9
Fix CI: remove _modifierHasRun gate, add @ember/runloop to expected refs
lukemelia Apr 2, 2026
a2ba317
Add comments explaining why modifier complexity is needed
lukemelia Apr 2, 2026
debe392
Prevent flash of raw URLs in markdown card references
lukemelia Apr 2, 2026
f15a4cb
sideload relationships when returning FileDef JSON-API
lukemelia Apr 2, 2026
fd61b86
Fix CI: remove server-side sideloading from file-meta responses
lukemelia Apr 3, 2026
e7cf1ea
Fix CI: add card-refs.md to directory listing, increase waitUntil tim…
lukemelia Apr 3, 2026
be011fa
Fix CI: wait for overlay click handler before clicking card reference
lukemelia Apr 3, 2026
9bb4bda
Fix CI: increase overlay click handler timeout for back-navigation test
lukemelia Apr 4, 2026
8318636
Fix CI: increase all waitUntil timeouts to 15s with diagnostic messages
lukemelia Apr 4, 2026
4f372d4
Fix CI: replace history.back() with URL bar navigation in back-nav test
lukemelia Apr 4, 2026
4c27f35
Fix lint: prettier formatting in back-navigation test
lukemelia Apr 4, 2026
fc706d7
Address PR review comments on bfm-card-references
lukemelia Apr 4, 2026
31bf2d5
Fix test: revert heading ID assertion not yet in this PR
lukemelia Apr 4, 2026
87cda0a
Handle .json extension in BFM card reference URLs
lukemelia Apr 6, 2026
d15aecb
Add BFM Layer 3 markdown extensions: alerts, footnotes, math, tables,…
lukemelia Apr 5, 2026
e899c91
Update BFM card reference tests for heading ID extension
lukemelia Apr 5, 2026
d26c6c6
Add Mermaid diagram client-side rendering (CS-10602)
lukemelia Apr 5, 2026
74ff718
Add KaTeX math client-side rendering (CS-10601)
lukemelia Apr 5, 2026
ad74c75
Fix build: add .woff asset rule for KaTeX fonts
lukemelia Apr 5, 2026
7b047f3
Add BFM showcase document to experiments realm
lukemelia Apr 5, 2026
b5794cf
Fix lint: prettier formatting and eslint rule violations
lukemelia Apr 5, 2026
3c4b81d
Fix KaTeX and Mermaid modifiers: use stable method refs with schedule…
lukemelia Apr 5, 2026
e9b15d7
Add diagnostic logging to KaTeX/Mermaid modifiers
lukemelia Apr 5, 2026
94c9c86
Fix CI: refactor KaTeX/Mermaid to string-level processing, fix headin…
lukemelia Apr 5, 2026
0ada8be
Fix: export bfm-math-render and bfm-mermaid-render from main entry point
lukemelia Apr 5, 2026
0d5b19e
Fix test: update heading id assertion to match user-content- prefix
lukemelia Apr 5, 2026
9a67165
Fix tests: update all heading id assertions to match user-content- pr…
lukemelia Apr 5, 2026
f6da55d
Fix prettier formatting in marked-sync test
lukemelia Apr 5, 2026
e08d772
Fix prettier formatting in marked-gfm-heading-id type declaration
lukemelia Apr 5, 2026
861cc4c
Fix CI: add marked-gfm-heading-id types to local-types directly
lukemelia Apr 5, 2026
8a7796f
Fix CI: move marked-gfm-heading-id types to standalone ambient file
lukemelia Apr 5, 2026
652e202
Fix CI: use require() for marked-alert and marked-footnote
lukemelia Apr 5, 2026
9c548d2
Fix prettier formatting for marked-alert and marked-footnote requires
lukemelia Apr 5, 2026
fd90242
Fix memory leak: register marked code renderer once instead of per-call
lukemelia Apr 6, 2026
652584c
Fix ESM default export interop for marked extensions in esbuild bundle
lukemelia Apr 6, 2026
0063995
Add RichMarkdownField composite FieldDef with linkedCards support
lukemelia Apr 6, 2026
fc2d6ec
Merge cs-bfm-markdown-extensions into cs-10578, resolving conflicts
lukemelia Apr 7, 2026
d5ccebb
Add ProseMirror WYSIWYG editor for RichMarkdownField (CS-10572)
lukemelia Apr 7, 2026
ce5f918
Add RichMarkdownField playground page to experiments realm
lukemelia Apr 7, 2026
445e5ce
Add authored markdown ↔ ProseMirror round-trip serialization (CS-10573)
lukemelia Apr 8, 2026
9f67de6
Fix relative card reference paths in RichMarkdownPlayground
lukemelia Apr 8, 2026
945d5f4
Render embedded cards inside ProseMirror editor (CS-10574)
lukemelia Apr 8, 2026
275b6f2
Add card insertion UX with slash command in Compose mode (CS-10575)
lukemelia Apr 8, 2026
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
1,176 changes: 1,176 additions & 0 deletions packages/base/prosemirror-editor.gts

Large diffs are not rendered by default.

118 changes: 118 additions & 0 deletions packages/base/rich-markdown.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
extractCardReferenceUrls,
relativeTo,
} from '@cardstack/runtime-common';

import {
CardDef,
Component,
FieldDef,
MarkdownField,
StringField,
contains,
containsMany,
field,
linksToMany,
} from './card-api';
import MarkdownTemplate from './default-templates/markdown';
import ProseMirrorEditor from './prosemirror-editor';
import { CardContextConsumer } from './field-component';

/**
* A composite FieldDef that stores markdown content and exposes structured
* `linkedCards` relationships for embedded card references.
*
* Unlike `MarkdownField` (which extends StringField), this field is a
* composite FieldDef with sub-fields for content, computed reference URLs,
* and query-based linked cards.
*
* Usage:
* ```
* import RichMarkdownField from 'https://cardstack.com/base/rich-markdown';
*
* class MyCard extends CardDef {
* @field body = contains(RichMarkdownField);
* }
* ```
*/
export class RichMarkdownField extends FieldDef {
static displayName = 'Rich Markdown';

/** The raw markdown text. Uses MarkdownField for textarea edit UI. */
@field content = contains(MarkdownField);

/** Resolved absolute URLs of `:card[URL]` and `::card[URL]` references. */
@field cardReferenceUrls = containsMany(StringField, {
computeVia: function (this: RichMarkdownField) {
if (!this.content) {
return [];
}
let baseUrl = this[relativeTo]?.href ?? '';
return extractCardReferenceUrls(this.content, baseUrl);
},
});

/** Cards referenced in the markdown, loaded via query. */
@field linkedCards = linksToMany(CardDef, {
query: {
filter: {
in: { id: '$this.cardReferenceUrls' },
},
},
});

static embedded = class Embedded extends Component<typeof this> {
get content() {
return this.args.model?.content ?? null;
}
get baseUrl(): string | null {
return this.args.model?.[relativeTo]?.href ?? null;
}
<template>
<MarkdownTemplate
@content={{this.content}}
@linkedCards={{@model.linkedCards}}
@cardReferenceBaseUrl={{this.baseUrl}}
/>
</template>
};

static atom = class Atom extends Component<typeof this> {
get content() {
return this.args.model?.content ?? null;
}
<template>
<MarkdownTemplate @content={{this.content}} />
</template>
};

static edit = class Edit extends Component<typeof this> {
updateContent = (markdown: string) => {
this.args.model.content = markdown;
};
get baseUrl(): string | null {
return this.args.model?.[relativeTo]?.href ?? null;
}
get linkedCards(): CardDef[] | null {
try {
return this.args.model?.linkedCards ?? null;
} catch {
// linksToMany query may fail in environments without a full card store
return null;
}
}
<template>
<CardContextConsumer as |context|>
<ProseMirrorEditor
@content={{@model.content}}
@onUpdate={{this.updateContent}}
@linkedCards={{this.linkedCards}}
@cardReferenceBaseUrl={{this.baseUrl}}
@getCards={{context.getCards}}
/>
</CardContextConsumer>
</template>
};
}

export default RichMarkdownField;
30 changes: 30 additions & 0 deletions packages/experiments-realm/RichMarkdownPlayground/playground.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"data": {
"meta": {
"adoptsFrom": {
"name": "RichMarkdownPlayground",
"module": "../rich-markdown-playground"
}
},
"type": "card",
"attributes": {
"cardInfo": {
"notes": null,
"name": "Rich Markdown Playground",
"summary": "Exercise the RichMarkdownField with its ProseMirror WYSIWYG editor.",
"cardThumbnailURL": null
},
"title": "Rich Markdown Playground",
"body": {
"content": "# Welcome to the Rich Markdown Playground\n\nThis card uses `RichMarkdownField` which provides a **ProseMirror WYSIWYG editor** in edit mode.\n\n## Features to Try\n\n### Inline Formatting\n\nYou can write **bold text**, *italic text*, and `inline code`.\n\n### Lists\n\n- Bullet list item one\n- Bullet list item two\n- Bullet list item three\n\n1. Ordered list first\n2. Ordered list second\n3. Ordered list third\n\n### Code Block\n\n```typescript\nimport { RichMarkdownField } from 'https://cardstack.com/base/rich-markdown';\n\nclass MyCard extends CardDef {\n @field body = contains(RichMarkdownField);\n}\n```\n\n### Blockquote\n\n> The ProseMirror editor supports keyboard shortcuts like **Cmd+B** for bold, **Cmd+I** for italic, and **Cmd+Z** for undo.\n\n---\n\n### Card References\n\nInline card atom: :card[../Author/jane-doe]\n\nBlock card:\n\n::card[../Author/jane-doe]\n\nSwitch to **Edit** mode to try the WYSIWYG editor!"
}
},
"relationships": {
"cardInfo.theme": {
"links": {
"self": null
}
}
}
}
}
89 changes: 89 additions & 0 deletions packages/experiments-realm/rich-markdown-playground.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
contains,
field,
CardDef,
Component,
} from 'https://cardstack.com/base/card-api';
import StringField from 'https://cardstack.com/base/string';
import { RichMarkdownField } from 'https://cardstack.com/base/rich-markdown';

export class RichMarkdownPlayground extends CardDef {
static displayName = 'Rich Markdown Playground';

@field title = contains(StringField);
@field body = contains(RichMarkdownField);

static isolated = class Isolated extends Component<typeof this> {
<template>
<article class='playground'>
<header>
<h1>{{@model.title}}</h1>
<p class='subtitle'>
This playground exercises the
<code>RichMarkdownField</code>
with its ProseMirror WYSIWYG editor. Switch to
<strong>Edit</strong>
mode to see the editor, or view the rendered markdown below.
</p>
</header>

<section class='content'>
<h2>Content</h2>
<div class='rendered-markdown'>
<@fields.body />
</div>
</section>
</article>

<style scoped>
.playground {
max-width: 800px;
margin: 0 auto;
padding: var(--boxel-sp-lg, 24px);
font-family: var(--boxel-font-family, sans-serif);
}

.playground header {
margin-bottom: var(--boxel-sp-lg, 24px);
padding-bottom: var(--boxel-sp, 16px);
border-bottom: 1px solid var(--boxel-border-color, #e0e0e0);
}

.playground h1 {
margin: 0 0 var(--boxel-sp-xs, 4px);
font-size: 1.75rem;
font-weight: 700;
}

.subtitle {
color: var(--boxel-400, #666);
margin: 0;
line-height: 1.5;
}

.subtitle code {
background: var(--boxel-100, #f5f5f5);
padding: 0.1em 0.4em;
border-radius: 3px;
font-size: 0.9em;
}

.content h2 {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: var(--boxel-sp-sm, 8px);
color: var(--boxel-400, #666);
text-transform: uppercase;
letter-spacing: 0.05em;
}

.rendered-markdown {
border: 1px solid var(--boxel-border-color, #e0e0e0);
border-radius: var(--boxel-border-radius, 4px);
padding: var(--boxel-sp, 16px);
min-height: 200px;
}
</style>
</template>
};
}
Loading