feat(workspace): links to single bullets
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m8s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m8s
- copy a link to a bullet from its action bubble or pick a bullet in the link dialog; [[Note#^id|Note › excerpt]] opens the note, jumps to and highlights the bullet, or says it is gone - wiki links render inline with their label and are clickable; the duplicate chip below the text is gone test(e2e): run each suite with its own container, image tag and port, so parallel runs from another working copy no longer remove our server Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d408e7d307
commit
1a49e6b304
12 files changed
+284
-45
No files matched your search
@@ -18,13 +18,13 @@ import {
|
||||
ExternalLink,
|
||||
Maximize2,
|
||||
Link as LinkIcon,
|
||||
FileText,
|
||||
Lock,
|
||||
AlertTriangle,
|
||||
Image as ImageIcon,
|
||||
Mic,
|
||||
AudioLines,
|
||||
FileOutput,
|
||||
Link2,
|
||||
Type,
|
||||
} from 'lucide-react';
|
||||
import { DocumentBlock, BlockType } from '../../lib/types';
|
||||
@@ -94,7 +94,9 @@ export interface OutlinerBlockProps {
|
||||
onBlurBlock?: (blockId: string) => void;
|
||||
onOpenTask?: (taskId: string) => void;
|
||||
onTagClick?: (tag: string) => void;
|
||||
onWikiLinkClick?: (pageTitle: string) => void;
|
||||
onWikiLinkClick?: (pageTitle: string, blockId?: string) => void;
|
||||
/** Copies a link to this bullet ([[Notiz#^id|…]]) */
|
||||
onCopyBlockLink?: (blockId: string) => void;
|
||||
onDragStartBlock?: (index: number) => void;
|
||||
onDragOverBlock?: (e: React.DragEvent, index: number) => void;
|
||||
onDropBlock?: (index: number) => void;
|
||||
@@ -144,6 +146,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
onOpenTask,
|
||||
onTagClick,
|
||||
onWikiLinkClick,
|
||||
onCopyBlockLink,
|
||||
onDragStartBlock,
|
||||
onDragOverBlock,
|
||||
onDropBlock,
|
||||
@@ -1065,9 +1068,9 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
}, [localContent]);
|
||||
|
||||
// #Hashtags are rendered inline inside the text, so only links need the badge bar below
|
||||
// Wiki links are readable and clickable in the text itself, so only web links get a chip
|
||||
const hasSpecialTokens = tokens.some(
|
||||
(t) =>
|
||||
(t.startsWith('[[') && t.endsWith(']]')) ||
|
||||
/^\[(.*?)\]\((https?:\/\/.*?)\)$/.test(t) ||
|
||||
(/^https?:\/\//.test(t) && t !== previewUrl)
|
||||
);
|
||||
@@ -1384,7 +1387,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
>
|
||||
{/* When actively focused, ghost text is invisible so textarea renders text/caret directly */}
|
||||
<span ref={renderedTextRef} className={activeFocus ? 'invisible' : ''}>
|
||||
{localContent ? renderInlineMarkdown(localContent, { onTagClick }) : '\u200b'}
|
||||
{localContent ? renderInlineMarkdown(localContent, { onTagClick, onWikiLinkClick }) : '\u200b'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1504,21 +1507,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
{hasSpecialTokens && !isFocused && (
|
||||
<div className="flex flex-wrap items-center gap-1 mt-0.5">
|
||||
{tokens.map((part, idx) => {
|
||||
if (part.startsWith('[[') && part.endsWith(']]')) {
|
||||
const page = part.slice(2, -2).trim();
|
||||
return (
|
||||
<button
|
||||
key={idx}
|
||||
type="button"
|
||||
onClick={() => onWikiLinkClick?.(page)}
|
||||
className="inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-medium text-status-info bg-status-info/10 hover:bg-status-info/20 rounded-btn transition-colors"
|
||||
title={`Notiz öffnen: ${page}`}
|
||||
>
|
||||
<FileText className="w-2.5 h-2.5 opacity-80" />
|
||||
<span>{page}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
if (part.startsWith('[[')) return null;
|
||||
const mdMatch = part.match(/^\[(.*?)\]\((https?:\/\/.*?)\)$/);
|
||||
if (mdMatch && safeHref(mdMatch[2])) {
|
||||
const label = mdMatch[1];
|
||||
@@ -1727,6 +1716,19 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Link to this bullet */}
|
||||
{onCopyBlockLink && localContent.trim() !== '' && !mediaPicker && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onCopyBlockLink(block.id)}
|
||||
className="p-1 sm:p-0.5 min-w-[28px] min-h-[28px] sm:min-w-0 sm:min-h-0 flex items-center justify-center text-faint hover:text-accent rounded-btn transition-colors cursor-pointer"
|
||||
title="Link zu diesem Punkt kopieren"
|
||||
aria-label="Link zu diesem Punkt kopieren"
|
||||
>
|
||||
<Link2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Branch into its own note */}
|
||||
{canEdit && onExtractBlock && localContent.trim() !== '' && !mediaPicker && (
|
||||
<button
|
||||
|
||||
Reference in new issue
Block a user