feat(workspace): merge bullet on Backspace, render hashtags inline
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m8s

- Backspace at the start of a non-empty bullet appends its text to the
  previous visible bullet (skipped for blocks with children and for
  code/divider/table/progress blocks).
- Hashtags are rendered inline as clickable chips instead of a badge row
  below the text, so lines no longer grow taller.
- Targeted/highlighted bullets use a neutral background instead of accent.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Sonnet 5.5 committed 2026-10-28 08:31:25 +01:00
1 parent bc13235da6
commit c7e05b385c
3 files changed
+104 -29

No files matched your search

+38 -6
View File
@@ -7,11 +7,20 @@ import React from 'react';
* - ~~strikethrough~~
* - `code`
* - [label](url)
* - #hashtag (only when `options.onTagClick` is given), rendered inline as a clickable chip
*/
function renderInlineTokens(text: string): React.ReactNode[] {
export interface InlineMarkdownOptions {
onTagClick?: (tag: string) => void;
}
function renderInlineTokens(text: string, options?: InlineMarkdownOptions): React.ReactNode[] {
if (!text) return [];
const regex = /(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|~~[^~]+~~|\*[^*]+\*|_[^_]+_|\[([^\]]+)\]\(([^)]+)\))/g;
const tagAlt = options?.onTagClick ? '|#[a-zA-Z0-9äöüÄÖÜß_-]+' : '';
const regex = new RegExp(
'(`[^`]+`|\\*\\*[^*]+\\*\\*|__[^_]+__|~~[^~]+~~|\\*[^*]+\\*|_[^_]+_|\\[([^\\]]+)\\]\\(([^)]+)\\)' + tagAlt + ')',
'g'
);
const elements: React.ReactNode[] = [];
let lastIndex = 0;
@@ -24,7 +33,30 @@ function renderInlineTokens(text: string): React.ReactNode[] {
const token = match[0];
const key = `${match.index}-${token}`;
if (token.startsWith('`') && token.endsWith('`')) {
if (token.startsWith('#')) {
const prevChar = match.index > 0 ? text[match.index - 1] : '';
if (prevChar && !/[\s(]/.test(prevChar)) {
// e.g. an URL fragment like https://x/#anchor: keep as plain text
elements.push(token);
} else {
const tag = token.slice(1);
elements.push(
<span
key={key}
role="button"
tabIndex={-1}
onClick={(e) => {
e.stopPropagation();
options?.onTagClick?.(tag);
}}
className="text-accent bg-accent-dim hover:bg-accent/20 rounded-btn px-0.5 cursor-pointer transition-colors"
title={`Nach #${tag} filtern`}
>
{token}
</span>
);
}
} else if (token.startsWith('`') && token.endsWith('`')) {
elements.push(
<code key={key} className="px-1.5 py-0.5 rounded bg-panel-elevated text-accent font-mono text-[0.9em] border border-border/50">
{token.slice(1, -1)}
@@ -92,7 +124,7 @@ function renderInlineTokens(text: string): React.ReactNode[] {
* - `code`
* - [label](url)
*/
export function renderInlineMarkdown(text: string): React.ReactNode[] {
export function renderInlineMarkdown(text: string, options?: InlineMarkdownOptions): React.ReactNode[] {
if (!text) return [];
if (text.includes('```')) {
@@ -122,14 +154,14 @@ export function renderInlineMarkdown(text: string): React.ReactNode[] {
</div>
);
} else if (part) {
nodes.push(...renderInlineTokens(part));
nodes.push(...renderInlineTokens(part, options));
}
});
return nodes;
}
return renderInlineTokens(text);
return renderInlineTokens(text, options);
}
export function MarkdownViewer({ content, className = '' }: { content: string; className?: string }) {