feat(workspace): add hashtag tabs matching tasks view style and interactive tile grid with search and sorting
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
This commit is contained in:
1 parent
23eff21520
commit
60fd1c9ae7
1 file changed
+421
-14
@@ -17,6 +17,8 @@ import {
|
||||
X,
|
||||
ExternalLink,
|
||||
Sparkles,
|
||||
ArrowLeft,
|
||||
CheckSquare,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
Document,
|
||||
@@ -40,7 +42,7 @@ interface WorkspaceViewProps {
|
||||
onStatsChange: () => void;
|
||||
}
|
||||
|
||||
type WorkspaceSubView = 'editor' | 'tag_detail' | 'todos';
|
||||
type WorkspaceSubView = 'editor' | 'tag_detail' | 'todos' | 'tags';
|
||||
|
||||
interface FolderTreeNode {
|
||||
name: string;
|
||||
@@ -248,6 +250,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
const [selectedTag, setSelectedTag] = useState<string | null>(null);
|
||||
const [tagDetails, setTagDetails] = useState<TagDetails | null>(null);
|
||||
const [tagNoteDraft, setTagNoteDraft] = useState<string>('');
|
||||
const [tagSearchQuery, setTagSearchQuery] = useState<string>('');
|
||||
const [tagSortBy, setTagSortBy] = useState<'count' | 'name'>('count');
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [expandedFolders, setExpandedFolders] = useState<Record<string, boolean>>({});
|
||||
@@ -925,6 +929,70 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
return [];
|
||||
}, [activeDocShares, shareModalDoc, shareModalFolder]);
|
||||
|
||||
// Aggregated metadata map for every tag (documents where it occurs, open todos count)
|
||||
const tagsMetadataMap = useMemo(() => {
|
||||
const map = new Map<
|
||||
string,
|
||||
{
|
||||
tag: string;
|
||||
count: number;
|
||||
docs: { id: string; title: string; icon?: string; folderPath?: string }[];
|
||||
openTodosCount: number;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const t of tagsSummary) {
|
||||
const cleanTag = t.tag.toLowerCase();
|
||||
const matchingDocs: { id: string; title: string; icon?: string; folderPath?: string }[] = [];
|
||||
let openTodos = 0;
|
||||
|
||||
for (const d of docs) {
|
||||
let hasInDoc = false;
|
||||
const blocks = d.blocks || [];
|
||||
for (const b of blocks) {
|
||||
const content = b.content || '';
|
||||
const regex = new RegExp(`(?:^|[\\s(])#(${cleanTag})(?=[\\s.,!?:;)#\\]}]|$)`, 'i');
|
||||
if (regex.test(content)) {
|
||||
hasInDoc = true;
|
||||
if (b.type === 'todo' && !b.checked) openTodos++;
|
||||
}
|
||||
}
|
||||
if (hasInDoc) {
|
||||
matchingDocs.push({
|
||||
id: d.id,
|
||||
title: d.title || 'Unbenannte Notiz',
|
||||
icon: d.icon,
|
||||
folderPath: d.folderPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
map.set(cleanTag, {
|
||||
tag: t.tag,
|
||||
count: t.count,
|
||||
docs: matchingDocs,
|
||||
openTodosCount: openTodos,
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
}, [tagsSummary, docs]);
|
||||
|
||||
// Filtered and sorted tags for the Kachel Grid view
|
||||
const filteredTags = useMemo(() => {
|
||||
const q = tagSearchQuery.trim().toLowerCase().replace(/^#/, '');
|
||||
let list = [...tagsSummary];
|
||||
if (q) {
|
||||
list = list.filter((t) => t.tag.toLowerCase().includes(q));
|
||||
}
|
||||
if (tagSortBy === 'count') {
|
||||
list.sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
|
||||
} else {
|
||||
list.sort((a, b) => a.tag.localeCompare(b.tag));
|
||||
}
|
||||
return list;
|
||||
}, [tagsSummary, tagSearchQuery, tagSortBy]);
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-4rem)] bg-canvas rounded-card border border-border overflow-hidden shadow-panel">
|
||||
{/* LEFT MASTER SIDEBAR */}
|
||||
@@ -981,18 +1049,20 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sub-view Switcher: Notizen vs. To-Dos */}
|
||||
<div className="grid grid-cols-2 gap-1 p-0.5 rounded-btn bg-canvas border border-border text-xs">
|
||||
{/* Sub-view Switcher: Notizen vs. To-Dos vs. Hashtags */}
|
||||
<div className="grid grid-cols-3 gap-1 p-0.5 rounded-btn bg-canvas border border-border text-xs">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className={`py-1 rounded-btn font-medium transition-colors ${
|
||||
className={`py-1.5 px-1 rounded-btn font-medium transition-colors flex items-center justify-center gap-1 cursor-pointer truncate ${
|
||||
subView === 'editor'
|
||||
? 'bg-panel-elevated text-main font-semibold shadow-sm'
|
||||
? 'bg-panel-elevated text-main font-semibold shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="Notizen & Dokumente"
|
||||
>
|
||||
Dokumente ({docs.length})
|
||||
<FileText className="w-3.5 h-3.5 text-accent shrink-0" />
|
||||
<span className="truncate">Notizen</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1000,13 +1070,37 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
setSubView('todos');
|
||||
setMobileShowDetail(true);
|
||||
}}
|
||||
className={`py-1 rounded-btn font-medium transition-colors ${
|
||||
className={`py-1.5 px-1 rounded-btn font-medium transition-colors flex items-center justify-center gap-1 cursor-pointer truncate ${
|
||||
subView === 'todos'
|
||||
? 'bg-panel-elevated text-main font-semibold shadow-sm'
|
||||
? 'bg-panel-elevated text-main font-semibold shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="To-Dos Rollup"
|
||||
>
|
||||
To-Dos ({totalOpenTodos})
|
||||
<Check className="w-3.5 h-3.5 text-status-ok shrink-0" />
|
||||
<span className="truncate">To-Dos</span>
|
||||
{totalOpenTodos > 0 && (
|
||||
<span className="badge-neutral text-[9px] px-1 py-0">{totalOpenTodos}</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSubView('tags');
|
||||
setMobileShowDetail(true);
|
||||
}}
|
||||
className={`py-1.5 px-1 rounded-btn font-medium transition-colors flex items-center justify-center gap-1 cursor-pointer truncate ${
|
||||
subView === 'tags' || subView === 'tag_detail'
|
||||
? 'bg-panel-elevated text-main font-semibold shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="Hashtag Kacheln & Übersicht"
|
||||
>
|
||||
<Hash className="w-3.5 h-3.5 text-accent shrink-0" />
|
||||
<span className="truncate">Hashtags</span>
|
||||
{tagsSummary.length > 0 && (
|
||||
<span className="badge-neutral text-[9px] px-1 py-0">{tagsSummary.length}</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1189,22 +1283,292 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
onWikiLinkClick={handleWikiLinkClick}
|
||||
onBackMobile={() => setMobileShowDetail(false)}
|
||||
/>
|
||||
) : subView === 'tags' ? (
|
||||
/* =========================================================================
|
||||
HASHTAG OVERVIEW & TILE DIRECTORY (KACHEL-ANSICHT MIT SUCHE)
|
||||
========================================================================= */
|
||||
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
||||
<div className="max-w-4xl mx-auto space-y-5">
|
||||
{/* Top Navigation Tabs */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-3">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
||||
>
|
||||
<FileText className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Notizen</span>
|
||||
<span className="badge-neutral text-[10px]">{docs.length}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSubView('todos');
|
||||
setMobileShowDetail(true);
|
||||
}}
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
||||
>
|
||||
<Check className="w-3.5 h-3.5 text-status-ok" />
|
||||
<span>To-Dos</span>
|
||||
<span className="badge-neutral text-[10px]">{totalOpenTodos}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-panel border-accent text-main font-semibold shadow-xs"
|
||||
>
|
||||
<Hash className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Hashtags</span>
|
||||
<span className="badge-neutral text-[10px]">{tagsSummary.length}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
||||
>
|
||||
Zum Editor
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Header with Title and Search Controls */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Hash className="w-5 h-5 text-accent" />
|
||||
<h1 className="text-xl font-bold text-main">Hashtags Übersicht</h1>
|
||||
<span className="badge-neutral text-xs font-mono">{tagsSummary.length} Tags</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted mt-0.5">
|
||||
Entdecke alle Themen, Schlagworte und verknüpften Blöcke aus deinen Notizen
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search & Sort Toolbar */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative min-w-[200px] sm:min-w-[240px]">
|
||||
<Search className="w-3.5 h-3.5 text-faint absolute left-2.5 top-1/2 -translate-y-1/2" />
|
||||
<input
|
||||
type="text"
|
||||
value={tagSearchQuery}
|
||||
onChange={(e) => setTagSearchQuery(e.target.value)}
|
||||
placeholder="Hashtags durchsuchen…"
|
||||
className="input-field w-full pl-8 pr-7 py-1.5 text-xs"
|
||||
/>
|
||||
{tagSearchQuery && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTagSearchQuery('')}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-faint hover:text-main cursor-pointer"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sort Mode Switcher */}
|
||||
<div className="flex items-center bg-canvas border border-border rounded-btn p-0.5 text-[11px]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTagSortBy('count')}
|
||||
className={`px-2 py-1 rounded-btn font-semibold transition-colors cursor-pointer ${
|
||||
tagSortBy === 'count'
|
||||
? 'bg-panel-elevated text-main shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="Nach Häufigkeit sortieren"
|
||||
>
|
||||
Häufigkeit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTagSortBy('name')}
|
||||
className={`px-2 py-1 rounded-btn font-semibold transition-colors cursor-pointer ${
|
||||
tagSortBy === 'name'
|
||||
? 'bg-panel-elevated text-main shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="Alphabetisch sortieren"
|
||||
>
|
||||
A-Z
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Grid of Hashtag Tiles (Kacheln) */}
|
||||
{filteredTags.length === 0 ? (
|
||||
<div className="p-12 text-center text-muted text-xs space-y-2 border border-border rounded-card bg-canvas">
|
||||
<Hash className="w-8 h-8 text-faint mx-auto" />
|
||||
<p className="font-semibold text-main text-sm">
|
||||
{tagSearchQuery
|
||||
? `Keine Hashtags für "${tagSearchQuery}" gefunden`
|
||||
: 'Noch keine Hashtags vorhanden'}
|
||||
</p>
|
||||
<p className="text-faint max-w-sm mx-auto">
|
||||
{tagSearchQuery
|
||||
? 'Versuche einen anderen Suchbegriff oder setze die Suche zurück.'
|
||||
: 'Schreibe #tag in einen beliebigen Notiz-Bullet, um Themen automatisch als Kachel zu gruppieren.'}
|
||||
</p>
|
||||
{tagSearchQuery && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTagSearchQuery('')}
|
||||
className="btn-action inline-flex items-center gap-1 px-3 py-1 text-xs text-accent mt-2 cursor-pointer"
|
||||
>
|
||||
Suche zurücksetzen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3.5">
|
||||
{filteredTags.map((t) => {
|
||||
const meta = tagsMetadataMap.get(t.tag.toLowerCase());
|
||||
return (
|
||||
<div
|
||||
key={t.tag}
|
||||
onClick={() => handleSelectTag(t.tag)}
|
||||
className="p-4 rounded-card border border-border bg-canvas hover:border-accent hover:shadow-panel transition-all cursor-pointer group flex flex-col justify-between space-y-3"
|
||||
>
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="w-7 h-7 rounded-btn bg-accent/10 border border-accent/20 text-accent font-bold flex items-center justify-center shrink-0 text-sm group-hover:bg-accent group-hover:text-on-accent transition-colors">
|
||||
#
|
||||
</span>
|
||||
<h3 className="font-bold text-sm text-main group-hover:text-accent transition-colors truncate">
|
||||
#{t.tag}
|
||||
</h3>
|
||||
</div>
|
||||
<span className="badge-neutral font-mono text-[10px] shrink-0">
|
||||
{t.count} {t.count === 1 ? 'Treffer' : 'Treffer'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{meta && meta.docs.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] font-semibold text-faint uppercase tracking-wider block">
|
||||
In {meta.docs.length} {meta.docs.length === 1 ? 'Notiz' : 'Notizen'}:
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{meta.docs.slice(0, 3).map((d) => (
|
||||
<span
|
||||
key={d.id}
|
||||
className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-btn bg-panel-elevated text-muted truncate max-w-[140px]"
|
||||
title={d.title}
|
||||
>
|
||||
<span>{d.icon || '📝'}</span>
|
||||
<span className="truncate">{d.title}</span>
|
||||
</span>
|
||||
))}
|
||||
{meta.docs.length > 3 && (
|
||||
<span className="text-[10px] text-faint self-center">
|
||||
+{meta.docs.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-2.5 border-t border-border/50 text-[11px]">
|
||||
<div>
|
||||
{meta && meta.openTodosCount > 0 ? (
|
||||
<span className="text-status-warn text-[10px] flex items-center gap-1 font-medium">
|
||||
<Check className="w-2.5 h-2.5" />
|
||||
{meta.openTodosCount} offene To-Dos
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-faint text-[10px]">Blöcke & Childs anzeigen</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-accent text-xs font-semibold group-hover:translate-x-0.5 transition-transform flex items-center gap-0.5">
|
||||
<span>Details</span>
|
||||
<span>→</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : subView === 'tag_detail' && selectedTag ? (
|
||||
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
<div className="flex items-center justify-between border-b border-border pb-3">
|
||||
{/* Top Navigation Tabs */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-3">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
||||
>
|
||||
<FileText className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Notizen</span>
|
||||
<span className="badge-neutral text-[10px]">{docs.length}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSubView('todos');
|
||||
setMobileShowDetail(true);
|
||||
}}
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
||||
>
|
||||
<Check className="w-3.5 h-3.5 text-status-ok" />
|
||||
<span>To-Dos</span>
|
||||
<span className="badge-neutral text-[10px]">{totalOpenTodos}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('tags')}
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-panel border-accent text-main font-semibold shadow-xs"
|
||||
>
|
||||
<Hash className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Hashtags</span>
|
||||
<span className="badge-neutral text-[10px]">{tagsSummary.length}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('tags')}
|
||||
className="btn-action inline-flex items-center gap-1 px-2.5 py-1 text-xs text-muted hover:text-main cursor-pointer"
|
||||
>
|
||||
<ArrowLeft className="w-3.5 h-3.5" />
|
||||
<span>Alle Kacheln</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
||||
>
|
||||
Zum Editor
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tag Context Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Hash className="w-5 h-5 text-accent" />
|
||||
<span className="w-7 h-7 rounded-btn bg-accent/10 border border-accent/20 text-accent font-bold flex items-center justify-center shrink-0 text-sm">
|
||||
#
|
||||
</span>
|
||||
<h1 className="text-xl font-bold text-main">
|
||||
#{selectedTag}
|
||||
</h1>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
||||
onClick={() => setSubView('tags')}
|
||||
className="text-xs text-accent hover:underline flex items-center gap-1 cursor-pointer"
|
||||
>
|
||||
Zurück zum Editor
|
||||
<ArrowLeft className="w-3 h-3" />
|
||||
<span>Zur Kachel-Übersicht</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1382,6 +1746,49 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
) : subView === 'todos' ? (
|
||||
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{/* Top Navigation Tabs */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-3">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
||||
>
|
||||
<FileText className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Notizen</span>
|
||||
<span className="badge-neutral text-[10px]">{docs.length}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-panel border-accent text-main font-semibold shadow-xs"
|
||||
>
|
||||
<Check className="w-3.5 h-3.5 text-status-ok" />
|
||||
<span>To-Dos</span>
|
||||
<span className="badge-neutral text-[10px]">{totalOpenTodos}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSubView('tags');
|
||||
setMobileShowDetail(true);
|
||||
}}
|
||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
||||
>
|
||||
<Hash className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Hashtags</span>
|
||||
<span className="badge-neutral text-[10px]">{tagsSummary.length}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
||||
>
|
||||
Zum Editor
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between border-b border-border pb-3 gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-main">
|
||||
|
||||
Reference in new issue
Block a user