fix(markdown): replace annotation with edited indicator
Some checks failed
/ deploy-and-package (push) Has been cancelled

This commit is contained in:
Alois 2026-08-10 00:07:22 +02:00
commit 03325797bf
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
4 changed files with 23 additions and 90 deletions

View file

@ -6,7 +6,7 @@ import rehypeSlug from "rehype-slug";
import remarkBreaks from "remark-breaks";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import type { ComponentProps } from "react";
import type { ComponentProps, ReactNode } from "react";
import { cn } from "../lib/utils";
import { CodeBlock } from "./code-block";
@ -38,7 +38,6 @@ import { EmojiProvider, useEmojiRegistry } from "./emoji-context";
import type { EmojiRegistry } from "./emoji-data";
import { parseCodeMeta, remarkCodeMeta } from "./markdown-meta";
import { remarkEmoji } from "./remark-emoji";
import { remarkSmallMuted } from "./remark-small-muted";
export type MarkdownTheme = "light" | "dark";
export type MarkdownVariant = "article" | "compact";
@ -48,6 +47,7 @@ export type MarkdownProps = Omit<ComponentProps<"div">, "children"> & {
components?: Partial<MarkdownComponents>;
registry?: EmojiRegistry;
theme?: MarkdownTheme;
trailingContent?: ReactNode;
variant?: MarkdownVariant;
};
@ -81,45 +81,6 @@ function classNames(node: Element) {
return value ? [String(value)] : [];
}
function moveFenceNotes(content: string) {
const lines = content.split("\n");
const fence = /^ {0,3}`{3,}/;
const closingFence = /^ {0,3}`{3,}\s*$/;
const note = /\?\?\+\+.*?\?\?\+\+/g;
let inCodeBlock = false;
let openingIndex = -1;
let openingNotes: string[] = [];
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
if (inCodeBlock) {
const closingNotes = line.match(note) ?? [];
const cleanedLine = line.replace(note, "").trimEnd();
if (closingFence.test(cleanedLine)) {
if (openingNotes.length > 0) {
lines[openingIndex] = lines[openingIndex].replace(note, "").trimEnd();
}
lines[index] = cleanedLine;
inCodeBlock = false;
const notes = [...openingNotes, ...closingNotes];
if (notes.length > 0) {
lines.splice(index + 1, 0, ...notes);
index += notes.length;
}
openingNotes = [];
}
continue;
}
if (!fence.test(line)) continue;
inCodeBlock = true;
openingIndex = index;
openingNotes = line.match(note) ?? [];
}
return lines.join("\n");
}
const defaultComponents: Components = {
a: MarkdownLink,
blockquote: MarkdownBlockquote,
@ -176,6 +137,7 @@ function Markdown({
components,
registry: registryOverride,
theme,
trailingContent,
variant = "article",
...props
}: MarkdownProps) {
@ -187,7 +149,6 @@ function Markdown({
remarkMath,
remarkBreaks,
remarkCodeMeta,
remarkSmallMuted,
[remarkEmoji, { registry }],
];
@ -210,8 +171,9 @@ function Markdown({
rehypePlugins={rehypePlugins}
components={{ ...defaultComponents, ...components }}
>
{moveFenceNotes(content)}
{content}
</ReactMarkdown>
{trailingContent}
</div>
</EmojiProvider>
);

View file

@ -1,45 +0,0 @@
import type { Emphasis, Parent, Root, Text } from "mdast";
import { SKIP, visit } from "unist-util-visit";
const delimiter = "??++";
export function remarkSmallMuted() {
return (tree: Root) => {
visit(tree, "text", (node: Text, index, parent: Parent | undefined) => {
if (index === undefined || !parent) return;
const children: Parent["children"] = [];
let cursor = 0;
let opening = node.value.indexOf(delimiter);
while (opening !== -1) {
const contentStart = opening + delimiter.length;
const closing = node.value.indexOf(delimiter, contentStart);
if (closing === -1) break;
if (opening > cursor) {
children.push({ type: "text", value: node.value.slice(cursor, opening) });
}
children.push({
type: "emphasis",
children: [{ type: "text", value: node.value.slice(contentStart, closing) }],
data: {
hName: "span",
hProperties: { className: ["text-xs", "text-muted-foreground"] },
},
} satisfies Emphasis);
cursor = closing + delimiter.length;
opening = node.value.indexOf(delimiter, cursor);
}
if (children.length === 0) return;
if (cursor < node.value.length) {
children.push({ type: "text", value: node.value.slice(cursor) });
}
parent.children.splice(index, 1, ...children);
return [SKIP, index + children.length];
});
};
}

View file

@ -6,14 +6,30 @@ import { Markdown } from "./markdown";
export type TextProps = {
fontSize?: CSSProperties["fontSize"];
registry?: EmojiRegistry;
showEditedIndicator?: boolean;
value: string;
};
export default function Text({ fontSize, registry, value }: TextProps) {
export default function Text({
fontSize,
registry,
showEditedIndicator = false,
value,
}: TextProps) {
return (
<Markdown
className={
showEditedIndicator
? "[&>p:nth-last-child(2)]:my-0 [&>p:nth-last-child(2)]:inline"
: undefined
}
content={value}
registry={registry}
trailingContent={
showEditedIndicator ? (
<span className="ms-1 text-xs text-muted-foreground">(edited)</span>
) : undefined
}
variant="compact"
style={{ fontSize: fontSize ?? "1rem" }}
/>