123 lines
6.1 KiB
TypeScript
123 lines
6.1 KiB
TypeScript
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { RouterModule } from '@angular/router';
|
|
import { FileExplorerComponent } from '../../../components/file-explorer/file-explorer.component';
|
|
import { QuickLinksComponent } from '../quick-links/quick-links.component';
|
|
import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrollable-overlay.directive';
|
|
import type { VaultNode, TagInfo } from '../../../types';
|
|
import { environment } from '../../../environments/environment';
|
|
|
|
@Component({
|
|
selector: 'app-nimbus-sidebar',
|
|
standalone: true,
|
|
imports: [CommonModule, RouterModule, FileExplorerComponent, QuickLinksComponent, ScrollableOverlayDirective],
|
|
host: { class: 'block h-full' },
|
|
template: `
|
|
<div class="h-full flex flex-col overflow-hidden select-none">
|
|
<!-- Header -->
|
|
<div class="h-12 flex items-center justify-between px-3 border-b border-gray-200 dark:border-gray-800">
|
|
<div class="text-sm font-semibold truncate">{{ vaultName }} - ObsiViewer</div>
|
|
<button (click)="toggleSidebarRequest.emit()" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800" title="Hide Sidebar">⟨⟨</button>
|
|
</div>
|
|
|
|
<!-- Content (scroll) -->
|
|
<div class="flex-1 overflow-y-auto min-h-0" appScrollableOverlay>
|
|
<!-- Section Tests (dev-only) -->
|
|
<section *ngIf="env.features.showTestSection" class="border-b border-gray-200 dark:border-gray-800">
|
|
<button class="w-full flex items-center justify-between px-3 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
(click)="open.tests = !open.tests">
|
|
<span>Section Tests</span>
|
|
<span class="text-xs text-gray-500">{{ open.tests ? '▾' : '▸' }}</span>
|
|
</button>
|
|
<div *ngIf="open.tests" class="px-3 py-2">
|
|
<button
|
|
(click)="onMarkdownPlaygroundClick()"
|
|
class="w-full text-left block text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100">
|
|
Markdown Playground
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Quick Links accordion -->
|
|
<section class="border-b border-gray-200 dark:border-gray-800">
|
|
<button class="w-full flex items-center justify-between px-3 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
(click)="open.quick = !open.quick">
|
|
<span>Quick Links</span>
|
|
<span class="text-xs text-gray-500">{{ open.quick ? '▾' : '▸' }}</span>
|
|
</button>
|
|
<div *ngIf="open.quick" class="pt-1">
|
|
<app-quick-links (quickLinkSelected)="onQuickLink($event)"></app-quick-links>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Folders accordion -->
|
|
<section class="border-b border-gray-200 dark:border-gray-800">
|
|
<button class="w-full flex items-center justify-between px-3 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
(click)="open.folders = !open.folders">
|
|
<span>Folders</span>
|
|
<span class="text-xs text-gray-500">{{ open.folders ? '▾' : '▸' }}</span>
|
|
</button>
|
|
<div *ngIf="open.folders" class="px-1 py-1">
|
|
<app-file-explorer [nodes]="effectiveFileTree" [selectedNoteId]="selectedNoteId" [foldersOnly]="true" (folderSelected)="folderSelected.emit($event)" (fileSelected)="fileSelected.emit($event)"></app-file-explorer>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Tags accordion -->
|
|
<section class="border-b border-gray-200 dark:border-gray-800">
|
|
<button class="w-full flex items-center justify-between px-3 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
(click)="open.tags = !open.tags">
|
|
<span>Tags</span>
|
|
<span class="text-xs text-gray-500">{{ open.tags ? '▾' : '▸' }}</span>
|
|
</button>
|
|
<div *ngIf="open.tags" class="px-2 py-2">
|
|
<ul class="space-y-0.5 text-sm">
|
|
<li *ngFor="let t of tags" class="flex items-center gap-2">
|
|
<button (click)="tagSelected.emit(t.name)" class="flex-1 text-left px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 truncate">
|
|
<span>🏷️</span>
|
|
<span class="ml-1">{{ t.name }}</span>
|
|
</button>
|
|
<span class="text-xs text-gray-500">{{ t.count }}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Trash accordion -->
|
|
<section class="border-b border-gray-200 dark:border-gray-800">
|
|
<button class="w-full flex items-center justify-between px-3 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
(click)="open.trash = !open.trash">
|
|
<span>Trash</span>
|
|
<span class="text-xs text-gray-500">{{ open.trash ? '▾' : '▸' }}</span>
|
|
</button>
|
|
<div *ngIf="open.trash" class="px-3 py-3 text-sm text-gray-500 dark:text-gray-400">Empty</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Footer placeholder -->
|
|
<div class="h-14 border-t border-gray-200 dark:border-gray-800 flex items-center px-3 text-xs text-gray-500">ObsiViewer</div>
|
|
</div>
|
|
`
|
|
})
|
|
export class NimbusSidebarComponent {
|
|
@Input() vaultName = '';
|
|
@Input() effectiveFileTree: VaultNode[] = [];
|
|
@Input() selectedNoteId: string | null = null;
|
|
@Input() tags: TagInfo[] = [];
|
|
|
|
@Output() toggleSidebarRequest = new EventEmitter<void>();
|
|
@Output() folderSelected = new EventEmitter<string>();
|
|
@Output() fileSelected = new EventEmitter<string>();
|
|
@Output() tagSelected = new EventEmitter<string>();
|
|
@Output() quickLinkSelected = new EventEmitter<string>();
|
|
@Output() markdownPlaygroundSelected = new EventEmitter<void>();
|
|
|
|
env = environment;
|
|
open = { quick: true, folders: true, tags: false, trash: false, tests: true };
|
|
|
|
onQuickLink(id: string) { this.quickLinkSelected.emit(id); }
|
|
|
|
onMarkdownPlaygroundClick(): void {
|
|
this.markdownPlaygroundSelected.emit();
|
|
}
|
|
}
|