From 7fd4f5bf8e816581745ce9f726fd23d98136d48b Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Wed, 15 Oct 2025 14:28:46 -0400 Subject: [PATCH] tests --- src/app.component.simple.html | 6 + src/app.component.ts | 6 +- src/app/core/guards/dev-only.guard.ts | 10 ++ .../sidebar/nimbus-sidebar.component.ts | 28 +++- .../markdown-playground.component.ts | 129 ++++++++++++++++++ src/app/features/tests/tests.routes.ts | 14 ++ .../app-shell-nimbus.component.ts | 13 +- src/assets/samples/markdown-playground.md | 129 ++++++++++++++++++ src/environments/environment.prod.ts | 3 + src/environments/environment.ts | 3 + src/services/markdown.service.ts | 27 ++++ 11 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 src/app/core/guards/dev-only.guard.ts create mode 100644 src/app/features/tests/markdown-playground/markdown-playground.component.ts create mode 100644 src/app/features/tests/tests.routes.ts create mode 100644 src/assets/samples/markdown-playground.md diff --git a/src/app.component.simple.html b/src/app.component.simple.html index 2b34c02..b063963 100644 --- a/src/app.component.simple.html +++ b/src/app.component.simple.html @@ -14,6 +14,7 @@ [centerPanelWidth]="centerPanelWidth()" [searchTerm]="sidebarSearchTerm()" [tags]="allTags()" + [activeView]="activeView()" (noteSelected)="selectNote($event)" (tagClicked)="handleTagClick($event)" (wikiLinkActivated)="handleWikiLink($event)" @@ -25,6 +26,7 @@ (navigateHeading)="scrollToHeading($event)" (searchTermChange)="onSidebarSearchTermChange($event)" (searchOptionsChange)="onHeaderSearchOptionsChange($event)" + (markdownPlaygroundSelected)="setView('markdown-playground')" > } @else {
@@ -525,6 +527,10 @@ (nodeSelected)="selectNoteFromGraph($event)"> + } @else if (activeView() === 'markdown-playground') { +
+ +
} @else { @if (activeView() === 'drawings') { @if (currentDrawingPath()) { diff --git a/src/app.component.ts b/src/app.component.ts index a5af903..0819f80 100644 --- a/src/app.component.ts +++ b/src/app.component.ts @@ -21,6 +21,7 @@ import { GraphInlineSettingsComponent } from './app/graph/ui/inline-settings-pan import { DrawingsEditorComponent } from './app/features/drawings/drawings-editor.component'; import { DrawingsFileService, ExcalidrawScene } from './app/features/drawings/drawings-file.service'; import { AppShellNimbusLayoutComponent } from './app/layout/app-shell-nimbus/app-shell-nimbus.component'; +import { MarkdownPlaygroundComponent } from './app/features/tests/markdown-playground/markdown-playground.component'; import { RawViewOverlayComponent } from './shared/overlays/raw-view-overlay.component'; import { BookmarksPanelComponent } from './components/bookmarks-panel/bookmarks-panel.component'; import { AddBookmarkModalComponent, type BookmarkFormData, type BookmarkDeleteEvent } from './components/add-bookmark-modal/add-bookmark-modal.component'; @@ -61,6 +62,7 @@ interface TocEntry { SearchPanelComponent, DrawingsEditorComponent, AppShellNimbusLayoutComponent, + MarkdownPlaygroundComponent, ], templateUrl: './app.component.simple.html', styleUrls: ['./app.component.css'], @@ -86,7 +88,7 @@ export class AppComponent implements OnInit, OnDestroy { isSidebarOpen = signal(true); isOutlineOpen = signal(true); outlineTab = signal<'outline' | 'settings'>('outline'); - activeView = signal<'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings'>('files'); + activeView = signal<'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings' | 'markdown-playground'>('files'); currentDrawingPath = signal(null); selectedNoteId = signal(''); sidebarSearchTerm = signal(''); @@ -856,7 +858,7 @@ export class AppComponent implements OnInit, OnDestroy { handle?.addEventListener('lostpointercapture', cleanup); } - setView(view: 'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings'): void { + setView(view: 'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings' | 'markdown-playground'): void { const previousView = this.activeView(); this.activeView.set(view); this.sidebarSearchTerm.set(''); diff --git a/src/app/core/guards/dev-only.guard.ts b/src/app/core/guards/dev-only.guard.ts new file mode 100644 index 0000000..c36d2d6 --- /dev/null +++ b/src/app/core/guards/dev-only.guard.ts @@ -0,0 +1,10 @@ +import { CanMatchFn } from '@angular/router'; +import { environment } from '../../../environments/environment'; + +/** + * Guard qui bloque l'accès aux routes en mode production. + * Utilisé pour protéger les routes de test et de développement. + */ +export const devOnlyGuard: CanMatchFn = () => { + return !environment.production; +}; diff --git a/src/app/features/sidebar/nimbus-sidebar.component.ts b/src/app/features/sidebar/nimbus-sidebar.component.ts index 63ffb32..dd05e03 100644 --- a/src/app/features/sidebar/nimbus-sidebar.component.ts +++ b/src/app/features/sidebar/nimbus-sidebar.component.ts @@ -1,14 +1,16 @@ 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, FileExplorerComponent, QuickLinksComponent, ScrollableOverlayDirective], + imports: [CommonModule, RouterModule, FileExplorerComponent, QuickLinksComponent, ScrollableOverlayDirective], host: { class: 'block h-full' }, template: `
@@ -20,6 +22,22 @@ import type { VaultNode, TagInfo } from '../../../types';
+ +
+ +
+ +
+
+
+
+
+
+
+
+ + + `, + styles: [` + :host { + display: block; + height: 100%; + } + + textarea { + tab-size: 2; + } + + textarea::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + textarea::-webkit-scrollbar-track { + background: transparent; + } + + textarea::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + } + + textarea::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.3); + } + + .dark textarea::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + } + + .dark textarea::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); + } + `] +}) +export class MarkdownPlaygroundComponent { + private markdownService = inject(MarkdownService); + private http = inject(HttpClient); + + sample = signal(''); + + renderedHtml = computed(() => { + const markdown = this.sample(); + try { + return this.markdownService.render(markdown, [], undefined); + } catch (error) { + console.error('Markdown render error:', error); + return `
Erreur de rendu: ${error}
`; + } + }); + + constructor() { + this.loadDefaultSample(); + } + + private loadDefaultSample(): void { + this.http.get(DEFAULT_MD_PATH, { responseType: 'text' }).subscribe({ + next: (text) => this.sample.set(text ?? ''), + error: (err) => { + console.error('Failed to load default markdown:', err); + this.sample.set(''); + } + }); + } + + resetToDefault(): void { + this.loadDefaultSample(); + } +} diff --git a/src/app/features/tests/tests.routes.ts b/src/app/features/tests/tests.routes.ts new file mode 100644 index 0000000..444c4c9 --- /dev/null +++ b/src/app/features/tests/tests.routes.ts @@ -0,0 +1,14 @@ +import { Routes } from '@angular/router'; +import { MarkdownPlaygroundComponent } from './markdown-playground/markdown-playground.component'; + +export const TESTS_ROUTES: Routes = [ + { + path: 'markdown', + component: MarkdownPlaygroundComponent + }, + { + path: '', + pathMatch: 'full', + redirectTo: 'markdown' + } +]; diff --git a/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts b/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts index 6a310cd..9323ff0 100644 --- a/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts +++ b/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts @@ -15,11 +15,12 @@ import { NotesListComponent } from '../../features/list/notes-list.component'; import { NimbusSidebarComponent } from '../../features/sidebar/nimbus-sidebar.component'; import { QuickLinksComponent } from '../../features/quick-links/quick-links.component'; import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrollable-overlay.directive'; +import { MarkdownPlaygroundComponent } from '../../features/tests/markdown-playground/markdown-playground.component'; @Component({ selector: 'app-shell-nimbus-layout', standalone: true, - imports: [CommonModule, FileExplorerComponent, NoteViewerComponent, AppBottomNavigationComponent, AppSidebarDrawerComponent, AppTocOverlayComponent, SwipeNavDirective, NotesListComponent, NimbusSidebarComponent, QuickLinksComponent, ScrollableOverlayDirective], + imports: [CommonModule, FileExplorerComponent, NoteViewerComponent, AppBottomNavigationComponent, AppSidebarDrawerComponent, AppTocOverlayComponent, SwipeNavDirective, NotesListComponent, NimbusSidebarComponent, QuickLinksComponent, ScrollableOverlayDirective, MarkdownPlaygroundComponent], template: `
@@ -57,6 +58,7 @@ import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrol (fileSelected)="noteSelected.emit($event)" (tagSelected)="onTagSelected($event)" (quickLinkSelected)="onQuickLink($event)" + (markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()" /> @@ -117,7 +119,8 @@ import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrol
- + (); @Output() tagClicked = new EventEmitter(); @@ -213,6 +217,7 @@ export class AppShellNimbusLayoutComponent { @Output() navigateHeading = new EventEmitter(); @Output() searchTermChange = new EventEmitter(); @Output() searchOptionsChange = new EventEmitter(); + @Output() markdownPlaygroundSelected = new EventEmitter(); folderFilter: string | null = null; listQuery: string = ''; @@ -303,4 +308,8 @@ export class AppShellNimbusLayoutComponent { this.flyoutCloseTimer = null; } } + + onMarkdownPlaygroundSelected(): void { + this.markdownPlaygroundSelected.emit(); + } } diff --git a/src/assets/samples/markdown-playground.md b/src/assets/samples/markdown-playground.md new file mode 100644 index 0000000..e5ebe73 --- /dev/null +++ b/src/assets/samples/markdown-playground.md @@ -0,0 +1,129 @@ +# Titre H1 +## Titre H2 +### Titre H3 + +**Gras**, *Italique*, ~~Barré~~, `inline code`, [lien externe](https://example.com) + +> Blockquote / Citation +> Peut s'étendre sur plusieurs lignes + +> [!NOTE] +> Callout de type NOTE + +> [!WARNING] +> Callout de type WARNING + +> [!TIP] +> Callout de type TIP + +## Listes + +- Liste non ordonnée +- Deuxième élément + - Sous-élément +- Troisième élément + +1. Liste ordonnée +2. Deuxième élément +3. Troisième élément + +## Tâches + +- [ ] Tâche non cochée +- [x] Tâche cochée +- [ ] Autre tâche en attente + +## Code + +```typescript +function hello(name: string): string { + return `Hello ${name}!`; +} + +const result = hello("World"); +console.log(result); +``` + +```javascript +const data = [1, 2, 3, 4, 5]; +const doubled = data.map(x => x * 2); +console.log(doubled); +``` + +```python +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +print(fibonacci(10)) +``` + +## Mermaid + +```mermaid +graph TD + A[Start] --> B{Decision} + B -->|Yes| C[Action 1] + B -->|No| D[Action 2] + C --> E[End] + D --> E +``` + +```mermaid +sequenceDiagram + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +## Math (LaTeX) + +Inline math: $E = mc^2$ + +Block math: + +$$ +\frac{-b \pm \sqrt{b^2 - 4ac}}{2a} +$$ + +$$ +\int_{0}^{\infty} e^{-x^2} dx = \frac{\sqrt{\pi}}{2} +$$ + +## Tableaux + +| Colonne 1 | Colonne 2 | Colonne 3 | +|-----------|-----------|-----------| +| A | B | C | +| D | E | F | +| G | H | I | + +## Images + +![Image externe](https://picsum.photos/400/200) + +## Tags + +Les tags inline fonctionnent: #test #markdown #playground + +## Liens internes (WikiLinks) + +[[Note Example]] - Lien vers une note +[[Note Example#Section]] - Lien vers une section +[[Note Example|Alias personnalisé]] - Lien avec alias + +## Footnotes + +Voici un texte avec une note de bas de page[^1]. + +Et une autre référence[^2]. + +[^1]: Ceci est la première note de bas de page. +[^2]: Ceci est la deuxième note de bas de page avec plus de détails. + +--- + +## Séparateur horizontal + +Le séparateur ci-dessus est créé avec `---` diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index f2a944c..0ff2d68 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -7,4 +7,7 @@ export const environment = { production: true, serviceURL: "/AuMenuManager", + features: { + showTestSection: false + } }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index ab34137..dafe0b7 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -8,4 +8,7 @@ export const environment = { production: false, serviceURL: "http://localhost:8080/AuMenuManager", // serviceURL: "https://public-tomcat.guru.lan/AuMenuManager", + features: { + showTestSection: true + } }; \ No newline at end of file diff --git a/src/services/markdown.service.ts b/src/services/markdown.service.ts index 7ed7303..5c0bd5c 100644 --- a/src/services/markdown.service.ts +++ b/src/services/markdown.service.ts @@ -379,6 +379,25 @@ export class MarkdownService { return placeholder; }); + const codeBlockPlaceholders: { placeholder: string; content: string }[] = []; + const inlineCodePlaceholders: { placeholder: string; content: string }[] = []; + + const stashSegments = ( + source: string, + regex: RegExp, + collection: { placeholder: string; content: string }[], + marker: string + ): string => { + return source.replace(regex, (match) => { + const placeholder = `@@__${marker}_${collection.length}__@@`; + collection.push({ placeholder, content: match }); + return placeholder; + }); + }; + + text = stashSegments(text, /```[\s\S]*?```/g, codeBlockPlaceholders, 'CODE_BLOCK'); + text = stashSegments(text, /`[^`]*`/g, inlineCodePlaceholders, 'CODE_INLINE'); + const addMathPlaceholder = (expression: string, display: 'block' | 'inline') => { const placeholder = `@@MATH::${display.toUpperCase()}::${math.length}@@`; math.push({ placeholder, expression: expression.trim(), display }); @@ -395,6 +414,14 @@ export class MarkdownService { text = text.replace(/(? addMathPlaceholder(expr, 'inline')); text = text.replace(/\\\((.+?)\\\)/g, (_match, expr) => addMathPlaceholder(expr, 'inline')); + for (const { placeholder, content } of inlineCodePlaceholders) { + text = text.split(placeholder).join(content); + } + + for (const { placeholder, content } of codeBlockPlaceholders) { + text = text.split(placeholder).join(content); + } + return { markdown: text, wikiLinks, math }; }