feat: add help and about pages with sidebar navigation buttons

This commit is contained in:
Bruno Charest 2025-10-22 08:58:26 -04:00
parent eeb957cf17
commit 698bfef3d8
9 changed files with 295 additions and 2 deletions

View File

@ -29,6 +29,7 @@
(searchOptionsChange)="onHeaderSearchOptionsChange($event)" (searchOptionsChange)="onHeaderSearchOptionsChange($event)"
(markdownPlaygroundSelected)="setView('markdown-playground')" (markdownPlaygroundSelected)="setView('markdown-playground')"
(parametersOpened)="setView('parameters')" (parametersOpened)="setView('parameters')"
(helpPageRequested)="openHelpPage()"
></app-shell-nimbus-layout> ></app-shell-nimbus-layout>
} @else { } @else {
<main class="relative flex min-h-screen flex-col bg-bg-main text-text-main lg:flex-row lg:h-screen lg:overflow-hidden"> <main class="relative flex min-h-screen flex-col bg-bg-main text-text-main lg:flex-row lg:h-screen lg:overflow-hidden">

View File

@ -878,6 +878,19 @@ export class AppComponent implements OnInit, OnDestroy {
} }
} }
async openHelpPage(): Promise<void> {
// Load the help.md file from the vault
const helpPath = 'help.md';
await this.vaultService.ensureNoteLoadedByPath(helpPath);
const helpNoteId = this.vaultService.buildSlugIdFromPath(helpPath);
const helpNote = this.vaultService.getNoteById(helpNoteId);
if (helpNote) {
this.selectNote(helpNote.id);
this.logService.log('HELP_PAGE_OPEN');
}
}
async selectNote(noteId: string): Promise<void> { async selectNote(noteId: string): Promise<void> {
let note = this.vaultService.getNoteById(noteId); let note = this.vaultService.getNoteById(noteId);
if (!note) { if (!note) {

View File

@ -0,0 +1,131 @@
import { Component, EventEmitter, Output, ChangeDetectionStrategy, HostListener } from '@angular/core';
import { CommonModule } from '@angular/common';
import { trigger, transition, style, animate } from '@angular/animations';
@Component({
selector: 'app-about-panel',
standalone: true,
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('fadeInOut', [
transition(':enter', [
style({ opacity: 0 }),
animate('200ms ease-in', style({ opacity: 1 }))
]),
transition(':leave', [
animate('200ms ease-out', style({ opacity: 0 }))
])
]),
trigger('scaleIn', [
transition(':enter', [
style({ opacity: 0, transform: 'scale(0.95)' }),
animate('200ms ease-out', style({ opacity: 1, transform: 'scale(1)' }))
]),
transition(':leave', [
animate('150ms ease-in', style({ opacity: 0, transform: 'scale(0.95)' }))
])
])
],
template: `
<!-- Backdrop -->
<div
@fadeInOut
class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4"
(click)="onBackdropClick()">
<!-- Panel -->
<div
@scaleIn
class="bg-card dark:bg-main border border-border dark:border-gray-700 rounded-2xl shadow-2xl max-w-lg w-full p-8 relative"
(click)="$event.stopPropagation()">
<!-- Close button -->
<button
type="button"
class="absolute top-4 right-4 w-8 h-8 rounded-full hover:bg-surface1 dark:hover:bg-card transition-colors flex items-center justify-center text-muted hover:text-main"
(click)="close.emit()"
aria-label="Fermer">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
<!-- Content -->
<div class="text-center space-y-6">
<!-- Logo/Icon -->
<div class="flex justify-center">
<div class="w-20 h-20 rounded-2xl bg-gradient-to-br from-nimbus-400 to-nimbus-600 flex items-center justify-center text-4xl shadow-lg">
📖
</div>
</div>
<!-- App Name -->
<div>
<h1 class="text-3xl font-bold text-main dark:text-white mb-2">ObsiViewer</h1>
<p class="text-sm text-muted">Version {{ version }}</p>
</div>
<!-- Description -->
<p class="text-base text-main dark:text-gray-300 leading-relaxed">
Visualiseur moderne et élégant pour vos notes Obsidian.
Explorez, éditez et organisez vos connaissances avec une interface intuitive.
</p>
<!-- Author -->
<div class="pt-4 border-t border-border dark:border-gray-700">
<p class="text-sm text-muted">
Créé par <span class="font-semibold text-main dark:text-white">Bruno Charest</span>
</p>
</div>
<!-- Credits -->
<div class="text-xs text-muted space-y-1">
<p class="font-semibold text-main dark:text-gray-300 mb-2">Technologies utilisées</p>
<div class="flex flex-wrap justify-center gap-2">
<span class="px-3 py-1 bg-surface1 dark:bg-card rounded-full">Angular 20</span>
<span class="px-3 py-1 bg-surface1 dark:bg-card rounded-full">TailwindCSS</span>
<span class="px-3 py-1 bg-surface1 dark:bg-card rounded-full">CodeMirror 6</span>
<span class="px-3 py-1 bg-surface1 dark:bg-card rounded-full">Markdown-it</span>
<span class="px-3 py-1 bg-surface1 dark:bg-card rounded-full">Lucide Icons</span>
</div>
</div>
<!-- GitHub Link (optional) -->
<div class="pt-4">
<a
href="https://github.com/brunoCharest/ObsiViewer"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-sm text-nimbus-600 dark:text-nimbus-400 hover:text-nimbus-700 dark:hover:text-nimbus-300 transition-colors">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
Voir sur GitHub
</a>
</div>
</div>
</div>
</div>
`,
styles: [`
:host {
display: block;
}
`]
})
export class AboutPanelComponent {
@Output() close = new EventEmitter<void>();
readonly version = '1.0.0';
@HostListener('document:keydown.escape')
onEscapeKey(): void {
this.close.emit();
}
onBackdropClick(): void {
this.close.emit();
}
}

View File

@ -105,6 +105,24 @@ import { TrashExplorerComponent } from '../../layout/sidebar/trash/trash-explore
</ng-template> </ng-template>
</div> </div>
</section> </section>
<!-- Help & About section -->
<section class="border-b border-border dark:border-gray-800">
<div class="px-2 py-2 space-y-1">
<button
(click)="onHelpPageClick()"
class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card active:bg-surface2 dark:active:bg-gray-700 transition-all active:scale-[0.98] transform">
<span>🆘</span>
<span>Help Page</span>
</button>
<button
(click)="onAboutClick()"
class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card active:bg-surface2 dark:active:bg-gray-700 transition-all active:scale-[0.98] transform">
<span></span>
<span>About</span>
</button>
</div>
</section>
</div> </div>
<!-- Footer --> <!-- Footer -->
@ -136,6 +154,8 @@ export class AppSidebarDrawerComponent {
@Output() tagSelected = new EventEmitter<string>(); @Output() tagSelected = new EventEmitter<string>();
@Output() quickLinkSelected = new EventEmitter<string>(); @Output() quickLinkSelected = new EventEmitter<string>();
@Output() markdownPlaygroundSelected = new EventEmitter<void>(); @Output() markdownPlaygroundSelected = new EventEmitter<void>();
@Output() helpPageSelected = new EventEmitter<void>();
@Output() aboutSelected = new EventEmitter<void>();
open = { quick: true, folders: true, tags: false, trash: false, tests: true }; open = { quick: true, folders: true, tags: false, trash: false, tests: true };
@ -165,6 +185,16 @@ export class AppSidebarDrawerComponent {
this.mobileNav.sidebarOpen.set(false); this.mobileNav.sidebarOpen.set(false);
} }
onHelpPageClick(): void {
this.helpPageSelected.emit();
this.mobileNav.sidebarOpen.set(false);
}
onAboutClick(): void {
this.aboutSelected.emit();
this.mobileNav.sidebarOpen.set(false);
}
trashNotes = () => this.vault.trashNotes(); trashNotes = () => this.vault.trashNotes();
trashCount = () => this.vault.counts().trash; trashCount = () => this.vault.counts().trash;
trashHasContent = () => (this.vault.trashTree() || []).length > 0; trashHasContent = () => (this.vault.trashTree() || []).length > 0;

View File

@ -112,6 +112,24 @@ import { VaultService } from '../../../services/vault.service';
</ng-template> </ng-template>
</div> </div>
</section> </section>
<!-- Help & About section -->
<section class="border-b border-border dark:border-gray-800">
<div class="px-2 py-2 space-y-1">
<button
(click)="helpPageSelected.emit()"
class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card transition-colors">
<span>🆘</span>
<span>Help Page</span>
</button>
<button
(click)="aboutSelected.emit()"
class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card transition-colors">
<span></span>
<span>About</span>
</button>
</div>
</section>
</div> </div>
<!-- Footer placeholder --> <!-- Footer placeholder -->
@ -131,6 +149,8 @@ export class NimbusSidebarComponent {
@Output() tagSelected = new EventEmitter<string>(); @Output() tagSelected = new EventEmitter<string>();
@Output() quickLinkSelected = new EventEmitter<string>(); @Output() quickLinkSelected = new EventEmitter<string>();
@Output() markdownPlaygroundSelected = new EventEmitter<void>(); @Output() markdownPlaygroundSelected = new EventEmitter<void>();
@Output() helpPageSelected = new EventEmitter<void>();
@Output() aboutSelected = new EventEmitter<void>();
env = environment; env = environment;
open = { quick: true, folders: true, tags: false, trash: false, tests: true }; open = { quick: true, folders: true, tags: false, trash: false, tests: true };

View File

@ -17,11 +17,12 @@ import { QuickLinksComponent } from '../../features/quick-links/quick-links.comp
import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrollable-overlay.directive'; import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrollable-overlay.directive';
import { MarkdownPlaygroundComponent } from '../../features/tests/markdown-playground/markdown-playground.component'; import { MarkdownPlaygroundComponent } from '../../features/tests/markdown-playground/markdown-playground.component';
import { ParametersPage } from '../../features/parameters/parameters.page'; import { ParametersPage } from '../../features/parameters/parameters.page';
import { AboutPanelComponent } from '../../features/about/about-panel.component';
@Component({ @Component({
selector: 'app-shell-nimbus-layout', selector: 'app-shell-nimbus-layout',
standalone: true, standalone: true,
imports: [CommonModule, FileExplorerComponent, NoteViewerComponent, AppBottomNavigationComponent, AppSidebarDrawerComponent, AppTocOverlayComponent, SwipeNavDirective, NotesListComponent, NimbusSidebarComponent, QuickLinksComponent, ScrollableOverlayDirective, MarkdownPlaygroundComponent, ParametersPage], imports: [CommonModule, FileExplorerComponent, NoteViewerComponent, AppBottomNavigationComponent, AppSidebarDrawerComponent, AppTocOverlayComponent, SwipeNavDirective, NotesListComponent, NimbusSidebarComponent, QuickLinksComponent, ScrollableOverlayDirective, MarkdownPlaygroundComponent, ParametersPage, AboutPanelComponent],
template: ` template: `
<div class="relative h-screen flex flex-col bg-card dark:bg-main text-main dark:text-gray-100"> <div class="relative h-screen flex flex-col bg-card dark:bg-main text-main dark:text-gray-100">
@ -62,6 +63,8 @@ import { ParametersPage } from '../../features/parameters/parameters.page';
(tagSelected)="onTagSelected($event)" (tagSelected)="onTagSelected($event)"
(quickLinkSelected)="onQuickLink($event)" (quickLinkSelected)="onQuickLink($event)"
(markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()" (markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()"
(helpPageSelected)="onHelpPageSelected()"
(aboutSelected)="onAboutSelected()"
/> />
</aside> </aside>
</ng-container> </ng-container>
@ -209,6 +212,8 @@ import { ParametersPage } from '../../features/parameters/parameters.page';
(tagSelected)="onTagSelected($event)" (tagSelected)="onTagSelected($event)"
(quickLinkSelected)="onQuickLink($event)" (quickLinkSelected)="onQuickLink($event)"
(markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()" (markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()"
(helpPageSelected)="onHelpPageSelected()"
(aboutSelected)="onAboutSelected()"
></app-sidebar-drawer> ></app-sidebar-drawer>
@if (mobileNav.activeTab() === 'list') { @if (mobileNav.activeTab() === 'list') {
@ -244,6 +249,9 @@ import { ParametersPage } from '../../features/parameters/parameters.page';
<app-bottom-navigation></app-bottom-navigation> <app-bottom-navigation></app-bottom-navigation>
</div> </div>
<!-- About Panel Overlay -->
<app-about-panel *ngIf="showAboutPanel" (close)="showAboutPanel = false"></app-about-panel>
</div> </div>
` `
}) })
@ -254,6 +262,7 @@ export class AppShellNimbusLayoutComponent {
mobileNav = inject(MobileNavService); mobileNav = inject(MobileNavService);
noteFullScreen = false; noteFullScreen = false;
showAboutPanel = false;
@Input() vaultName = ''; @Input() vaultName = '';
@Input() effectiveFileTree: VaultNode[] = []; @Input() effectiveFileTree: VaultNode[] = [];
@ -283,6 +292,7 @@ export class AppShellNimbusLayoutComponent {
@Output() searchOptionsChange = new EventEmitter<any>(); @Output() searchOptionsChange = new EventEmitter<any>();
@Output() markdownPlaygroundSelected = new EventEmitter<void>(); @Output() markdownPlaygroundSelected = new EventEmitter<void>();
@Output() parametersOpened = new EventEmitter<void>(); @Output() parametersOpened = new EventEmitter<void>();
@Output() helpPageRequested = new EventEmitter<void>();
folderFilter: string | null = null; folderFilter: string | null = null;
listQuery: string = ''; listQuery: string = '';
@ -489,4 +499,12 @@ export class AppShellNimbusLayoutComponent {
this.navigateHeading.emit(headingId); this.navigateHeading.emit(headingId);
}, 100); }, 100);
} }
onHelpPageSelected(): void {
this.helpPageRequested.emit();
}
onAboutSelected(): void {
this.showAboutPanel = true;
}
} }

View File

@ -14,7 +14,8 @@ export type LogEvent =
| 'CALENDAR_SEARCH_EXECUTED' | 'CALENDAR_SEARCH_EXECUTED'
| 'THEME_CHANGE' | 'THEME_CHANGE'
| 'THEME_MODE_CHANGE' | 'THEME_MODE_CHANGE'
| 'LANGUAGE_CHANGE'; | 'LANGUAGE_CHANGE'
| 'HELP_PAGE_OPEN';
export interface LogContext { export interface LogContext {
route?: string; route?: string;

43
vault/help.md Normal file
View File

@ -0,0 +1,43 @@
---
titre: Guide d'utilisateur
auteur: Bruno Charest
creation_date: 2025-10-21
modification_date: 2025-10-21T22:42:09-04:00
catégorie: documentation
tags:
- aide
- guide
aliases: []
status: publié
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---
# 🧭 Guide d'utilisateur ObsiViewer
Bienvenue dans **ObsiViewer** !
Ce guide vous aidera à comprendre les principales fonctionnalités :
## 📂 Navigation
- La barre latérale permet d'accéder aux notes, favoris, tags et modèles.
- Les boutons en haut à droite donnent accès au mode édition, plein écran, et propriétés.
## ✏️ Édition
- Le bouton ✏️ ouvre le **mode édition** basé sur CodeMirror 6.
- Toutes les modifications sont sauvegardées automatiquement.
## 🏷️ Tags
- Cliquez sur un tag pour voir toutes les notes qui le contiennent.
- Utilisez l'icône 🏷️ à gauche pour modifier ou ajouter des tags.
## ⚙️ Paramètres
- Les thèmes, langues et préférences sont gérés depuis le menu principal.
---
💡 **Astuce :** vous pouvez mettre à jour ce guide directement depuis votre voute Markdown.

36
vault/help.md.bak Normal file
View File

@ -0,0 +1,36 @@
---
titre: Guide d'utilisateur
auteur: Bruno Charest
creation_date: 2025-10-21
modification_date: 2025-10-21
catégorie: documentation
tags:
- aide
- guide
status: publié
---
# 🧭 Guide d'utilisateur ObsiViewer
Bienvenue dans **ObsiViewer** !
Ce guide vous aidera à comprendre les principales fonctionnalités :
## 📂 Navigation
- La barre latérale permet d'accéder aux notes, favoris, tags et modèles.
- Les boutons en haut à droite donnent accès au mode édition, plein écran, et propriétés.
## ✏️ Édition
- Le bouton ✏️ ouvre le **mode édition** basé sur CodeMirror 6.
- Toutes les modifications sont sauvegardées automatiquement.
## 🏷️ Tags
- Cliquez sur un tag pour voir toutes les notes qui le contiennent.
- Utilisez l'icône 🏷️ à gauche pour modifier ou ajouter des tags.
## ⚙️ Paramètres
- Les thèmes, langues et préférences sont gérés depuis le menu principal.
---
💡 **Astuce :** vous pouvez mettre à jour ce guide directement depuis votre voute Markdown.