diff --git a/documentation/AE_UI_Journals_module_update_2026.md b/documentation/AE_UI_Journals_module_update_2026.md index 8b2f4f5b..475d266d 100644 --- a/documentation/AE_UI_Journals_module_update_2026.md +++ b/documentation/AE_UI_Journals_module_update_2026.md @@ -1,7 +1,7 @@ # Aether Journals UI Update (2026) > **Status:** Active -> **Last Updated:** 2026-01-13 +> **Last Updated:** 2026-01-14 > **Primary Agent:** Frontend SvelteKit Agent ## 1. Project Overview @@ -33,6 +33,7 @@ This document outlines the modernization of the Journals module UI in the Svelte * **State Management:** `src/lib/ae_journals/ae_journals_stores.ts` * **Local Storage:** Dexie.js (`db_journals`) * **API Client:** `src/lib/api/api.ts` -> `get_ae_obj_v3` +* **Export Engine:** Centralized templates in `src/lib/ae_journals/ae_journals_export_templates.ts`. --- @@ -61,8 +62,8 @@ This document outlines the modernization of the Journals module UI in the Svelte ### 🔄 Interop (Markdown/HTML) * **Goal:** Bulk export/import for data portability. -* **Format:** JSON container vs. Raw Markdown files. -* **Integration:** potential drag-and-drop zone for Nextcloud Note files. +* **Format:** Optimized templates for different journal types (Standard, Personal Log, Amazon Vine). +* **Integration:** drag-and-drop zone for Nextcloud Note files and bulk parser logic in `ae_journals_parsers.ts`. --- @@ -82,11 +83,12 @@ This document outlines the modernization of the Journals module UI in the Svelte - [x] Implement Append/Prepend logic in `ae_comp__journal_entry_obj_id_view.svelte`. - [x] Implement Bulk Export (Markdown/HTML/JSON) via `ae_comp__modal_journal_export.svelte`. - [x] Implement Bulk Import with Drag-and-Drop via `ae_comp__modal_journal_import.svelte`. +- [x] Establish centralized Export Template system (`ae_journals_export_templates.ts`). ### Phase 4: Polish & Security (Active) - [x] Implement Auto-Save toggle and visual status indicators. - [ ] Audit encryption flow for Quick Added and Imported entries. -- [ ] Styling and Mobile responsiveness check. +- [x] Styling and Mobile responsiveness check for Import/Export modals. - [ ] Integrate Outbound Email sharing. --- diff --git a/src/lib/ae_journals/ae_journals_export_templates.ts b/src/lib/ae_journals/ae_journals_export_templates.ts new file mode 100644 index 00000000..db15d29e --- /dev/null +++ b/src/lib/ae_journals/ae_journals_export_templates.ts @@ -0,0 +1,149 @@ +/** + * @file ae_journals_export_templates.ts + * @description Templates for formatting journal entries during export. + * @author One Sky IT + */ + +import { ae_util } from '$lib/ae_utils/ae_utils'; +import type { ae_JournalEntry } from '$lib/types/ae_types'; + +export interface ExportTemplate { + id: string; + name: string; + description: string; + extension: string; + formatter: (entries: ae_JournalEntry[]) => string; +} + +/** + * Standard Markdown Template + */ +export const template_standard_markdown: ExportTemplate = { + id: 'standard_markdown', + name: 'Standard Markdown', + description: 'Basic Markdown with title, date, and content.', + extension: 'md', + formatter: (entries) => { + return entries.map(entry => { + const dateStr = ae_util.iso_datetime_formatter(entry.created_on, 'datetime_12_long'); + const title = entry.name || dateStr; + const header = `# ${title}\n*${dateStr}*\n\n`; + return `${header}${entry.content || ''}\n\n---\n`; + }).join('\n'); + } +}; + +/** + * Personal Log Template + * Formats entries as a continuous daily log. + */ +export const template_personal_log: ExportTemplate = { + id: 'personal_log', + name: 'Personal Log', + description: 'Formatted as a daily log with ## Date headers.', + extension: 'md', + formatter: (entries) => { + // Sort entries by date ascending for a chronological log + const sorted = [...entries].sort((a, b) => + (a.created_on || '').localeCompare(b.created_on || '') + ); + + return sorted.map(entry => { + const dateStr = ae_util.iso_datetime_formatter(entry.created_on, 'date_iso'); + const title = entry.name || ''; + const header = `## ${dateStr}${title ? ' - ' + title : ''}\n\n`; + return `${header}${entry.content || ''}\n\n`; + }).join('\n'); + } +}; + +/** + * Amazon Vine Template + * Specific formatting for product reviews. + */ +export const template_amazon_vine: ExportTemplate = { + id: 'amazon_vine', + name: 'Amazon Vine', + description: 'Optimized for product reviews.', + extension: 'md', + formatter: (entries) => { + return entries.map(entry => { + const dateStr = ae_util.iso_datetime_formatter(entry.created_on, 'date_iso'); + // Try to find a product name in the title or content + const productName = entry.name || 'Unknown Product'; + + // Look for product link in content_json or tags if available, + // otherwise just output content + let output = `## ${productName}\n`; + output += `*Date: ${dateStr}*\n\n`; + output += `${entry.content || ''}\n\n`; + output += `---`; + return output; + }).join('\n\n'); + } +}; + +/** + * Standard HTML Template + */ +export const template_standard_html: ExportTemplate = { + id: 'standard_html', + name: 'Standard HTML', + description: 'Semantic HTML5 articles.', + extension: 'html', + formatter: (entries) => { + const body = entries.map(entry => { + const dateStr = ae_util.iso_datetime_formatter(entry.created_on, 'datetime_12_long'); + const title = entry.name || dateStr; + return ` +
+
+

${title}

+ +
+
+ ${entry.content_md_html || ''} +
+
+
`; + }).join('\n'); + + return ` + + + + Journal Export + + + + ${body} + +`; + } +}; + +/** + * Standard JSON Template + */ +export const template_standard_json: ExportTemplate = { + id: 'standard_json', + name: 'Raw JSON', + description: 'Full database dump of selected entries.', + extension: 'json', + formatter: (entries) => JSON.stringify(entries, null, 2) +}; + +export const EXPORT_TEMPLATES = { + standard_markdown: template_standard_markdown, + personal_log: template_personal_log, + amazon_vine: template_amazon_vine, + standard_html: template_standard_html, + standard_json: template_standard_json +}; diff --git a/src/routes/journals/[journal_id]/+page.svelte b/src/routes/journals/[journal_id]/+page.svelte index 63ca087b..24d23f04 100644 --- a/src/routes/journals/[journal_id]/+page.svelte +++ b/src/routes/journals/[journal_id]/+page.svelte @@ -67,6 +67,7 @@ // import Journal_entry_obj_qry from './../ae_comp__journal_entry_obj_qry.svelte'; import Journal_obj_id_edit from '../ae_comp__journal_obj_id_edit.svelte'; import AeCompModalJournalExport from '../ae_comp__modal_journal_export.svelte'; + import AeCompModalJournalImport from '../ae_comp__modal_journal_import.svelte'; import { FileDown } from '@lucide/svelte'; // let ae_promises: key_val = {}; @@ -77,6 +78,12 @@ // *** Quickly pull out data from parent(s) let ae_acct = data[$slct.account_id]; let show_export_modal = $state(false); + let show_import_modal = $state(false); + + function handle_import_complete() { + // Trigger a refresh of the journal entry list + $journals_trig.journal_entry_li = true; + } $effect(() => { if (log_lvl > 1) { @@ -329,7 +336,12 @@ " > --> - show_export_modal = true} /> + show_export_modal = true} + onShowImport={() => show_import_modal = true} + /> {#if $lq__journal_entry_obj_li && $lq__journal_entry_obj_li?.length} @@ -353,8 +365,16 @@ show_export_modal = false} /> + + + show_import_modal = false} + onImportComplete={handle_import_complete} + /> {:else}

You must be logged in as the owner to view this Journal.

diff --git a/src/routes/journals/[journal_id]/entry/[journal_entry_id]/+page.svelte b/src/routes/journals/[journal_id]/entry/[journal_entry_id]/+page.svelte index 46a547a1..af1d1b27 100644 --- a/src/routes/journals/[journal_id]/entry/[journal_entry_id]/+page.svelte +++ b/src/routes/journals/[journal_id]/entry/[journal_entry_id]/+page.svelte @@ -325,6 +325,7 @@ show_export_modal = false} /> {:else} diff --git a/src/routes/journals/ae_comp__journal_obj_id_view.svelte b/src/routes/journals/ae_comp__journal_obj_id_view.svelte index e62cd269..2926c995 100644 --- a/src/routes/journals/ae_comp__journal_obj_id_view.svelte +++ b/src/routes/journals/ae_comp__journal_obj_id_view.svelte @@ -3,7 +3,7 @@ import { goto } from '$app/navigation'; // *** Import other supporting libraries - import { BookPlus, BookOpenText, FilePlus, Menu, Pencil, FileDown } from '@lucide/svelte'; + import { BookPlus, BookOpenText, FilePlus, Menu, Pencil, FileDown, FileUp } from '@lucide/svelte'; // *** Import Aether specific variables and functions import { ae_util } from '$lib/ae_utils/ae_utils'; @@ -30,9 +30,10 @@ lq__journal_obj: any; lq__journal_entry_obj_li: any; onShowExport?: () => void; + onShowImport?: () => void; } - let { log_lvl = 0, lq__journal_obj, lq__journal_entry_obj_li, onShowExport }: Props = $props(); + let { log_lvl = 0, lq__journal_obj, lq__journal_entry_obj_li, onShowExport, onShowImport }: Props = $props(); // let ae_promises: key_val = {}; // let ae_tmp: key_val = {}; @@ -286,6 +287,24 @@ {/if} + + diff --git a/src/routes/journals/ae_comp__modal_journal_export.svelte b/src/routes/journals/ae_comp__modal_journal_export.svelte index b8110a43..9ae7323f 100644 --- a/src/routes/journals/ae_comp__modal_journal_export.svelte +++ b/src/routes/journals/ae_comp__modal_journal_export.svelte @@ -2,72 +2,54 @@ /** * @file ae_comp__modal_journal_export.svelte * @description Modal component for bulk exporting journal entries. - * Allows exporting the currently filtered list of entries to Markdown, HTML, or JSON. - * Supports both downloading as a file and copying to clipboard. + * Allows exporting entries using various templates (Markdown, HTML, JSON). * @author One Sky IT - * @version 1.0.0 */ import { Modal } from 'flowbite-svelte'; - import { Download, Copy, FileJson, FileType, Code } from '@lucide/svelte'; + import { Download, Copy, FileJson, FileType, Code, Settings2 } from '@lucide/svelte'; import { ae_util } from '$lib/ae_utils/ae_utils'; - import type { ae_JournalEntry } from '$lib/types/ae_types'; + import type { ae_JournalEntry, ae_Journal } from '$lib/types/ae_types'; + import { EXPORT_TEMPLATES, type ExportTemplate } from '$lib/ae_journals/ae_journals_export_templates'; interface Props { open: boolean; entries: ae_JournalEntry[]; + journal?: ae_Journal; onClose: () => void; } - let { open = $bindable(false), entries = [], onClose }: Props = $props(); + let { open = $bindable(false), entries = [], journal, onClose }: Props = $props(); // State - let export_format: 'markdown' | 'html' | 'json' = $state('markdown'); + let selected_template_id: string = $state('standard_markdown'); let export_preview: string = $state(''); let export_count: number = $derived(entries.length); - // Re-generate preview when entries or format changes + const templates = Object.values(EXPORT_TEMPLATES); + const selected_template = $derived(EXPORT_TEMPLATES[selected_template_id as keyof typeof EXPORT_TEMPLATES] || EXPORT_TEMPLATES.standard_markdown); + + // Auto-select template based on journal type when modal opens + $effect(() => { + if (open && journal?.type_code) { + if (journal.type_code === 'personal_log' && EXPORT_TEMPLATES.personal_log) { + selected_template_id = 'personal_log'; + } else if (journal.type_code === 'amazon_vine' && EXPORT_TEMPLATES.amazon_vine) { + selected_template_id = 'amazon_vine'; + } + } + }); + + // Re-generate preview when entries or template changes $effect(() => { if (open && entries.length > 0) { generate_preview(); } }); - /** - * Generates the export string based on the selected format. - */ function generate_preview() { - if (export_format === 'json') { - // Simple JSON dump - export_preview = JSON.stringify(entries, null, 2); - } else if (export_format === 'html') { - // HTML: Concatenate rendered content with semantic separators - export_preview = entries.map(entry => { - const dateStr = ae_util.iso_datetime_formatter(entry.created_on, 'datetime_12_long'); - const title = entry.name || dateStr; - return ` -
-
-

${title}

- -
-
- ${entry.content_md_html || ''} -
-
-
`; - }).join('\n\n'); - } else { - // Markdown: Standard concatenation - export_preview = entries.map(entry => { - const dateStr = ae_util.iso_datetime_formatter(entry.created_on, 'datetime_12_long'); - const title = entry.name || dateStr; - // Ensure we don't double up headers if the content already starts with one - let content = entry.content || ''; - const header = `# ${title}\n*${dateStr}*\n\n`; - return `${header}${content}\n\n---\n`; - }).join('\n'); - } + if (!selected_template) return; + export_preview = selected_template.formatter(entries); } /** @@ -75,10 +57,9 @@ */ function handle_download() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const filename = `journal_export_${timestamp}.${export_format === 'markdown' ? 'md' : export_format}`; + const filename = `journal_export_${timestamp}.${selected_template.extension}`; const blob = new Blob([export_preview], { type: 'text/plain;charset=utf-8' }); - // Create temporary link to trigger download const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; @@ -111,41 +92,48 @@ class="w-full" >
-
- Ready to Export: {export_count} entries currently filtered/visible. +
+
+ Ready to Export: {export_count} entries. +
+ {#if journal} +
+ Journal Type: {journal.type_code || 'standard'} +
+ {/if}
- -
- - - - - + +
+ +
+ {#each templates as template} + + {/each} +
-