feat(workspace): merge bullet on Backspace, render hashtags inline
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m8s
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:
1 parent
bc13235da6
commit
c7e05b385c
3 files changed
+104
-29
No files matched your search
@@ -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 }) {
|
||||
|
||||
Reference in new issue
Block a user