feat: enhance note list views with rich metadata display
- Added timestamp, author, and description fields to both compact and detailed view modes - Implemented thumbnail support in detailed view with automatic image extraction from note content - Improved auto-selection behavior when filters change using reactive signals for more reliable note navigation
This commit is contained in:
parent
7331077ffa
commit
7be20d05e0
@ -192,21 +192,26 @@ import { AIToolsService } from '../../services/ai-tools.service';
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="title text-sm truncate">{{ n.title }}</div>
|
||||
<div class="meta text-xs truncate">{{ n.filePath }}</div>
|
||||
<div *ngIf="getUpdatedTimestamp(n) as ts" class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">🕓 {{ formatDateTime(ts) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed View -->
|
||||
<div *ngIf="state.viewMode() === 'detailed'" class="note-inner flex items-start gap-2 space-y-0">
|
||||
<div *ngIf="state.viewMode() === 'detailed'" class="note-inner flex flex-col sm:flex-row items-start gap-4">
|
||||
<div class="flex items-start gap-2 min-w-0 flex-1">
|
||||
<span class="note-color-dot flex-shrink-0 mt-1" [style.backgroundColor]="getNoteColor(n)" aria-hidden="true"></span>
|
||||
<span class="flex-shrink-0 mt-0.5" title="Type">{{ typeIcon(n) }}</span>
|
||||
<div class="min-w-0 flex-1 space-y-1.5">
|
||||
<div class="title text-sm truncate">{{ n.title }}</div>
|
||||
<div class="meta text-xs truncate">{{ n.filePath }}</div>
|
||||
<div class="excerpt text-xs">
|
||||
<span *ngIf="n.frontmatter?.status">Status: {{ n.frontmatter.status }}</span>
|
||||
<span *ngIf="n.mtime" class="ml-2">{{ formatDate(n.mtime) }}</span>
|
||||
<div *ngIf="getAuthor(n) as a" class="text-xs text-gray-500 dark:text-gray-400 truncate">✍️ {{ a }}</div>
|
||||
<div *ngIf="getDescription(n) as d" class="text-xs text-muted truncate">{{ d }}</div>
|
||||
<div *ngIf="getUpdatedTimestamp(n) as ts" class="text-xs text-gray-500 dark:text-gray-400">🕓 {{ formatDateTime(ts) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0 sm:ml-4 mt-2 sm:mt-0" *ngIf="getThumbnailSrc(n) as imgSrc">
|
||||
<img [src]="imgSrc" alt="" class="rounded-md shadow-sm object-cover w-24 h-24" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@ -1049,6 +1054,75 @@ export class NotesListComponent {
|
||||
}
|
||||
}
|
||||
|
||||
getUpdatedTimestamp(n: Note): number | null {
|
||||
try {
|
||||
const m = Number(n.mtime || 0);
|
||||
if (m && !Number.isNaN(m)) return m;
|
||||
} catch {}
|
||||
const parse = (s?: string) => {
|
||||
if (!s) return 0;
|
||||
const t = Date.parse(s);
|
||||
return Number.isFinite(t) ? t : 0;
|
||||
};
|
||||
const u = parse(n.updatedAt);
|
||||
if (u) return u;
|
||||
const c = parse(n.createdAt);
|
||||
return c || null;
|
||||
}
|
||||
|
||||
formatDateTime(ts: number): string {
|
||||
try {
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleString('fr-FR', { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
||||
} catch { return ''; }
|
||||
}
|
||||
|
||||
getAuthor(n: Note): string | null {
|
||||
const fm = (n.frontmatter || {}) as any;
|
||||
const v = (fm.author ?? fm.auteur ?? n.author) as any;
|
||||
return (typeof v === 'string' && v.trim().length > 0) ? v : null;
|
||||
}
|
||||
|
||||
getDescription(n: Note): string | null {
|
||||
const fm = (n.frontmatter || {}) as any;
|
||||
const v = (fm.description ?? fm.desc) as any;
|
||||
return (typeof v === 'string' && v.trim().length > 0) ? v : null;
|
||||
}
|
||||
|
||||
private extractFirstImageFromContent(n: Note): string | null {
|
||||
const content = (n.rawContent ?? n.content ?? '').toString();
|
||||
if (!content) return null;
|
||||
try {
|
||||
const embed = content.match(/!\[\[(.*?)\]\]/);
|
||||
if (embed && embed[1]) {
|
||||
const name = embed[1].trim();
|
||||
if (name) {
|
||||
const notePath = (n.filePath || n.originalPath || '').replace(/\\/g, '/');
|
||||
return `/api/attachments/resolve?name=${encodeURIComponent(name)}¬e=${encodeURIComponent(notePath)}`;
|
||||
}
|
||||
}
|
||||
const md = content.match(/!\[[^\]]*\]\(([^\)\s]+)(?:\s+\"[^\"]*\")?\)/);
|
||||
if (md && md[1]) {
|
||||
const p = md[1].trim();
|
||||
if (/^(data:|https?:)/i.test(p)) return p;
|
||||
if (p.startsWith('/')) return `/vault/${encodeURI(p.replace(/^\/+/, ''))}`;
|
||||
const notePath = (n.filePath || n.originalPath || '').replace(/\\/g, '/');
|
||||
return `/api/attachments/resolve?name=${encodeURIComponent(p)}¬e=${encodeURIComponent(notePath)}`;
|
||||
}
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
getThumbnailSrc(n: Note): string | null {
|
||||
try {
|
||||
const kind = this.fileTypes.getViewerType(n.filePath, n.rawContent ?? n.content);
|
||||
if (kind === 'image') {
|
||||
return `/vault/${encodeURI(n.filePath)}`;
|
||||
}
|
||||
} catch {}
|
||||
return this.extractFirstImageFromContent(n);
|
||||
}
|
||||
|
||||
// ============ Multiple Selection Methods ============
|
||||
|
||||
/**
|
||||
|
||||
@ -138,16 +138,25 @@ import { takeUntil } from 'rxjs/operators';
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="title text-sm truncate">{{ note.title }}</div>
|
||||
<div class="meta text-xs truncate">{{ note.filePath }}</div>
|
||||
<div *ngIf="getUpdatedTimestampById(note) as ts" class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">🕓 {{ formatDateTime(ts) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed View -->
|
||||
<div *ngIf="state.viewMode() === 'detailed'" class="note-inner flex items-start gap-2 space-y-0">
|
||||
<div *ngIf="state.viewMode() === 'detailed'" class="note-inner flex flex-col sm:flex-row items-start gap-4">
|
||||
<div class="flex items-start gap-2 min-w-0 flex-1">
|
||||
<span class="note-color-dot flex-shrink-0 mt-1" [style.backgroundColor]="getNoteColorById(note.id)" aria-hidden="true"></span>
|
||||
<span class="flex-shrink-0 mt-0.5" title="Type">{{ typeIcon(note.filePath) }}</span>
|
||||
<div class="min-w-0 flex-1 space-y-1.5">
|
||||
<div class="title text-sm truncate">{{ note.title }}</div>
|
||||
<div class="meta text-xs truncate">{{ note.filePath }}</div>
|
||||
<div *ngIf="getAuthorById(note.id) as a" class="text-xs text-gray-500 dark:text-gray-400 truncate">✍️ {{ a }}</div>
|
||||
<div *ngIf="getDescriptionById(note.id) as d" class="text-xs text-muted truncate">{{ d }}</div>
|
||||
<div *ngIf="getUpdatedTimestampById(note) as ts" class="text-xs text-gray-500 dark:text-gray-400">🕓 {{ formatDateTime(ts) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0 sm:ml-4 mt-2 sm:mt-0" *ngIf="getThumbnailSrcById(note) as imgSrc">
|
||||
<img [src]="imgSrc" alt="" class="rounded-md shadow-sm object-cover w-24 h-24" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@ -1030,6 +1039,82 @@ export class PaginatedNotesListComponent implements OnInit, OnDestroy {
|
||||
} catch { return '📎'; }
|
||||
}
|
||||
|
||||
// Metadata helpers used by Comfortable/Detailed modes
|
||||
getUpdatedTimestampById(meta: NoteMetadata): number | null {
|
||||
try {
|
||||
const full = this.getFullNoteById(meta.id) as any;
|
||||
const m = Number(full?.mtime || 0);
|
||||
if (m && !Number.isNaN(m)) return m;
|
||||
} catch {}
|
||||
const parse = (s?: string) => {
|
||||
if (!s) return 0;
|
||||
const t = Date.parse(s);
|
||||
return Number.isFinite(t) ? t : 0;
|
||||
};
|
||||
const u = parse(meta.updatedAt);
|
||||
if (u) return u;
|
||||
const c = parse(meta.createdAt);
|
||||
return c || null;
|
||||
}
|
||||
|
||||
formatDateTime(ts: number): string {
|
||||
try {
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleString('fr-FR', { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
||||
} catch { return ''; }
|
||||
}
|
||||
|
||||
getAuthorById(id: string): string | null {
|
||||
const full = this.getFullNoteById(id) as any;
|
||||
const fm = (full?.frontmatter || {}) as any;
|
||||
const v = (fm.author ?? fm.auteur ?? full?.author) as any;
|
||||
return (typeof v === 'string' && v.trim().length > 0) ? v : null;
|
||||
}
|
||||
|
||||
getDescriptionById(id: string): string | null {
|
||||
const full = this.getFullNoteById(id) as any;
|
||||
const fm = (full?.frontmatter || {}) as any;
|
||||
const v = (fm.description ?? fm.desc) as any;
|
||||
return (typeof v === 'string' && v.trim().length > 0) ? v : null;
|
||||
}
|
||||
|
||||
private extractFirstImageFromFull(full: any): string | null {
|
||||
if (!full) return null;
|
||||
const content = String(full.rawContent ?? full.content ?? '');
|
||||
if (!content) return null;
|
||||
try {
|
||||
const embed = content.match(/!\[\[(.*?)\]\]/);
|
||||
if (embed && embed[1]) {
|
||||
const name = embed[1].trim();
|
||||
if (name) {
|
||||
const notePath = String(full.filePath || full.originalPath || '').replace(/\\/g, '/');
|
||||
return `/api/attachments/resolve?name=${encodeURIComponent(name)}¬e=${encodeURIComponent(notePath)}`;
|
||||
}
|
||||
}
|
||||
const md = content.match(/!\[[^\]]*\]\(([^\)\s]+)(?:\s+\"[^\"]*\")?\)/);
|
||||
if (md && md[1]) {
|
||||
const p = md[1].trim();
|
||||
if (/^(data:|https?:)/i.test(p)) return p;
|
||||
if (p.startsWith('/')) return `/vault/${encodeURI(p.replace(/^\/+/, ''))}`;
|
||||
const notePath = String(full.filePath || full.originalPath || '').replace(/\\/g, '/');
|
||||
return `/api/attachments/resolve?name=${encodeURIComponent(p)}¬e=${encodeURIComponent(notePath)}`;
|
||||
}
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
getThumbnailSrcById(meta: NoteMetadata): string | null {
|
||||
try {
|
||||
const full = this.getFullNoteById(meta.id) as any;
|
||||
const path = full?.filePath || meta.filePath || '';
|
||||
const kind = this.fileTypes.getViewerType(path, full?.rawContent ?? full?.content ?? '');
|
||||
if (kind === 'image') {
|
||||
return `/vault/${encodeURI(path)}`;
|
||||
}
|
||||
return this.extractFirstImageFromFull(full);
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
// Sort/View menus
|
||||
toggleSortMenu(): void { this.sortMenuOpen.set(!this.sortMenuOpen()); this.viewModeMenuOpen.set(false); }
|
||||
toggleViewModeMenu(): void { this.viewModeMenuOpen.set(!this.viewModeMenuOpen()); this.sortMenuOpen.set(false); }
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, HostListener, Input, Output, inject, effect, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Component, EventEmitter, HostListener, Input, Output, inject, effect, signal, computed, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiModeService } from '../../shared/services/ui-mode.service';
|
||||
import { ResponsiveService } from '../../shared/services/responsive.service';
|
||||
@ -431,6 +431,22 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
quickLinkFilter: 'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | 'bookmarks' | null = null;
|
||||
private suppressNextNoteSelection = false;
|
||||
|
||||
// Signaux pour tracker les filtres et déclencher la sélection automatique
|
||||
private filterStateSignal = signal<{
|
||||
tag: string | null;
|
||||
folder: string | null;
|
||||
quick: string | null;
|
||||
search: string;
|
||||
}>({
|
||||
tag: null,
|
||||
folder: null,
|
||||
quick: null,
|
||||
search: ''
|
||||
});
|
||||
|
||||
// Signal pour forcer un recalcul de la liste filtrée
|
||||
private filterChangeCounter = signal(0);
|
||||
|
||||
// --- URL State <-> Layout sync ---
|
||||
private mapUrlQuickToInternal(q: string | null): AppShellNimbusLayoutComponent['quickLinkFilter'] {
|
||||
switch ((q || '').toLowerCase()) {
|
||||
@ -525,7 +541,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.tagFilter = norm || null;
|
||||
this.folderFilter = null;
|
||||
this.quickLinkFilter = null;
|
||||
if (!hasNote) this.autoSelectFirstNote();
|
||||
if (!hasNote) this.notifyFilterChange();
|
||||
if (!this.responsive.isDesktop()) this.mobileNav.setActiveTab('list');
|
||||
}
|
||||
// Auto-open tags flyout when tag filter is active
|
||||
@ -538,7 +554,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.folderFilter = folder || null;
|
||||
this.tagFilter = null;
|
||||
this.quickLinkFilter = null;
|
||||
if (!hasNote) this.autoSelectFirstNote();
|
||||
if (!hasNote) this.notifyFilterChange();
|
||||
if (!this.responsive.isDesktop()) this.mobileNav.setActiveTab('list');
|
||||
}
|
||||
// Auto-open folders flyout when folder filter is active
|
||||
@ -552,12 +568,12 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.quickLinkFilter = internal;
|
||||
this.folderFilter = null;
|
||||
this.tagFilter = null;
|
||||
this.autoSelectFirstNote();
|
||||
this.notifyFilterChange();
|
||||
if (!this.responsive.isDesktop()) {
|
||||
this.mobileNav.setActiveTab('list');
|
||||
}
|
||||
} else {
|
||||
this.autoSelectFirstNote();
|
||||
this.notifyFilterChange();
|
||||
}
|
||||
// Auto-open quick flyout when quick filter is active
|
||||
if (this.hoveredFlyout !== 'quick') {
|
||||
@ -571,7 +587,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.folderFilter = null;
|
||||
this.tagFilter = null;
|
||||
this.quickLinkFilter = null;
|
||||
this.autoSelectFirstNote();
|
||||
this.notifyFilterChange();
|
||||
}
|
||||
this.suppressNextNoteSelection = false;
|
||||
// Close any open flyout when no filters
|
||||
@ -589,11 +605,58 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
});
|
||||
});
|
||||
|
||||
// Effect pour sélection automatique quand les filtres changent
|
||||
private _autoSelectEffect = effect(() => {
|
||||
// Écouter le signal de changement de filtre
|
||||
const filterState = this.filterStateSignal();
|
||||
const counter = this.filterChangeCounter();
|
||||
|
||||
// Éviter l'exécution lors de l'initialisation
|
||||
if (counter === 0) return;
|
||||
|
||||
console.log('🎯 Auto-select effect triggered:', { filterState, counter });
|
||||
|
||||
// Utiliser queueMicrotask pour s'assurer que la liste est mise à jour
|
||||
queueMicrotask(() => {
|
||||
this.autoSelectFirstNote(true);
|
||||
});
|
||||
});
|
||||
|
||||
// Méthode helper pour notifier les changements de filtres
|
||||
private notifyFilterChange() {
|
||||
const currentState = {
|
||||
tag: this.tagFilter,
|
||||
folder: this.folderFilter,
|
||||
quick: this.quickLinkFilter,
|
||||
search: this.listQuery
|
||||
};
|
||||
|
||||
console.log('🔔 Notifying filter change:', currentState);
|
||||
|
||||
// Mettre à jour le signal d'état
|
||||
this.filterStateSignal.set(currentState);
|
||||
|
||||
// Incrémenter le compteur pour déclencher l'effect
|
||||
this.filterChangeCounter.update(c => c + 1);
|
||||
}
|
||||
|
||||
// Auto-select first note when filters change
|
||||
private autoSelectFirstNote() {
|
||||
private autoSelectFirstNote(forceSelection = false) {
|
||||
const filteredNotes = this.getFilteredNotes();
|
||||
if (filteredNotes.length > 0 && filteredNotes[0].id !== this.selectedNoteId) {
|
||||
this.noteSelected.emit(filteredNotes[0].id);
|
||||
console.log('🎯 autoSelectFirstNote called:', {
|
||||
forceSelection,
|
||||
notesCount: filteredNotes.length,
|
||||
firstNoteId: filteredNotes[0]?.id,
|
||||
selectedNoteId: this.selectedNoteId
|
||||
});
|
||||
|
||||
if (filteredNotes.length > 0) {
|
||||
const firstNote = filteredNotes[0];
|
||||
// Forcer la sélection si demandé, ou si la note est différente
|
||||
if (forceSelection || firstNote.id !== this.selectedNoteId) {
|
||||
console.log('✅ Emitting noteSelected for:', firstNote.title, firstNote.id);
|
||||
this.noteSelected.emit(firstNote.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -685,8 +748,8 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
|
||||
onQueryChange(query: string) {
|
||||
this.listQuery = query;
|
||||
// Auto-select first note on any list change including search updates
|
||||
this.autoSelectFirstNote();
|
||||
// Notifier le changement pour déclencher la sélection automatique
|
||||
this.notifyFilterChange();
|
||||
// Sync URL search term
|
||||
this.urlState.updateSearch(query);
|
||||
}
|
||||
@ -747,6 +810,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
|
||||
onClearFolderFromList() {
|
||||
this.folderFilter = null;
|
||||
this.notifyFilterChange();
|
||||
this.urlState.clearFolderFilter();
|
||||
}
|
||||
|
||||
@ -807,7 +871,8 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.listQuery = '';
|
||||
try { this.filters.clearKinds(); } catch {}
|
||||
try { this.filters.clearTags(); } catch {}
|
||||
this.autoSelectFirstNote();
|
||||
// Notifier le changement pour déclencher la sélection automatique
|
||||
this.notifyFilterChange();
|
||||
if (this.responsive.isMobile() || this.responsive.isTablet()) {
|
||||
this.mobileNav.setActiveTab('list');
|
||||
}
|
||||
@ -829,7 +894,8 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.listQuery = '';
|
||||
try { this.filters.clearKinds(); } catch {}
|
||||
try { this.filters.clearTags(); } catch {}
|
||||
this.autoSelectFirstNote();
|
||||
// Notifier le changement pour déclencher la sélection automatique
|
||||
this.notifyFilterChange();
|
||||
this.mobileNav.setActiveTab('list');
|
||||
this.mobileNav.sidebarOpen.set(false);
|
||||
if (path) {
|
||||
@ -987,8 +1053,8 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.urlState.setQuickWithMarkdown(label);
|
||||
}
|
||||
}
|
||||
// Auto-select first note after filter changes
|
||||
this.autoSelectFirstNote();
|
||||
// Notifier le changement pour déclencher la sélection automatique
|
||||
this.notifyFilterChange();
|
||||
this.suppressNextNoteSelection = false;
|
||||
}
|
||||
|
||||
@ -1000,8 +1066,8 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
// Clear other filters and search to focus on tag results
|
||||
this.quickLinkFilter = null;
|
||||
this.listQuery = '';
|
||||
// Auto-select first note after filter changes
|
||||
this.autoSelectFirstNote();
|
||||
// Notifier le changement pour déclencher la sélection automatique
|
||||
this.notifyFilterChange();
|
||||
// Ensure the list is visible: exit fullscreen if active
|
||||
if (this.noteFullScreen) {
|
||||
this.noteFullScreen = false;
|
||||
@ -1104,6 +1170,6 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
this.tagFilter = null;
|
||||
this.quickLinkFilter = null;
|
||||
this.listQuery = '';
|
||||
this.autoSelectFirstNote();
|
||||
this.notifyFilterChange();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
---
|
||||
titre: test
|
||||
auteur: Bruno Charest
|
||||
creation_date: 2025-09-25T07:45:20-04:00
|
||||
modification_date: 2025-11-02T12:07:38-04:00
|
||||
catégorie: ""
|
||||
tags: []
|
||||
aliases:
|
||||
- ""
|
||||
status: en-cours
|
||||
titre: "test"
|
||||
auteur: "Bruno Charest"
|
||||
creation_date: "2025-09-25T07:45:20-04:00"
|
||||
modification_date: "2025-11-02T12:07:38-04:00"
|
||||
tags: [""]
|
||||
status: "en-cours"
|
||||
publish: true
|
||||
favoris: false
|
||||
template: true
|
||||
@ -15,14 +12,14 @@ task: true
|
||||
archive: true
|
||||
draft: true
|
||||
private: true
|
||||
first_name: Bruno
|
||||
birth_date: 2025-06-18
|
||||
email: bruno.charest@gmail.com
|
||||
first_name: "Bruno"
|
||||
birth_date: "2025-06-18"
|
||||
email: "bruno.charest@gmail.com"
|
||||
number: "12345"
|
||||
todo: false
|
||||
url: https://google.com
|
||||
image: https://images.unsplash.com/photo-1675789652575-0a5d2425b6c2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80
|
||||
color: "#64748B"
|
||||
url: "https://google.com"
|
||||
image: "https://images.unsplash.com/photo-1675789652575-0a5d2425b6c2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80"
|
||||
color: "#22C55E"
|
||||
---
|
||||
# Test 1 Markdown
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ publish: false
|
||||
favoris: true
|
||||
template: false
|
||||
task: false
|
||||
archive: false
|
||||
archive: true
|
||||
draft: false
|
||||
private: false
|
||||
description: "Stargate Atlantis: une expédition militaire et scientifique découvre la cité mythique d'Atlantis dans la galaxie de Pégase et affronte les Wraiths."
|
||||
|
||||
@ -12,24 +12,9 @@ archive: false
|
||||
draft: true
|
||||
private: false
|
||||
titre: ""
|
||||
Les Compléments Alimentaires: "Un Guide Général"
|
||||
catégorie: ""
|
||||
readOnly: false
|
||||
description: "Les Compléments Alimentaires : Un Guide Général Dans notre quête constante de bien-être et de..."
|
||||
tags:
|
||||
- supplments
|
||||
- tag2
|
||||
- configuration
|
||||
- accueil
|
||||
- home
|
||||
- markdown
|
||||
- bruno
|
||||
- tag4
|
||||
- tag3
|
||||
- test
|
||||
- tag1
|
||||
- test2
|
||||
- tagtag
|
||||
color: "#00AEEF"
|
||||
---
|
||||
## Les Compléments Alimentaires : Un Guide Général
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user