app/lib/site-markdown.ts
56 lines and 7 functions in DrDustinEdwards/carrel, at commit d5d5e82 on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| dustinedwards | 10 to 10 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| germomics | 12 to 12 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| attr | 15 to 17 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| urlPart | 22 to 24 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| <anonymous:23> | 23 to 23 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| plain | 27 to 27 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| figureMarkup | 29 to 31 | 2 | 100% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
Imports
It imports no other module of this repo. Imported by app/components/editor/writing-surface.tsx, app/routes/editor.tsx.
Source
// Each site's own Markdown for the things Carrel's editor inserts, shared by the server and the
// browser (the editor runs in both). One place per site, so the editor writes what the site renders.
import type { Scaffold } from "capsomer/react/markdown-editor";
/** How the site places an image with its alt text (stage 3), in the site's own dialect. */
const FIGURES: Record<string, (url: string, alt: string) => string> = {
// The site's figure directive, as its own editor inserts it (dustinedwards-info@f84b978,
// app/components/admin/use-image-upload.ts).
dustinedwards: (url, alt) => [`:::figure{src="${urlPart(url)}" alt="${alt.trim().replace(/"/g, """)}"}`, ":::"].join("\n"),
// germomics stores the article body as HTML, and its sanitizer keeps figure and img.
germomics: (url, alt) => `<figure><img src="${attr(url)}" alt="${attr(alt.trim())}"></figure>`,
};
function attr(value: string): string {
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
}
/** The URL with the characters that would end a directive attribute or a Markdown link percent-encoded: a quote, a
* space, parentheses, angle brackets. Encoding keeps the URL the same address, where an entity relies on the
* site's parser decoding it. */
function urlPart(url: string): string {
return url.replace(/["\s()<>]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
}
/** Plain Markdown for a site with no figure syntax of its own. */
const plain = (url: string, alt: string) => `![${alt.trim().replace(/[[\]]/g, "")}](${urlPart(url)})`;
export function figureMarkup(site: string, url: string, alt: string): string {
return (FIGURES[site] ?? plain)(url, alt);
}
/**
* The blocks the toolbar and the `/` menu insert, in the site's own dialect. Alt is mandatory on
* :::chart and :::diagram and the site's build fails without it, so the cursor lands inside alt="".
* Capsomer's editor knows no block syntax; these are the data it is given.
*/
export const SCAFFOLDS: Scaffold[] = [
{
id: "chart",
label: "Chart",
hint: "Bar, line, dot or area from inline CSV",
icon: "M4 20V10M10 20V4M16 20v-7M22 20H2",
text: [':::chart{type=bar x=name y=value title="" alt=""}', "```csv", "name,value", "first,1", "```", "An optional caption.", ":::"].join("\n"),
cursorAfter: 'alt="',
},
{
id: "diagram",
label: "Diagram",
hint: "Mermaid, rendered by build:diagrams",
icon: "M4 4h6a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zM15 15h5a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1zM10 6.5h4a3 3 0 0 1 3 3V15",
text: [':::diagram{title="" alt=""}', "```mermaid", "flowchart LR", " A[Start] --> B[End]", "```", "An optional caption.", ":::"].join("\n"),
cursorAfter: 'alt="',
},
{
id: "figure",
label: "Figure",
hint: "An image with a caption",
icon: "M5 4h14a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zM3 14l4-4 5 5M6 21h12",
text: [':::figure{src="" alt=""}', "A caption.", ":::"].join("\n"),
cursorAfter: 'src="',
},
];