feat(workspace): render links and rich tokens inline inside blocks with direct edit support
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
This commit is contained in:
1 parent
7c47f245e2
commit
90a6062c7a
1 file changed
+182
-77
@@ -153,6 +153,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
const [linkSearchQuery, setLinkSearchQuery] = useState('');
|
const [linkSearchQuery, setLinkSearchQuery] = useState('');
|
||||||
const [linkWebTitle, setLinkWebTitle] = useState('');
|
const [linkWebTitle, setLinkWebTitle] = useState('');
|
||||||
const [linkWebUrl, setLinkWebUrl] = useState('');
|
const [linkWebUrl, setLinkWebUrl] = useState('');
|
||||||
|
const [activeEditingBlockId, setActiveEditingBlockId] = useState<string | null>(null);
|
||||||
|
const [editingLinkOriginalRaw, setEditingLinkOriginalRaw] = useState<string | null>(null);
|
||||||
|
|
||||||
// Drag and Drop reordering state for blocks
|
// Drag and Drop reordering state for blocks
|
||||||
const [draggingBlockId, setDraggingBlockId] = useState<string | null>(null);
|
const [draggingBlockId, setDraggingBlockId] = useState<string | null>(null);
|
||||||
@@ -1245,6 +1247,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
setSlashBlockId(null);
|
setSlashBlockId(null);
|
||||||
deleteBlockAndFocusPrev(index);
|
deleteBlockAndFocusPrev(index);
|
||||||
}
|
}
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
setActiveEditingBlockId(null);
|
||||||
|
(e.target as HTMLElement)?.blur();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1327,6 +1332,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
|
|
||||||
const focusBlock = (blockId: string) => {
|
const focusBlock = (blockId: string) => {
|
||||||
setLastFocusedBlockId(blockId);
|
setLastFocusedBlockId(blockId);
|
||||||
|
setActiveEditingBlockId(blockId);
|
||||||
const tryFocus = () => {
|
const tryFocus = () => {
|
||||||
const el = blockInputRefs.current[blockId];
|
const el = blockInputRefs.current[blockId];
|
||||||
if (el) {
|
if (el) {
|
||||||
@@ -1373,6 +1379,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
|
|
||||||
if (cmd.action) {
|
if (cmd.action) {
|
||||||
updateBlock(blockId, { content: cleanContent });
|
updateBlock(blockId, { content: cleanContent });
|
||||||
|
setEditingLinkOriginalRaw(null);
|
||||||
setLinkModalBlockId(blockId);
|
setLinkModalBlockId(blockId);
|
||||||
setLinkModalTab(cmd.action === 'insert_weblink' ? 'web' : 'note');
|
setLinkModalTab(cmd.action === 'insert_weblink' ? 'web' : 'note');
|
||||||
setLinkSearchQuery('');
|
setLinkSearchQuery('');
|
||||||
@@ -1419,13 +1426,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
if (!linkModalBlockId || !selectedDoc) return;
|
if (!linkModalBlockId || !selectedDoc) return;
|
||||||
const block = selectedDoc.blocks.find((b) => b.id === linkModalBlockId);
|
const block = selectedDoc.blocks.find((b) => b.id === linkModalBlockId);
|
||||||
if (block) {
|
if (block) {
|
||||||
const space = block.content && !block.content.endsWith(' ') ? ' ' : '';
|
let newContent: string;
|
||||||
const newContent = `${block.content}${space}[[${docTitle}]] `;
|
if (editingLinkOriginalRaw && block.content.includes(editingLinkOriginalRaw)) {
|
||||||
|
newContent = block.content.replace(editingLinkOriginalRaw, `[[${docTitle}]]`);
|
||||||
|
} else {
|
||||||
|
const space = block.content && !block.content.endsWith(' ') ? ' ' : '';
|
||||||
|
newContent = `${block.content}${space}[[${docTitle}]] `;
|
||||||
|
}
|
||||||
updateBlock(linkModalBlockId, { content: newContent });
|
updateBlock(linkModalBlockId, { content: newContent });
|
||||||
}
|
}
|
||||||
const targetBlockId = linkModalBlockId;
|
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
focusBlock(targetBlockId);
|
setEditingLinkOriginalRaw(null);
|
||||||
|
setActiveEditingBlockId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInsertWebLink = () => {
|
const handleInsertWebLink = () => {
|
||||||
@@ -1445,15 +1457,20 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (block) {
|
if (block) {
|
||||||
const space = block.content && !block.content.endsWith(' ') ? ' ' : '';
|
let newContent: string;
|
||||||
const newContent = `${block.content}${space}[${title}](${url}) `;
|
if (editingLinkOriginalRaw && block.content.includes(editingLinkOriginalRaw)) {
|
||||||
|
newContent = block.content.replace(editingLinkOriginalRaw, `[${title}](${url})`);
|
||||||
|
} else {
|
||||||
|
const space = block.content && !block.content.endsWith(' ') ? ' ' : '';
|
||||||
|
newContent = `${block.content}${space}[${title}](${url}) `;
|
||||||
|
}
|
||||||
updateBlock(linkModalBlockId, { content: newContent });
|
updateBlock(linkModalBlockId, { content: newContent });
|
||||||
}
|
}
|
||||||
const targetBlockId = linkModalBlockId;
|
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
|
setEditingLinkOriginalRaw(null);
|
||||||
setLinkWebTitle('');
|
setLinkWebTitle('');
|
||||||
setLinkWebUrl('');
|
setLinkWebUrl('');
|
||||||
focusBlock(targetBlockId);
|
setActiveEditingBlockId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDirectChildTodos = (blocks: DocumentBlock[], parentIndex: number): DocumentBlock[] => {
|
const getDirectChildTodos = (blocks: DocumentBlock[], parentIndex: number): DocumentBlock[] => {
|
||||||
@@ -1612,8 +1629,11 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasRichTokens = (text: string): boolean =>
|
||||||
|
Boolean(text && (text.includes('[[') || text.includes('#') || (text.includes('[') && text.includes(']('))));
|
||||||
|
|
||||||
// Helper to render text with clickable Wiki-Links, Web-Links and Hashtags
|
// Helper to render text with clickable Wiki-Links, Web-Links and Hashtags
|
||||||
const renderFormattedText = (text: string) => {
|
const renderFormattedText = (text: string, blockId?: string) => {
|
||||||
const parts = text.split(/(\[\[.*?\]\]|\[.*?\]\(https?:\/\/[^\s)]+\)|#[a-zA-Z0-9äöüÄÖÜß_-]+)/g);
|
const parts = text.split(/(\[\[.*?\]\]|\[.*?\]\(https?:\/\/[^\s)]+\)|#[a-zA-Z0-9äöüÄÖÜß_-]+)/g);
|
||||||
return parts.map((part, i) => {
|
return parts.map((part, i) => {
|
||||||
if (part.startsWith('[[') && part.endsWith(']]')) {
|
if (part.startsWith('[[') && part.endsWith(']]')) {
|
||||||
@@ -1622,24 +1642,44 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
onClick={(e) => {
|
className={`inline-flex items-center gap-1 px-1.5 py-0.5 mx-0.5 border text-xs font-medium rounded-btn transition-colors align-baseline ${
|
||||||
e.stopPropagation();
|
|
||||||
if (target) {
|
|
||||||
setSelectedDoc(target);
|
|
||||||
setZoomedBlockId(null);
|
|
||||||
setWorkspaceMode('editor');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={`inline-flex items-center gap-1 px-1.5 py-0.2 mx-0.5 border text-xs font-medium cursor-pointer rounded-btn transition-colors ${
|
|
||||||
target
|
target
|
||||||
? 'bg-accent/20 border-accent/40 text-accent hover:bg-accent/30'
|
? 'bg-accent/20 border-accent/40 text-accent hover:bg-accent/30'
|
||||||
: 'badge-neutral hover:text-main'
|
: 'badge-neutral hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
title={target ? `Öffne: ${target.title}` : `Dokument "${linkTitle}" existiert noch nicht`}
|
|
||||||
>
|
>
|
||||||
<span>📄</span>
|
<span
|
||||||
<span>{linkTitle}</span>
|
onClick={(e) => {
|
||||||
<ExternalLink className="w-2.5 h-2.5 opacity-60" />
|
e.stopPropagation();
|
||||||
|
if (target) {
|
||||||
|
setSelectedDoc(target);
|
||||||
|
setZoomedBlockId(null);
|
||||||
|
setWorkspaceMode('editor');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1 cursor-pointer"
|
||||||
|
title={target ? `Öffne: ${target.title}` : `Dokument "${linkTitle}" existiert noch nicht`}
|
||||||
|
>
|
||||||
|
<span>📄</span>
|
||||||
|
<span>{linkTitle}</span>
|
||||||
|
<ExternalLink className="w-2.5 h-2.5 opacity-60" />
|
||||||
|
</span>
|
||||||
|
{!isReadOnly && blockId && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setEditingLinkOriginalRaw(part);
|
||||||
|
setLinkModalBlockId(blockId);
|
||||||
|
setLinkModalTab('note');
|
||||||
|
setLinkSearchQuery(linkTitle);
|
||||||
|
}}
|
||||||
|
className="ml-0.5 p-0.5 rounded hover:bg-black/10 dark:hover:bg-white/10 opacity-60 hover:opacity-100 transition-opacity cursor-pointer"
|
||||||
|
title="Notiz-Link bearbeiten"
|
||||||
|
>
|
||||||
|
<Edit3 className="w-2.5 h-2.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1648,19 +1688,40 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
const title = webMatch[1] || webMatch[2];
|
const title = webMatch[1] || webMatch[2];
|
||||||
const url = webMatch[2];
|
const url = webMatch[2];
|
||||||
return (
|
return (
|
||||||
<a
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
href={url}
|
className="inline-flex items-center gap-1 px-1.5 py-0.5 mx-0.5 bg-sky-500/10 border border-sky-500/30 text-sky-500 dark:text-sky-400 hover:bg-sky-500/20 text-xs font-medium rounded-btn transition-colors align-baseline"
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
className="inline-flex items-center gap-1 px-1.5 py-0.2 mx-0.5 bg-sky-500/10 border border-sky-500/30 text-sky-500 dark:text-sky-400 hover:bg-sky-500/20 text-xs font-medium rounded-btn transition-colors"
|
|
||||||
title={`Öffne: ${url}`}
|
|
||||||
>
|
>
|
||||||
<span>🌐</span>
|
<a
|
||||||
<span>{title}</span>
|
href={url}
|
||||||
<ExternalLink className="w-2.5 h-2.5 opacity-60" />
|
target="_blank"
|
||||||
</a>
|
rel="noopener noreferrer"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="inline-flex items-center gap-1 cursor-pointer"
|
||||||
|
title={`Öffne: ${url}`}
|
||||||
|
>
|
||||||
|
<span>🌐</span>
|
||||||
|
<span>{title}</span>
|
||||||
|
<ExternalLink className="w-2.5 h-2.5 opacity-60" />
|
||||||
|
</a>
|
||||||
|
{!isReadOnly && blockId && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setEditingLinkOriginalRaw(part);
|
||||||
|
setLinkModalBlockId(blockId);
|
||||||
|
setLinkModalTab('web');
|
||||||
|
setLinkWebTitle(webMatch[1] || '');
|
||||||
|
setLinkWebUrl(url);
|
||||||
|
}}
|
||||||
|
className="ml-0.5 p-0.5 rounded hover:bg-sky-500/20 opacity-60 hover:opacity-100 transition-opacity cursor-pointer"
|
||||||
|
title="Link bearbeiten"
|
||||||
|
>
|
||||||
|
<Edit3 className="w-2.5 h-2.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (part.startsWith('#') && part.length > 1) {
|
if (part.startsWith('#') && part.length > 1) {
|
||||||
@@ -1672,7 +1733,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openTagPage(tagWord);
|
openTagPage(tagWord);
|
||||||
}}
|
}}
|
||||||
className="inline-flex items-center gap-0.5 px-1.5 py-0.2 mx-0.5 bg-blue-500/10 border border-blue-500/30 text-blue-500 dark:text-blue-400 hover:bg-blue-500/20 text-xs font-medium cursor-pointer rounded-btn transition-colors"
|
className="inline-flex items-center gap-0.5 px-1.5 py-0.5 mx-0.5 bg-blue-500/10 border border-blue-500/30 text-blue-500 dark:text-blue-400 hover:bg-blue-500/20 text-xs font-medium cursor-pointer rounded-btn transition-colors align-baseline"
|
||||||
title={`Öffne Hashtag-Seite: #${tagWord}`}
|
title={`Öffne Hashtag-Seite: #${tagWord}`}
|
||||||
>
|
>
|
||||||
<Hash className="w-2.5 h-2.5 opacity-80" />
|
<Hash className="w-2.5 h-2.5 opacity-80" />
|
||||||
@@ -1680,7 +1741,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <span key={i}>{part}</span>;
|
return <span key={i} className="whitespace-pre-wrap">{part}</span>;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2754,19 +2815,40 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
isReadOnly ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'
|
isReadOnly ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<input
|
{(!hasRichTokens(block.content) || activeEditingBlockId === block.id) && !isReadOnly ? (
|
||||||
ref={(el) => (blockInputRefs.current[block.id] = el)}
|
<input
|
||||||
type="text"
|
ref={(el) => (blockInputRefs.current[block.id] = el)}
|
||||||
readOnly={isReadOnly}
|
type="text"
|
||||||
value={block.content}
|
readOnly={isReadOnly}
|
||||||
onFocus={handleBlockFocus}
|
value={block.content}
|
||||||
onBlur={handleBlockBlur}
|
onFocus={() => {
|
||||||
onChange={(e) => handleContentChange(block, e.target.value)}
|
setActiveEditingBlockId(block.id);
|
||||||
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
|
handleBlockFocus();
|
||||||
className={`w-full bg-transparent text-sm focus:outline-none ${
|
}}
|
||||||
block.checked ? 'line-through text-faint' : 'text-main'
|
onBlur={() => {
|
||||||
} ${isReadOnly ? 'cursor-default' : ''}`}
|
setActiveEditingBlockId((prev) => (prev === block.id ? null : prev));
|
||||||
/>
|
handleBlockBlur();
|
||||||
|
}}
|
||||||
|
onChange={(e) => handleContentChange(block, e.target.value)}
|
||||||
|
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
|
||||||
|
className={`w-full bg-transparent text-sm focus:outline-none ${
|
||||||
|
block.checked ? 'line-through text-faint' : 'text-main'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!isReadOnly) {
|
||||||
|
focusBlock(block.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`w-full text-sm leading-relaxed break-words min-h-[20px] ${
|
||||||
|
block.checked ? 'line-through text-faint' : 'text-main'
|
||||||
|
} ${isReadOnly ? 'cursor-default' : 'cursor-text'}`}
|
||||||
|
>
|
||||||
|
{renderFormattedText(block.content, block.id)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2778,11 +2860,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(block.content.includes('[[') || block.content.includes('#') || (block.content.includes('[') && block.content.includes(']('))) && (
|
|
||||||
<div className="text-xs text-muted flex flex-wrap items-center gap-1 pt-0.5 pl-6">
|
|
||||||
{renderFormattedText(block.content)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Progress bar for child todos if any exist */}
|
{/* Progress bar for child todos if any exist */}
|
||||||
{(() => {
|
{(() => {
|
||||||
@@ -2996,22 +3073,36 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{/* TYPE: DEFAULT BULLET / PARAGRAPH */}
|
{/* TYPE: DEFAULT BULLET / PARAGRAPH */}
|
||||||
{(block.type === 'bullet' || block.type === 'paragraph') && (
|
{(block.type === 'bullet' || block.type === 'paragraph') && (
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<textarea
|
{(!hasRichTokens(block.content) || activeEditingBlockId === block.id) && !isReadOnly ? (
|
||||||
ref={(el) => (blockInputRefs.current[block.id] = el)}
|
<textarea
|
||||||
readOnly={isReadOnly}
|
ref={(el) => (blockInputRefs.current[block.id] = el)}
|
||||||
rows={Math.max(1, Math.ceil((block.content || '').length / 80))}
|
readOnly={isReadOnly}
|
||||||
value={block.content}
|
rows={Math.max(1, Math.ceil((block.content || '').length / 80))}
|
||||||
onFocus={handleBlockFocus}
|
value={block.content}
|
||||||
onBlur={handleBlockBlur}
|
onFocus={() => {
|
||||||
onChange={(e) => handleContentChange(block, e.target.value)}
|
setActiveEditingBlockId(block.id);
|
||||||
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
|
handleBlockFocus();
|
||||||
className={`w-full bg-transparent text-sm text-main leading-relaxed focus:outline-none resize-none placeholder:text-faint ${
|
}}
|
||||||
isReadOnly ? 'cursor-default' : ''
|
onBlur={() => {
|
||||||
}`}
|
setActiveEditingBlockId((prev) => (prev === block.id ? null : prev));
|
||||||
/>
|
handleBlockBlur();
|
||||||
{(block.content.includes('[[') || block.content.includes('#') || (block.content.includes('[') && block.content.includes(']('))) && (
|
}}
|
||||||
<div className="text-xs text-muted flex flex-wrap items-center gap-1 pt-0.5">
|
onChange={(e) => handleContentChange(block, e.target.value)}
|
||||||
{renderFormattedText(block.content)}
|
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
|
||||||
|
className="w-full bg-transparent text-sm text-main leading-relaxed focus:outline-none resize-none placeholder:text-faint"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!isReadOnly) {
|
||||||
|
focusBlock(block.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`w-full text-sm text-main leading-relaxed break-words min-h-[22px] ${
|
||||||
|
isReadOnly ? 'cursor-default' : 'cursor-text'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{renderFormattedText(block.content, block.id)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -3877,8 +3968,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-xs"
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const blockId = linkModalBlockId;
|
const blockId = linkModalBlockId;
|
||||||
|
const wasEditingLink = Boolean(editingLinkOriginalRaw);
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
if (blockId) focusBlock(blockId);
|
setEditingLinkOriginalRaw(null);
|
||||||
|
if (blockId && !wasEditingLink) focusBlock(blockId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -3891,14 +3984,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<div className="p-1.5 rounded-btn bg-accent/15 text-accent">
|
<div className="p-1.5 rounded-btn bg-accent/15 text-accent">
|
||||||
<ExternalLink className="w-4 h-4" />
|
<ExternalLink className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm font-semibold text-main">Link einfügen</h3>
|
<h3 className="text-sm font-semibold text-main">
|
||||||
|
{editingLinkOriginalRaw ? 'Link bearbeiten' : 'Link einfügen'}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const blockId = linkModalBlockId;
|
const blockId = linkModalBlockId;
|
||||||
|
const wasEditingLink = Boolean(editingLinkOriginalRaw);
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
if (blockId) focusBlock(blockId);
|
setEditingLinkOriginalRaw(null);
|
||||||
|
if (blockId && !wasEditingLink) focusBlock(blockId);
|
||||||
}}
|
}}
|
||||||
className="p-1 text-faint hover:text-main rounded-btn transition-colors cursor-pointer"
|
className="p-1 text-faint hover:text-main rounded-btn transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
@@ -3949,8 +4046,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const blockId = linkModalBlockId;
|
const blockId = linkModalBlockId;
|
||||||
|
const wasEditingLink = Boolean(editingLinkOriginalRaw);
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
if (blockId) focusBlock(blockId);
|
setEditingLinkOriginalRaw(null);
|
||||||
|
if (blockId && !wasEditingLink) focusBlock(blockId);
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
const filtered = docs.filter(
|
const filtered = docs.filter(
|
||||||
(d) =>
|
(d) =>
|
||||||
@@ -4031,8 +4130,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const blockId = linkModalBlockId;
|
const blockId = linkModalBlockId;
|
||||||
|
const wasEditingLink = Boolean(editingLinkOriginalRaw);
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
if (blockId) focusBlock(blockId);
|
setEditingLinkOriginalRaw(null);
|
||||||
|
if (blockId && !wasEditingLink) focusBlock(blockId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="input-field w-full h-9 text-xs"
|
className="input-field w-full h-9 text-xs"
|
||||||
@@ -4050,8 +4151,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const blockId = linkModalBlockId;
|
const blockId = linkModalBlockId;
|
||||||
|
const wasEditingLink = Boolean(editingLinkOriginalRaw);
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
if (blockId) focusBlock(blockId);
|
setEditingLinkOriginalRaw(null);
|
||||||
|
if (blockId && !wasEditingLink) focusBlock(blockId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="input-field w-full h-9 text-xs"
|
className="input-field w-full h-9 text-xs"
|
||||||
@@ -4063,8 +4166,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const blockId = linkModalBlockId;
|
const blockId = linkModalBlockId;
|
||||||
|
const wasEditingLink = Boolean(editingLinkOriginalRaw);
|
||||||
setLinkModalBlockId(null);
|
setLinkModalBlockId(null);
|
||||||
if (blockId) focusBlock(blockId);
|
setEditingLinkOriginalRaw(null);
|
||||||
|
if (blockId && !wasEditingLink) focusBlock(blockId);
|
||||||
}}
|
}}
|
||||||
className="btn-action h-8 px-3 text-xs cursor-pointer"
|
className="btn-action h-8 px-3 text-xs cursor-pointer"
|
||||||
>
|
>
|
||||||
@@ -4075,7 +4180,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
disabled={!linkWebUrl.trim()}
|
disabled={!linkWebUrl.trim()}
|
||||||
className="btn-primary h-8 px-4 text-xs font-medium cursor-pointer disabled:opacity-50"
|
className="btn-primary h-8 px-4 text-xs font-medium cursor-pointer disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Link einfügen
|
{editingLinkOriginalRaw ? 'Link speichern' : 'Link einfügen'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in new issue
Block a user