Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions src/components/editor/Editor/BacklinksPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { useState } from 'react';
import {
Link2,
ChevronDown,
ChevronRight,
FileText,
FolderOpen,
ArrowUpRight,
ArrowDownLeft,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import type { Backlink } from '../hooks/useBacklinks';

interface BacklinksPanelProps {
backlinks: Backlink[];
outgoingLinks: Backlink[];
onNavigateToNote: (noteId: string) => void;
}

export function BacklinksPanel({
backlinks,
outgoingLinks,
onNavigateToNote,
}: BacklinksPanelProps) {
const [isBacklinksExpanded, setIsBacklinksExpanded] = useState(true);
const [isOutgoingExpanded, setIsOutgoingExpanded] = useState(true);

const totalLinks = backlinks.length + outgoingLinks.length;

if (totalLinks === 0) {
return null;
}

return (
<div className="border-border bg-muted/30 border-t">
{/* Backlinks Section */}
{backlinks.length > 0 && (
<div className="border-border border-b">
<button
onClick={() => setIsBacklinksExpanded(!isBacklinksExpanded)}
className="hover:bg-muted/50 flex w-full items-center gap-2 px-4 py-2 text-left transition-colors"
>
{isBacklinksExpanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
<ArrowDownLeft className="text-muted-foreground h-4 w-4" />
<span className="text-sm font-medium">
{backlinks.length} note{backlinks.length !== 1 ? 's' : ''} link
here
</span>
</button>

{isBacklinksExpanded && (
<div className="px-2 pb-2">
{backlinks.map((backlink) => (
<Button
key={backlink.noteId}
variant="ghost"
size="sm"
className="h-auto w-full justify-start gap-2 px-2 py-1.5 text-left"
onClick={() => onNavigateToNote(backlink.noteId)}
>
<FileText className="text-muted-foreground h-4 w-4 shrink-0" />
<div className="min-w-0 flex-1">
<div className="truncate text-sm">{backlink.noteTitle}</div>
{backlink.folderName && (
<div className="text-muted-foreground flex items-center gap-1 text-xs">
<FolderOpen className="h-3 w-3" />
<span className="truncate">{backlink.folderName}</span>
</div>
)}
</div>
</Button>
))}
</div>
)}
</div>
)}

{/* Outgoing Links Section */}
{outgoingLinks.length > 0 && (
<div>
<button
onClick={() => setIsOutgoingExpanded(!isOutgoingExpanded)}
className="hover:bg-muted/50 flex w-full items-center gap-2 px-4 py-2 text-left transition-colors"
>
{isOutgoingExpanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
<ArrowUpRight className="text-muted-foreground h-4 w-4" />
<span className="text-sm font-medium">
{outgoingLinks.length} outgoing link
{outgoingLinks.length !== 1 ? 's' : ''}
</span>
</button>

{isOutgoingExpanded && (
<div className="px-2 pb-2">
{outgoingLinks.map((link) => (
<Button
key={link.noteId}
variant="ghost"
size="sm"
className="h-auto w-full justify-start gap-2 px-2 py-1.5 text-left"
onClick={() => onNavigateToNote(link.noteId)}
>
<FileText className="text-muted-foreground h-4 w-4 shrink-0" />
<div className="min-w-0 flex-1">
<div className="truncate text-sm">{link.noteTitle}</div>
{link.folderName && (
<div className="text-muted-foreground flex items-center gap-1 text-xs">
<FolderOpen className="h-3 w-3" />
<span className="truncate">{link.folderName}</span>
</div>
)}
</div>
</Button>
))}
</div>
)}
</div>
)}
</div>
);
}

/**
* Compact version for showing in status bar or header
*/
interface BacklinksIndicatorProps {
backlinksCount: number;
outgoingCount: number;
onClick?: () => void;
}

export function BacklinksIndicator({
backlinksCount,
outgoingCount,
onClick,
}: BacklinksIndicatorProps) {
const totalLinks = backlinksCount + outgoingCount;

if (totalLinks === 0) {
return null;
}

return (
<Button
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-foreground h-7 gap-1.5 px-2 text-xs"
onClick={onClick}
>
<Link2 className="h-3.5 w-3.5" />
<span>
{backlinksCount > 0 && (
<>
<ArrowDownLeft className="inline h-3 w-3" />
{backlinksCount}
</>
)}
{backlinksCount > 0 && outgoingCount > 0 && ' · '}
{outgoingCount > 0 && (
<>
<ArrowUpRight className="inline h-3 w-3" />
{outgoingCount}
</>
)}
</span>
</Button>
);
}
32 changes: 24 additions & 8 deletions src/components/editor/Editor/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
CheckSquare,
ChevronDown,
Link,
Link2,
Minus,
Highlighter,
FileText,
Expand Down Expand Up @@ -171,14 +172,29 @@ function ToolbarComponent({ editor }: ToolbarProps) {
>
<Code className="h-4 w-4" />
</Button>
<Button
variant={editor.isActive('link') ? 'default' : 'ghost'}
size="sm"
onClick={setLink}
title="Add Link (Ctrl+K)"
>
<Link className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant={editor.isActive('link') || editor.isActive('noteLink') ? 'default' : 'ghost'}
size="sm"
title="Links"
className="gap-1"
>
<Link className="h-4 w-4" />
<ChevronDown className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={setLink}>
<Link className="mr-2 h-4 w-4" />
External Link
</DropdownMenuItem>
<DropdownMenuItem onClick={() => editor.chain().focus().insertContent('[[').run()}>
<Link2 className="mr-2 h-4 w-4" />
Link to Note
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant={editor.isActive('highlight') ? 'default' : 'ghost'}
size="sm"
Expand Down
11 changes: 10 additions & 1 deletion src/components/editor/config/editor-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { TextStyle } from '@tiptap/extension-text-style';
import { TableOfContents } from '../extensions/TableOfContents';
import { ResizableImage } from '../extensions/ResizableImage';
import { ExecutableCodeBlock } from '../extensions/ExecutableCodeBlock';
import { NoteLink } from '../extensions/NoteLink';
import { StarterKit } from '@tiptap/starter-kit';
import bash from 'highlight.js/lib/languages/bash';
import cpp from 'highlight.js/lib/languages/cpp';
Expand Down Expand Up @@ -44,7 +45,11 @@ lowlight.register('php', php);
lowlight.register('sql', sql);
lowlight.register('markdown', markdown);

export function createEditorExtensions() {
export interface EditorExtensionsOptions {
onNoteLinkClick?: (noteId: string) => void;
}

export function createEditorExtensions(options: EditorExtensionsOptions = {}) {
return [
StarterKit.configure({
heading: {
Expand All @@ -57,6 +62,10 @@ export function createEditorExtensions() {
link: false,
underline: false,
}),
NoteLink.configure({
HTMLAttributes: {},
onNoteLinkClick: options.onNoteLinkClick,
}),
ExecutableCodeBlock.configure({
defaultLanguage: 'javascript',
HTMLAttributes: {
Expand Down
Loading
Loading