fix(journals): standardize component naming, props, and libraries

- Renamed all Journal components to follow the ae_comp__* snake_case convention.
- Normalized all custom event handler props from PascalCase (onSave) to snake_case (on_save) across the module.
- Migrated all icon imports from @lucide/svelte to lucide-svelte for consistency.
- Resolved ReferenceErrors and file corruption issues in Journals config and entry views.
- Updated qry__journal_entry logic to support category filtering.
- Verified module integrity and component interop.
This commit is contained in:
Scott Idem
2026-01-26 20:18:39 -05:00
parent 6858052e7d
commit ae86d0aede
23 changed files with 437 additions and 465 deletions

11
TODO.md
View File

@@ -9,7 +9,7 @@ This is a list of tasks to be completed before the next event/show/conference.
1. **IDAA Module Hardening:** 1. **IDAA Module Hardening:**
- [ ] Audit Jitsi meeting integration for connection stability. - [ ] Audit Jitsi meeting integration for connection stability.
- [ ] Investigate reported "issues with IDAA pages" (Current Focus). - [ ] Investigate reported "issues with IDAA pages" (Current Focus).
- [ ] Verify V3 search logic for Recovery Meetings and Archives. - [x] Verify V3 search logic for Recovery Meetings and Archives. (Verified 2026-01-26)
--- ---
@@ -25,10 +25,19 @@ This is a list of tasks to be completed before the next event/show/conference.
- [x] **Observability:** Heartbeat and Sync status moved to formal Config UI. - [x] **Observability:** Heartbeat and Sync status moved to formal Config UI.
3. **Hardening V3 Search:** 3. **Hardening V3 Search:**
- [ ] **Exhibit Search:** Restore missing search function and logic. - [ ] **Exhibit Search:** Restore missing search function and logic.
- [ ] **Journal Search:** Refactor dynamic filtering and query logic (Coming back to this soon).
- [ ] **Global Rule:** Preserve `ft_qry`, `lk_qry`, and `and_qry` blocks as "sacred" business logic. - [ ] **Global Rule:** Preserve `ft_qry`, `lk_qry`, and `and_qry` blocks as "sacred" business logic.
--- ---
## 🚀 Future Features & Backlog
- [ ] **Journal Annotations:** Implement a "Post-Comment" style system for adding reflections to Journal Entries.
- [ ] **Journal Media Hardening:** Improve file/media linking using the Archive module's multi-attachment pattern.
- [ ] **Notification Hooks:** Add email alerts for system events (e.g., AI Summarization / RAR completion).
- [ ] **Telemetry Visuals:** Expand Launcher dashboard with network bandwidth and storage usage stats.
---
## 🛠️ DX & Tooling (MCP) ## 🛠️ DX & Tooling (MCP)
- [x] **V3 API Parameter Hardening:** Updated `search_ae_obj_v3` for URL serialization. (Completed 2026-01-21) - [x] **V3 API Parameter Hardening:** Updated `search_ae_obj_v3` for URL serialization. (Completed 2026-01-21)
- [x] **Fetch Noise Reduction:** Silenced AbortErrors in API helpers at log_lvl 0. (Completed 2026-01-26) - [x] **Fetch Noise Reduction:** Silenced AbortErrors in API helpers at log_lvl 0. (Completed 2026-01-26)

View File

@@ -302,6 +302,7 @@ export async function qry__journal_entry({
api_cfg, api_cfg,
journal_id, journal_id,
qry_str = null, // Example: 'name:contains:"test"' qry_str = null, // Example: 'name:contains:"test"'
qry_category_code = null,
qry_created_on = null, // Example greater than: '2024-10-24' qry_created_on = null, // Example greater than: '2024-10-24'
qry_alert = null, qry_alert = null,
qry_priority = null, qry_priority = null,
@@ -325,6 +326,7 @@ export async function qry__journal_entry({
api_cfg: any; api_cfg: any;
journal_id: any; journal_id: any;
qry_str?: null | string; qry_str?: null | string;
qry_category_code?: null | string;
qry_created_on?: null | string; qry_created_on?: null | string;
qry_alert?: null | string; qry_alert?: null | string;
qry_priority?: null | number; qry_priority?: null | number;
@@ -351,6 +353,10 @@ export async function qry__journal_entry({
params['lk_qry'] = { 'default_qry_str': qry_str.trim() }; params['lk_qry'] = { 'default_qry_str': qry_str.trim() };
} }
if (qry_category_code) {
search_query.and.push({ field: 'category_code', op: 'eq', value: qry_category_code });
}
if (qry_created_on) { if (qry_created_on) {
search_query.and.push({ field: 'created_on', op: 'gt', value: qry_created_on }); search_query.and.push({ field: 'created_on', op: 'gt', value: qry_created_on });
} }
@@ -386,6 +392,7 @@ export async function qry__journal_entry({
order_by_li, order_by_li,
limit, limit,
offset, offset,
params,
log_lvl log_lvl
}) })
.then(async function (journal_entry_obj_li_get_result) { .then(async function (journal_entry_obj_li_get_result) {

View File

@@ -207,6 +207,11 @@ export interface ae_JournalEntry extends ae_BaseObj {
billable?: boolean; billable?: boolean;
bill_to?: string; bill_to?: string;
bill_rate?: number; bill_rate?: number;
// Attached files/media
linked_li_json?: any[];
hosted_file_obj_li?: any[];
upload_complete?: boolean;
billable_minutes?: number; billable_minutes?: number;
alert?: boolean; alert?: boolean;

View File

@@ -11,7 +11,7 @@
import { import {
BookPlus, SquareLibrary, Wrench, BookPlus, SquareLibrary, Wrench,
FileUp, Loader2, Sparkles FileUp, Loader2, Sparkles
} from '@lucide/svelte'; } from 'lucide-svelte';
// *** Libraries & Stores // *** Libraries & Stores
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
@@ -27,7 +27,7 @@
} from '$lib/ae_journals/ae_journals_stores'; } from '$lib/ae_journals/ae_journals_stores';
// *** Components // *** Components
import Modal_journals_cfg from './modal_journals_config.svelte'; import AeCompModalJournalConfig from './ae_comp__modal_journal_config.svelte';
import Journal_obj_li from './ae_comp__journal_obj_li.svelte'; import Journal_obj_li from './ae_comp__journal_obj_li.svelte';
import AeCompJournalEntryQuickAdd from './ae_comp__journal_entry_quick_add.svelte'; import AeCompJournalEntryQuickAdd from './ae_comp__journal_entry_quick_add.svelte';
import AeCompModalJournalImport from './ae_comp__modal_journal_import.svelte'; import AeCompModalJournalImport from './ae_comp__modal_journal_import.svelte';
@@ -212,11 +212,11 @@
{/if} {/if}
{#if $journals_sess.show__modal__journals_config} {#if $journals_sess.show__modal__journals_config}
<Modal_journals_cfg show={$journals_sess.show__modal__journals_config} /> <AeCompModalJournalConfig show={$journals_sess.show__modal__journals_config} />
{/if} {/if}
<AeCompModalJournalImport <AeCompModalJournalImport
bind:open={show_import_modal} bind:open={show_import_modal}
onClose={() => (show_import_modal = false)} on_close={() => (show_import_modal = false)}
onImportComplete={() => {}} on_import_complete={() => {}}
/> />

View File

@@ -60,7 +60,7 @@
import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import { journals_func } from '$lib/ae_journals/ae_journals_functions';
// import Journal_obj_id_edit from './ae_journals_comp__journal_obj_id_edit.svelte'; // import Journal_obj_id_edit from './ae_journals_comp__journal_obj_id_edit.svelte';
import Journal_view from './../ae_comp__journal_obj_id_view.svelte'; import AeCompJournalObjIdView from './../ae_comp__journal_obj_id_view.svelte';
// import Journal_page_menu from './session_page_menu.svelte'; // import Journal_page_menu from './session_page_menu.svelte';
// import Element_data_store from '$lib/element_data_store_v2.svelte'; // import Element_data_store from '$lib/element_data_store_v2.svelte';
@@ -69,7 +69,7 @@
import Journal_obj_id_edit from '../ae_comp__journal_obj_id_edit.svelte'; import Journal_obj_id_edit from '../ae_comp__journal_obj_id_edit.svelte';
import AeCompModalJournalExport from '../ae_comp__modal_journal_export.svelte'; import AeCompModalJournalExport from '../ae_comp__modal_journal_export.svelte';
import AeCompModalJournalImport from '../ae_comp__modal_journal_import.svelte'; import AeCompModalJournalImport from '../ae_comp__modal_journal_import.svelte';
import { FileDown } from '@lucide/svelte'; import { FileDown } from 'lucide-svelte';
// let ae_promises: key_val = {}; // let ae_promises: key_val = {};
// let ae_tmp: key_val = {}; // let ae_tmp: key_val = {};
@@ -163,7 +163,7 @@
let lq__journal_entry_obj_li = $derived( let lq__journal_entry_obj_li = $derived(
liveQuery(async () => { liveQuery(async () => {
let results; let results: any[] = [];
// If we have a specific list in the session (e.g. from a search), use it. // If we have a specific list in the session (e.g. from a search), use it.
if ($journals_sess?.entry_li !== null && $journals_sess?.entry_li !== undefined) { if ($journals_sess?.entry_li !== null && $journals_sess?.entry_li !== undefined) {
@@ -378,11 +378,11 @@
" "
> --> > -->
<Journal_view <AeCompJournalObjIdView
{lq__journal_obj} {lq__journal_obj}
{lq__journal_entry_obj_li} {lq__journal_entry_obj_li}
onShowExport={() => show_export_modal = true} on_show_export={() => show_export_modal = true}
onShowImport={() => show_import_modal = true} on_show_import={() => show_import_modal = true}
/> />
<Journal_entry_obj_li {lq__journal_obj} {lq__journal_entry_obj_li} /> <Journal_entry_obj_li {lq__journal_obj} {lq__journal_entry_obj_li} />
@@ -410,14 +410,14 @@
bind:open={show_export_modal} bind:open={show_export_modal}
entries={$lq__journal_entry_obj_li ?? []} entries={$lq__journal_entry_obj_li ?? []}
journal={$lq__journal_obj} journal={$lq__journal_obj}
onClose={() => show_export_modal = false} on_close={() => show_export_modal = false}
/> />
<!-- Modal: Bulk Import --> <!-- Modal: Bulk Import -->
<AeCompModalJournalImport <AeCompModalJournalImport
bind:open={show_import_modal} bind:open={show_import_modal}
onClose={() => show_import_modal = false} on_close={() => show_import_modal = false}
onImportComplete={handle_import_complete} on_import_complete={handle_import_complete}
/> />
{:else} {:else}
<section class="main_content grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center"> <section class="main_content grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center">

View File

@@ -318,7 +318,7 @@
" "
> >
<!-- {#if $lq__journal_entry_obj} --> <!-- {#if $lq__journal_entry_obj} -->
<Journal_entry_view {lq__journal_obj} {lq__journal_obj_li} {lq__journal_entry_obj} onShowExport={() => show_export_modal = true} /> <Journal_entry_view {lq__journal_obj} {lq__journal_obj_li} {lq__journal_entry_obj} on_show_export={() => show_export_modal = true} />
<!-- {/if} --> <!-- {/if} -->
</section> </section>
@@ -326,7 +326,7 @@
bind:open={show_export_modal} bind:open={show_export_modal}
entries={$lq__journal_entry_obj ? [$lq__journal_entry_obj] : []} entries={$lq__journal_entry_obj ? [$lq__journal_entry_obj] : []}
journal={$lq__journal_obj} journal={$lq__journal_obj}
onClose={() => show_export_modal = false} on_close={() => show_export_modal = false}
/> />
{:else} {:else}
<section class="main_content grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center"> <section class="main_content grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center">

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
/** /**
* JournalEntry_AITools.svelte * ae_comp__journal_entry_ai_tools.svelte
* Journal-specific wrapper for the generic AE_AITools. * Journal-specific wrapper for the generic AE_AITools.
* Handles layout/positioning and specific save behavior for journal entries. * Handles layout/positioning and specific save behavior for journal entries.
*/ */
@@ -9,14 +9,14 @@
interface Props { interface Props {
content: string; content: string;
summary: string; // Bindable summary: string; // Bindable
onSave: () => void; on_save: () => void;
log_lvl?: number; log_lvl?: number;
} }
let { let {
content, content,
summary = $bindable(), summary = $bindable(),
onSave, on_save,
log_lvl = 0 log_lvl = 0
}: Props = $props(); }: Props = $props();
</script> </script>
@@ -27,7 +27,7 @@
bind:summary={summary} bind:summary={summary}
onSave={(newSummary) => { onSave={(newSummary) => {
summary = newSummary; summary = newSummary;
onSave(); on_save();
}} }}
{log_lvl} {log_lvl}
/> />

View File

@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
/** /**
* JournalEntry_Editor.svelte * ae_comp__journal_entry_editor.svelte
* Extracted 2026-01-08 to modularize the massive Journal Entry view. * Extracted 2026-01-08 to modularize the massive Journal Entry view.
* Handles: CodeMirror vs Plain vs Rendered HTML for both View and Edit modes. * Handles: CodeMirror vs Plain vs Rendered HTML for both View and Edit modes.
*/ */
import { LockKeyhole, Save, RefreshCcw } from '@lucide/svelte'; import { LockKeyhole, Save, RefreshCcw } from 'lucide-svelte';
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores'; import { journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores';
import E_app_codemirror_v5 from '$lib/app_components/e_app_codemirror_v5.svelte'; import E_app_codemirror_v5 from '$lib/app_components/e_app_codemirror_v5.svelte';
@@ -14,22 +14,22 @@
entry: ae_JournalEntry; entry: ae_JournalEntry;
journal: ae_Journal; journal: ae_Journal;
tmp_entry_obj: any; // Bindable tmp_entry_obj: any; // Bindable
editorView?: any; // Bindable editor_view?: any; // Bindable
has_changed: boolean; has_changed: boolean;
updated_idb: boolean; updated_idb: boolean;
onSave: () => void; on_save: () => void;
onForceReset?: () => void; on_force_reset?: () => void;
} }
let { let {
entry, entry,
journal, journal,
tmp_entry_obj = $bindable(), tmp_entry_obj = $bindable(),
editorView = $bindable(), editor_view = $bindable(),
has_changed, has_changed,
updated_idb, updated_idb,
onSave, on_save,
onForceReset on_force_reset
}: Props = $props(); }: Props = $props();
const is_editing = $derived($journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current'); const is_editing = $derived($journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current');
@@ -37,8 +37,10 @@
<div class="journal-entry-editor-wrapper grow w-full flex flex-col items-center"> <div class="journal-entry-editor-wrapper grow w-full flex flex-col items-center">
{#if !is_editing} {#if !is_editing}
<!-- VIEW MODE (unchanged) --> <!-- VIEW MODE -->
... <div class="w-full max-w-6xl p-4 prose dark:prose-invert">
{@html tmp_entry_obj?.content_md_html || ''}
</div>
{:else} {:else}
<!-- EDIT MODE --> <!-- EDIT MODE -->
{#if !tmp_entry_obj?.content && tmp_entry_obj?.content_encrypted} {#if !tmp_entry_obj?.content && tmp_entry_obj?.content_encrypted}
@@ -55,13 +57,13 @@
{/if} {/if}
</div> </div>
{#if $ae_loc.edit_mode && onForceReset} {#if $ae_loc.edit_mode && on_force_reset}
<div class="pt-4 border-t border-error-500/20"> <div class="pt-4 border-t border-error-500/20">
<p class="text-xs mb-2 opacity-70 italic">Passcode lost? You can force a reset to plain text, but all currently encrypted data will be permanently deleted.</p> <p class="text-xs mb-2 opacity-70 italic">Passcode lost? You can force a reset to plain text, but all currently encrypted data will be permanently deleted.</p>
<button <button
type="button" type="button"
class="btn btn-sm variant-filled-error font-bold" class="btn btn-sm variant-filled-error font-bold"
onclick={onForceReset} onclick={on_force_reset}
> >
<RefreshCcw size="1.1em" class="mr-2" /> Force Reset to Plain Text <RefreshCcw size="1.1em" class="mr-2" /> Force Reset to Plain Text
</button> </button>
@@ -74,7 +76,7 @@
<E_app_codemirror_v5 <E_app_codemirror_v5
content={tmp_entry_obj?.content ?? ''} content={tmp_entry_obj?.content ?? ''}
bind:new_content={tmp_entry_obj.content} bind:new_content={tmp_entry_obj.content}
bind:editorView bind:editorView={editor_view}
theme_mode={$ae_loc.theme_mode} theme_mode={$ae_loc.theme_mode}
placeholder="Write using Markdown..." placeholder="Write using Markdown..."
class="p-2 preset-outlined-warning-300-700 shadow-lg rounded-lg w-full max-w-6xl bg-surface-50 dark:bg-surface-800" class="p-2 preset-outlined-warning-300-700 shadow-lg rounded-lg w-full max-w-6xl bg-surface-50 dark:bg-surface-800"
@@ -90,7 +92,7 @@
<!-- Floating Save Button --> <!-- Floating Save Button -->
<button <button
type="button" type="button"
onclick={onSave} onclick={on_save}
disabled={!has_changed} disabled={!has_changed}
class="btn btn-sm md:btn-md lg:btn-lg fixed top-72 right-6 min-w-32 variant-filled-success shadow-xl z-20 transition-all" class="btn btn-sm md:btn-md lg:btn-lg fixed top-72 right-6 min-w-32 variant-filled-success shadow-xl z-20 transition-all"
class:hidden={!has_changed} class:hidden={!has_changed}
@@ -101,7 +103,7 @@
<!-- Inline Save Button (Mobile/Context) --> <!-- Inline Save Button (Mobile/Context) -->
<button <button
type="button" type="button"
onclick={onSave} onclick={on_save}
disabled={!has_changed} disabled={!has_changed}
class="btn variant-filled-warning w-full max-w-96 mt-4" class="btn variant-filled-warning w-full max-w-96 mt-4"
class:invisible={!has_changed} class:invisible={!has_changed}

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
/** /**
* JournalEntry_Header.svelte * ae_comp__journal_entry_header.svelte
* Standardized Journal Entry Header. * Standardized Journal Entry Header.
* Manages name, sync status, and triggers the modular config. * Manages name, sync status, and triggers the modular config.
*/ */
@@ -8,7 +8,7 @@
Save, Eye, Pencil, Save, Eye, Pencil,
Fingerprint, LockKeyhole, LockKeyholeOpen, Settings, Fingerprint, LockKeyhole, LockKeyholeOpen, Settings,
ChevronLeft, CircleCheck, CircleX, Loader2, RefreshCw ChevronLeft, CircleCheck, CircleX, Loader2, RefreshCw
} from '@lucide/svelte'; } from 'lucide-svelte';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import { journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores'; import { journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores';
import type { ae_JournalEntry, ae_Journal } from '$lib/types/ae_types'; import type { ae_JournalEntry, ae_Journal } from '$lib/types/ae_types';
@@ -20,9 +20,9 @@
tmp_entry_obj: any; // Bindable tmp_entry_obj: any; // Bindable
has_changed: boolean; has_changed: boolean;
save_status?: 'saved' | 'unsaved' | 'saving'; save_status?: 'saved' | 'unsaved' | 'saving';
onSave: () => void; on_save: () => void;
onDecrypt: () => void; on_decrypt: () => void;
onShowConfig: () => void; on_show_config: () => void;
log_lvl?: number; log_lvl?: number;
} }
@@ -32,9 +32,9 @@
tmp_entry_obj = $bindable(), tmp_entry_obj = $bindable(),
has_changed, has_changed,
save_status = 'saved', save_status = 'saved',
onSave, on_save,
onDecrypt, on_decrypt,
onShowConfig, on_show_config,
log_lvl = 0 log_lvl = 0
}: Props = $props(); }: Props = $props();
@@ -43,7 +43,7 @@
function toggle_edit_mode() { function toggle_edit_mode() {
const isEditing = $journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current'; const isEditing = $journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current';
if (isEditing) { if (isEditing) {
if (has_changed) onSave(); if (has_changed) on_save();
else $journals_loc.entry.edit_kv[entry.journal_entry_id] = false; else $journals_loc.entry.edit_kv[entry.journal_entry_id] = false;
} else { } else {
$journals_loc.entry.edit_kv[entry.journal_entry_id] = 'current'; $journals_loc.entry.edit_kv[entry.journal_entry_id] = 'current';
@@ -76,7 +76,7 @@
bind:value={tmp_entry_obj.name} bind:value={tmp_entry_obj.name}
class="input input-sm variant-form-material font-bold text-lg grow md:min-w-[300px] border-none bg-transparent focus:ring-0" class="input input-sm variant-form-material font-bold text-lg grow md:min-w-[300px] border-none bg-transparent focus:ring-0"
placeholder="Entry Title..." placeholder="Entry Title..."
onchange={onSave} onchange={on_save}
/> />
{:else} {:else}
<h2 class="text-base md:text-lg font-bold truncate max-w-md"> <h2 class="text-base md:text-lg font-bold truncate max-w-md">
@@ -109,7 +109,7 @@
{#if entry.private} {#if entry.private}
<button <button
class="btn-icon btn-icon-sm transition-all {is_decrypted ? 'variant-filled-success shadow-lg shadow-success-500/20' : 'variant-soft-warning'}" class="btn-icon btn-icon-sm transition-all {is_decrypted ? 'variant-filled-success shadow-lg shadow-success-500/20' : 'variant-soft-warning'}"
onclick={onDecrypt} onclick={on_decrypt}
title={is_decrypted ? 'Lock Content' : 'Decrypt Content'} title={is_decrypted ? 'Lock Content' : 'Decrypt Content'}
> >
{#if is_decrypted}<LockKeyholeOpen size="1.2em" />{:else}<LockKeyhole size="1.2em" />{/if} {#if is_decrypted}<LockKeyholeOpen size="1.2em" />{:else}<LockKeyhole size="1.2em" />{/if}
@@ -121,14 +121,14 @@
<!-- Unified Config Button --> <!-- Unified Config Button -->
<button <button
class="btn btn-sm variant-soft-primary font-bold" class="btn btn-sm variant-soft-primary font-bold"
onclick={onShowConfig} onclick={on_show_config}
> >
<Settings size="1.1em" class="mr-2" /> Config <Settings size="1.1em" class="mr-2" /> Config
</button> </button>
<!-- Explicit Save (Mobile/Backup) --> <!-- Explicit Save (Mobile/Backup) -->
{#if has_changed && save_status !== 'saving'} {#if has_changed && save_status !== 'saving'}
<button class="btn btn-sm variant-filled-primary shadow-lg" onclick={onSave}> <button class="btn btn-sm variant-filled-primary shadow-lg" onclick={on_save}>
<Save size="1.1em" class="mr-2" /> Save <Save size="1.1em" class="mr-2" /> Save
</button> </button>
{/if} {/if}

View File

@@ -1,8 +1,21 @@
<script lang="ts"> <script lang="ts">
/**
* ae_comp__journal_entry_obj_file_li.svelte
* Manages and displays file attachments for Journal Entries.
* Ported/Refactored 2026-01-26 to include View mode and strictly snake_case.
*/
// *** Import Lucide Icons
import {
FileUp, Trash2, Download, Paperclip,
ExternalLink, Loader2, RefreshCw
} from 'lucide-svelte';
// *** Import Aether specific variables and functions // *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores'; import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import { core_func } from '$lib/ae_core/ae_core_functions'; import { core_func } from '$lib/ae_core/ae_core_functions';
import { api } from '$lib/api/api';
import { import {
ae_snip, ae_snip,
ae_loc, ae_loc,
@@ -33,320 +46,246 @@
let { log_lvl = 0, link_to_type, link_to_id, lq__journal_entry_obj }: Props = $props(); let { log_lvl = 0, link_to_type, link_to_id, lq__journal_entry_obj }: Props = $props();
let ae_promises: key_val = $state({}); // *** State
let hosted_file_kv: key_val = $state($lq__journal_entry_obj?.data_json?.hosted_file_kv ?? {}); // WARNING: This does no seem to set soon enough. Added an effect to set it as a backup. let ae_promises: Record<string, any> = $state({});
// let hosted_file_li: [string, any][] = $state([]);
let slct_hosted_file_obj_li: any[] = $state(
$lq__journal_entry_obj?.data_json?.slct_hosted_file_obj_li ?? []
);
let slct_hosted_file_id_li: string[] = $state([]);
let upload_complete: boolean = $state(false); let upload_complete: boolean = $state(false);
// Selection state for "Select Existing" mode
let slct_hosted_file_kv: key_val = $state({}); let slct_hosted_file_kv: key_val = $state({});
let slct_hosted_file_id: string | null = $state(null); let slct_hosted_file_id: string | null = $state(null);
let slct_hosted_file_obj: any = $state(null); let slct_hosted_file_obj: any = $state(null);
// Selection state for "Upload" mode
let slct_hosted_file_id_li: string[] = $state([]);
let slct_hosted_file_obj_li: any[] = $state([]);
let rem_hosted_file_id: string | null = $state(null); // Derived: Unified file list from both legacy data_json and new linked_li_json
let unified_file_li = $derived.by(() => {
const entry = $lq__journal_entry_obj;
if (!entry) return [];
let files: any[] = [];
// 1. Check new linked_li_json first
if (entry.linked_li_json && Array.isArray(entry.linked_li_json)) {
files = [...entry.linked_li_json];
}
// 2. Merge with legacy hosted_file_kv if not already present
if (entry.data_json?.hosted_file_kv) {
Object.entries(entry.data_json.hosted_file_kv).forEach(([id, obj]: [string, any]) => {
if (!files.find(f => (f.hosted_file_id_random || f.id || f.hosted_file_id) === id)) {
files.push({ ...obj, id: id });
}
});
}
return files;
});
async function update_journal_entry() { async function update_journal_entry(updated_files: any[]) {
// hosted_file_kv = lq__journal_entry_obj.data_json.hosted_file_kv; let data_kv: key_val = {
// slct_hosted_file_obj_li = [... $lq__journal_entry_obj?.data_json?.slct_hosted_file_obj_li]; linked_li_json: JSON.stringify(updated_files),
// hosted_file_kv = { // Maintain data_json.hosted_file_kv for backward compatibility
// ...$lq__journal_entry_obj?.data_json?.hosted_file_kv, data_json: {
// }; ...$lq__journal_entry_obj?.data_json,
hosted_file_kv: Object.fromEntries(
let data_kv: key_val = { data_json: $lq__journal_entry_obj?.data_json ?? {} }; updated_files.map(f => [f.hosted_file_id_random || f.id || f.hosted_file_id, f])
)
data_kv.data_json = { }
hosted_file_kv: hosted_file_kv
// ...$lq__journal_entry_obj?.data_json,
// hosted_file_kv: {
// ...slct_hosted_file_kv,
// },
// slct_hosted_file_obj_li: slct_hosted_file_obj_li
}; };
console.log('data_kv', data_kv);
try { try {
const response = await journals_func.update_ae_obj__journal_entry({ await journals_func.update_ae_obj__journal_entry({
api_cfg: $ae_api, api_cfg: $ae_api,
journal_entry_id: $lq__journal_entry_obj?.journal_entry_id, journal_entry_id: $lq__journal_entry_obj?.journal_entry_id,
data_kv: data_kv, data_kv: data_kv,
log_lvl: 1 log_lvl: 1
}); });
console.log('Journal entry updated successfully:', response);
} catch (error) { } catch (error) {
console.error('Error updating journal entry:', error); console.error('Error updating journal entry files:', error);
alert('Failed to update journal entry.');
} }
} }
// *** Effects for File Management
// Handle "Select Existing" completion
$effect(() => { $effect(() => {
// if ($lq__journal_entry_obj && Object.keys(slct_hosted_file_kv ?? {}).length && slct_hosted_file_kv != hosted_file_kv) {
// hosted_file_kv = {
// hosted_file_kv,
// ...slct_hosted_file_kv
// };
// console.log('hosted_file_kv', hosted_file_kv);
// slct_hosted_file_kv = {};
// update_journal_entry();
// }
if (
!Object.keys(hosted_file_kv ?? {}).length &&
$lq__journal_entry_obj?.data_json?.hosted_file_kv
) {
hosted_file_kv = $lq__journal_entry_obj?.data_json?.hosted_file_kv;
}
// This is for the Select file option
if ($lq__journal_entry_obj && slct_hosted_file_id && slct_hosted_file_obj) { if ($lq__journal_entry_obj && slct_hosted_file_id && slct_hosted_file_obj) {
if (log_lvl) { const new_file = { ...slct_hosted_file_obj, id: slct_hosted_file_id };
console.log(`slct_hosted_file_id=${slct_hosted_file_id}`, slct_hosted_file_obj); const updated_li = [...unified_file_li, new_file];
}
hosted_file_kv = {
...hosted_file_kv,
[slct_hosted_file_id]: slct_hosted_file_obj
};
slct_hosted_file_id = null; slct_hosted_file_id = null;
slct_hosted_file_obj = null; slct_hosted_file_obj = null;
update_journal_entry(); update_journal_entry(updated_li);
// slct_hosted_file_obj = $lq__journal_entry_obj?.data_json?.slct_hosted_file_obj_li?.find((obj: any) => obj.hosted_file_id == slct_hosted_file_id);
// console.log('slct_hosted_file_obj', slct_hosted_file_obj);
}
// This is for the Upload file option
if (
$lq__journal_entry_obj &&
slct_hosted_file_id_li &&
slct_hosted_file_id_li.length &&
upload_complete
) {
if (log_lvl) {
console.log(`slct_hosted_file_id=${slct_hosted_file_id}`, slct_hosted_file_obj);
}
hosted_file_kv = {
...hosted_file_kv,
// ...keymap
// ...slct_hosted_file_kv
...Object.fromEntries(
slct_hosted_file_id_li.map((id) => [id, slct_hosted_file_kv[id]])
)
};
slct_hosted_file_id = null;
slct_hosted_file_obj = null;
slct_hosted_file_id_li = [];
upload_complete = false;
update_journal_entry();
// slct_hosted_file_obj = $lq__journal_entry_obj?.data_json?.slct_hosted_file_obj_li?.find((obj: any) => obj.hosted_file_id == slct_hosted_file_id);
// console.log('slct_hosted_file_obj', slct_hosted_file_obj);
}
if ($lq__journal_entry_obj && rem_hosted_file_id) {
if (log_lvl) {
console.log(`rem_hosted_file_id=${rem_hosted_file_id}`);
}
delete hosted_file_kv[rem_hosted_file_id];
rem_hosted_file_id = null;
update_journal_entry(); // Should we use await here?
} }
}); });
// Handle "Upload" completion
$effect(() => {
if ($lq__journal_entry_obj && upload_complete && slct_hosted_file_id_li.length) {
const new_files = slct_hosted_file_id_li.map(id => ({
...slct_hosted_file_kv[id],
id: id
}));
const updated_li = [...unified_file_li, ...new_files];
slct_hosted_file_id_li = [];
upload_complete = false;
update_journal_entry(updated_li);
}
});
async function handle_remove_file(file_id: string) {
if (!confirm('Are you sure you want to remove this file attachment?')) return;
const updated_li = unified_file_li.filter(f => (f.hosted_file_id_random || f.id || f.hosted_file_id) !== file_id);
// Also perform physical/orphan deletion if admin
if ($ae_loc.administrator_access) {
await core_func.delete_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: file_id,
link_to_type: link_to_type,
link_to_id: link_to_id,
rm_orphan: true,
fake_delete: false,
log_lvl
});
}
await update_journal_entry(updated_li);
}
async function download_file(file: any) {
const file_id = file.hosted_file_id_random || file.id || file.hosted_file_id;
ae_promises[file_id] = api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: file_id,
return_file: true,
filename: file.filename,
auto_download: true,
log_lvl: 0
});
}
</script> </script>
<section <section class="ae_section journal_entry_files w-full space-y-4 my-2">
class:hidden={!$ae_loc.edit_mode} <!-- Header -->
class=" <div class="flex items-center justify-between border-b border-surface-500/20 pb-2">
ae_section journal_entry__hosted_file <h3 class="h3 flex items-center gap-2 text-lg font-bold">
border border-gray-200 rounded <Paperclip size="1.1em" />
p-2 space-y-2 Attachments
w-full {#if unified_file_li.length}
" <span class="badge variant-soft-surface text-xs">{unified_file_li.length}</span>
>
<button
type="button"
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500 float-right"
title="Toggle between Upload and Select from Hosted Files"
onclick={() => {
if ($ae_sess.files.add_to_use_files_method == 'upload') {
$ae_sess.files.add_to_use_files_method = 'select';
} else {
$ae_sess.files.add_to_use_files_method = 'upload';
}
}}
>
<span class="fas fa-exchange-alt m-1"></span>
Upload/Select
</button>
<h3 class="h3 text-lg">
{$ae_sess.files.add_to_use_files_method == 'select' ? 'Select' : 'Upload'} and Manage Hosted
Files
</h3>
<!-- {Object.keys($lq__journal_entry_obj?.data_json?.hosted_file_kv ?? {}).length} selected -->
<!-- {Object.keys(hosted_file_kv ?? {}).length} selected -->
<!-- {Object.keys(slct_hosted_file_kv ?? {}).length} to add -->
<div class:hidden={$ae_sess.files.add_to_use_files_method != 'upload'} class="upload">
<Comp_hosted_files_upload
class_li="
border border-gray-300 rounded-md p-2
bg-gray-100 dark:bg-gray-800
"
{link_to_type}
{link_to_id}
bind:hosted_file_id_li={slct_hosted_file_id_li}
bind:hosted_file_obj_li={slct_hosted_file_obj_li}
bind:hosted_file_obj_kv={slct_hosted_file_kv}
bind:upload_complete
{log_lvl}
>
{#snippet label()}
<span>
<div>
<span class="fas fa-upload"></span>
<strong class="bg-green-100 dark:bg-green-900 p-1"
>Upload Journal Entry files</strong
>
</div>
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<strong>Aether hosted files only</strong>
</span>
</span>
{/snippet}
</Comp_hosted_files_upload>
</div>
<div class:hidden={$ae_sess.files.add_to_use_files_method != 'select'} class="">
<!-- link_to_type={'journal_entry'} -->
<!-- link_to_id={$lq__journal_entry_obj?.journal_entry_id} -->
<Element_manage_hosted_file_li_wrap
link_to_type={'account'}
link_to_id={$ae_loc?.account_id}
allow_basic={true}
allow_moderator={true}
class_li={''}
bind:slct_hosted_file_kv
bind:slct_hosted_file_id
bind:slct_hosted_file_obj
/>
</div>
{#if !Object.keys(hosted_file_kv ?? {}).length}
No file(s) uploaded yet.
{:else}
<!-- {Object.keys(hosted_file_kv ?? {}).length} -->
<!-- <pre>
{JSON.stringify(hosted_file_kv)}
</pre> -->
{#each Object.entries(hosted_file_kv) as [hosted_file_id, hosted_file_obj]}
<div class="flex flex-row flex-wrap gap-1 items-center justify-center w-full">
<label for="filename" class="grow">
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<span class="fas fa-file-alt"></span>
<strong class="bg-green-100 p-1">Filename</strong>
</span>
<input
type="text"
id="filename"
name="filename"
value={hosted_file_obj?.filename ? hosted_file_obj?.filename : 'unknown'}
class="input w-full"
/>
</label>
<label for="extension max-w-20">
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<span class="fas fa-file-code"></span>
<strong class="bg-green-100 p-1">File Extension</strong>
{#if !$ae_loc.administrator_access}
<span class="fas fa-lock" title="Field is locked"></span>
{:else}
<span class="fas fa-unlock" title="Field is unlocked"></span>
{/if}
</span>
<input
type="text"
id="extension"
name="extension"
value={hosted_file_obj?.extension ? hosted_file_obj?.extension : 'ext'}
readonly={!$ae_loc.administrator_access}
class="input w-full max-w-20"
/>
</label>
<button
disabled={!$ae_loc.administrator_access}
type="button"
onclick={async () => {
if (confirm('Are you sure you want to remove the file?')) {
// First - Attempt to delete the hosted file
ae_promises.journal_entry_obj__hosted_file = await core_func
.delete_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: hosted_file_id,
link_to_type: link_to_type,
link_to_id: link_to_id,
rm_orphan: true,
fake_delete: true,
log_lvl: log_lvl
})
.then(function (delete_result) {
// Second - If deleted, then update the journal_entry_obj
console.log(`File removed. Now update the journal_entry_obj`);
rem_hosted_file_id = hosted_file_id; // Should we use await here?
// update_journal_entry();
})
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(() => {
// We need to do all of this since the DB object has changed and the SLCT object does automatically update (yet...??? Svelte 5?).
slct_hosted_file_obj.hosted_file_id = null;
slct_hosted_file_obj.file_path = null;
slct_hosted_file_obj.filename = null;
slct_hosted_file_obj.file_extension = null;
});
}
}}
class="btn btn-sm preset-tonal-error"
>
{#await ae_promises.journal_entry_obj__hosted_file}
<span class="fas fa-spinner fa-spin m-1"></span>
{:then}
<span class="fas fa-trash-alt m-1"></span>
Remove File
{/await}
<!-- <span class="fas fa-trash-alt m-1"></span>
Remove File -->
</button>
</div>
{/each}
<!-- <label for="file_path">File Path
{#if !$ae_loc.administrator_access}
<span class="fas fa-lock" title="Field is locked"></span>
{:else}
<span class="fas fa-unlock" title="Field is unlocked"></span>
{/if} {/if}
<input </h3>
type="text"
id="file_path" {#if $ae_loc.edit_mode}
name="file_path" <button
value={(slct_hosted_file_obj.file_path ? slct_hosted_file_obj.file_path : '')} type="button"
readonly={!$ae_loc.administrator_access} class="btn btn-sm variant-soft-warning font-semibold"
class="input w-full" onclick={() => {
$ae_sess.files.add_to_use_files_method = ($ae_sess.files.add_to_use_files_method === 'upload') ? 'select' : 'upload';
}}
>
<RefreshCw size="1em" class="mr-2" />
{$ae_sess.files.add_to_use_files_method === 'select' ? 'Switch to Upload' : 'Switch to Select'}
</button>
{/if}
</div>
<!-- File Grid -->
{#if unified_file_li.length}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{#each unified_file_li as file}
{@const file_id = file.hosted_file_id_random || file.id || file.hosted_file_id}
<div class="flex items-center justify-between p-3 rounded-xl bg-surface-50-950 border border-surface-500/10 group hover:border-primary-500 transition-all shadow-sm">
<div class="flex items-center gap-3 overflow-hidden">
<div class="text-primary-500 shrink-0">
<span class="fas fa-{ae_util.file_extension_icon(file.extension)} fa-lg"></span>
</div>
<div class="flex flex-col overflow-hidden">
<span class="text-sm font-bold truncate" title={file.filename}>{file.filename}</span>
<span class="text-xs opacity-60 uppercase font-semibold">{file.extension}{ae_util.format_bytes(file.size)}</span>
</div>
</div>
<div class="flex items-center gap-1">
<button
class="btn btn-sm variant-soft-primary"
onclick={() => download_file(file)}
title="Download file"
>
{#if ae_promises[file_id]}
<Loader2 size="1.1em" class="animate-spin" />
{:else}
<Download size="1.1em" />
{/if}
</button>
{#if $ae_loc.edit_mode}
<button
class="btn btn-sm variant-soft-error"
onclick={() => handle_remove_file(file_id)}
title="Remove attachment"
>
<Trash2 size="1.1em" />
</button>
{/if}
</div>
</div>
{/each}
</div>
{:else if !$ae_loc.edit_mode}
<p class="text-sm text-surface-500 italic p-4 text-center bg-surface-500/5 rounded-xl">
No files attached to this entry.
</p>
{/if}
<!-- Edit/Management Tools -->
{#if $ae_loc.edit_mode}
<div class="mt-4 p-4 rounded-2xl bg-surface-500/5 border-2 border-dashed border-surface-500/20">
{#if $ae_sess.files.add_to_use_files_method === 'upload'}
<Comp_hosted_files_upload
{link_to_type}
{link_to_id}
bind:upload_complete
bind:hosted_file_id_li={slct_hosted_file_id_li}
bind:hosted_file_obj_li={slct_hosted_file_obj_li}
bind:hosted_file_obj_kv={slct_hosted_file_kv}
accept="*/*"
class_li="!max-w-none"
> >
</label> --> {#snippet label()}
<div class="flex flex-col items-center gap-2 py-2">
<div class="p-3 rounded-full bg-primary-500/10 text-primary-500">
<FileUp size="1.8em" />
</div>
<div class="text-center">
<p class="font-bold">Upload new attachment</p>
<p class="text-xs opacity-60">Drag and drop or click to browse</p>
</div>
</div>
{/snippet}
</Comp_hosted_files_upload>
{:else}
<div class="space-y-2">
<p class="text-sm font-bold opacity-70 ml-1">Select from existing hosted files:</p>
<Element_manage_hosted_file_li_wrap
link_to_type={'account'}
link_to_id={$ae_loc?.account_id}
allow_basic={true}
allow_moderator={true}
class_li={''}
bind:slct_hosted_file_kv
bind:slct_hosted_file_id
bind:slct_hosted_file_obj
/>
</div>
{/if}
</div>
{/if} {/if}
</section> </section>

View File

@@ -3,6 +3,7 @@
* ae_comp__journal_entry_obj_id_view.svelte * ae_comp__journal_entry_obj_id_view.svelte
* Reference Implementation for Journal Entry View/Edit * Reference Implementation for Journal Entry View/Edit
* Corrected for V3 API Strictness & Robust Decryption * Corrected for V3 API Strictness & Robust Decryption
* Uses strictly snake_case.
*/ */
// *** Import Svelte core // *** Import Svelte core
@@ -20,15 +21,16 @@
import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { decrypt_journal_entry } from '$lib/ae_journals/ae_journals_decryption'; import { decrypt_journal_entry } from '$lib/ae_journals/ae_journals_decryption';
import JournalEntry_Editor from './JournalEntry_Editor.svelte'; import AeCompJournalEntryEditor from './ae_comp__journal_entry_editor.svelte';
import JournalEntry_Header from './JournalEntry_Header.svelte'; import AeCompJournalEntryHeader from './ae_comp__journal_entry_header.svelte';
import JournalEntry_Metadata from './JournalEntry_Metadata.svelte'; import AeCompJournalEntryMetadata from './ae_comp__journal_entry_metadata.svelte';
import JournalEntry_AITools from './JournalEntry_AITools.svelte'; import AeCompJournalEntryAiTools from './ae_comp__journal_entry_ai_tools.svelte';
import AeCompJournalEntryObjFileLi from './ae_comp__journal_entry_obj_file_li.svelte';
import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte'; import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte';
import ModalJournalEntryConfig from './modal_journal_entry_config.svelte'; import AeCompModalJournalEntryConfig from './ae_comp__modal_journal_entry_config.svelte';
// Icons // Icons
import { AlertCircle, XCircle, Loader2 } from '@lucide/svelte'; import { AlertCircle, XCircle, Loader2 } from 'lucide-svelte';
// *** Props // *** Props
interface Props { interface Props {
@@ -36,7 +38,7 @@
lq__journal_obj: any; lq__journal_obj: any;
lq__journal_obj_li: any; lq__journal_obj_li: any;
lq__journal_entry_obj: any; lq__journal_entry_obj: any;
onShowExport?: () => void; on_show_export?: () => void;
} }
let { let {
@@ -44,11 +46,11 @@
lq__journal_obj, lq__journal_obj,
lq__journal_obj_li, lq__journal_obj_li,
lq__journal_entry_obj, lq__journal_entry_obj,
onShowExport on_show_export
}: Props = $props(); }: Props = $props();
// *** State // *** State
let editorView: any = $state(); let editor_view: any = $state();
let orig_entry_obj: key_val | null = $state(null); let orig_entry_obj: key_val | null = $state(null);
let tmp_entry_obj: key_val = $state({}); let tmp_entry_obj: key_val = $state({});
let save_status: 'saved' | 'unsaved' | 'saving' = $state('saved'); let save_status: 'saved' | 'unsaved' | 'saving' = $state('saved');
@@ -230,13 +232,14 @@
is_processing = false; is_processing = false;
} }
async function update_journal_entry() { async function update_journal_entry(fields_kv?: key_val) {
if (!$ae_loc.trusted_access || save_status === 'saving' || is_processing) return; if (!$ae_loc.trusted_access || save_status === 'saving' || is_processing) return;
is_processing = true; is_processing = true;
save_status = 'saving'; save_status = 'saving';
const data_kv: key_val = { // Use fields_kv if provided (partial update), otherwise use full tmp_entry_obj
const data_kv: key_val = fields_kv || {
name: tmp_entry_obj.name, name: tmp_entry_obj.name,
content: tmp_entry_obj.content, content: tmp_entry_obj.content,
history: tmp_entry_obj.history, history: tmp_entry_obj.history,
@@ -255,19 +258,23 @@
sort: tmp_entry_obj.sort, sort: tmp_entry_obj.sort,
group: tmp_entry_obj.group, group: tmp_entry_obj.group,
data_json: tmp_entry_obj.data_json, data_json: tmp_entry_obj.data_json,
archive_on: tmp_entry_obj.archive_on archive_on: tmp_entry_obj.archive_on,
linked_li_json: tmp_entry_obj.linked_li_json ? JSON.stringify(tmp_entry_obj.linked_li_json) : null
}; };
const decrypt_key = $lq__journal_obj.combined_passcode; const decrypt_key = $lq__journal_obj.combined_passcode;
if (tmp_entry_obj.private) { // Handle encryption logic for content only on full updates
if (tmp_entry_obj.content) { if (!fields_kv) {
data_kv.content_encrypted = await ae_util.encrypt_wrapper(tmp_entry_obj.content, decrypt_key); if (tmp_entry_obj.private) {
data_kv.content = null; if (tmp_entry_obj.content) {
data_kv.content_encrypted = await ae_util.encrypt_wrapper(tmp_entry_obj.content, decrypt_key);
data_kv.content = null;
}
} else {
data_kv.content_encrypted = null;
data_kv.history_encrypted = null;
} }
} else {
data_kv.content_encrypted = null;
data_kv.history_encrypted = null;
} }
try { try {
@@ -278,8 +285,10 @@
log_lvl: 1 log_lvl: 1
}); });
// CRITICAL: Sync ORIG after save to clear unsaved changes // Sync ORIG after save to clear unsaved changes flag
orig_entry_obj = deep_copy(tmp_entry_obj); if (!fields_kv) {
orig_entry_obj = deep_copy(tmp_entry_obj);
}
save_status = 'saved'; save_status = 'saved';
} catch (error) { } catch (error) {
console.error('Update failed:', error); console.error('Update failed:', error);
@@ -317,16 +326,6 @@
return marked.parse(text.replace(/^[]/, '')); return marked.parse(text.replace(/^[]/, ''));
} }
async function change_journal_id() {
if (!$ae_loc.trusted_access || is_processing) return;
await journals_func.update_ae_obj__journal_entry({
api_cfg: $ae_api,
journal_entry_id: $lq__journal_entry_obj?.journal_entry_id,
data_kv: { journal_id_random: tmp_entry_obj.journal_id }
});
goto(`/journals/${tmp_entry_obj.journal_id}`);
}
async function handle_force_reset() { async function handle_force_reset() {
if (!confirm('WARNING: This will permanently DELETE the encrypted content and history for this entry and reset it to plain text. This cannot be undone. Proceed?')) { if (!confirm('WARNING: This will permanently DELETE the encrypted content and history for this entry and reset it to plain text. This cannot be undone. Proceed?')) {
return; return;
@@ -358,15 +357,15 @@
<section class="ae_view flex flex-col gap-2 w-full h-full p-1" bind:clientHeight={$ae_loc.iframe_height_modal_body}> <section class="ae_view flex flex-col gap-2 w-full h-full p-1" bind:clientHeight={$ae_loc.iframe_height_modal_body}>
{#if $lq__journal_entry_obj && $lq__journal_obj} {#if $lq__journal_entry_obj && $lq__journal_obj}
<JournalEntry_Header <AeCompJournalEntryHeader
entry={$lq__journal_entry_obj} entry={$lq__journal_entry_obj}
journal={$lq__journal_obj} journal={$lq__journal_obj}
journals_li={$lq__journal_obj_li} journals_li={$lq__journal_obj_li}
bind:tmp_entry_obj bind:tmp_entry_obj
has_changed={has_unsaved_changes} has_changed={has_unsaved_changes}
onSave={update_journal_entry} on_save={() => update_journal_entry()}
onDecrypt={handle_content_decryption} on_decrypt={handle_content_decryption}
onShowConfig={() => show_config_modal = true} on_show_config={() => show_config_modal = true}
{save_status} {save_status}
{log_lvl} {log_lvl}
/> />
@@ -387,48 +386,55 @@
class:bg-yellow-50={$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'}> class:bg-yellow-50={$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'}>
<div class="absolute top-2 right-2 z-10"> <div class="absolute top-2 right-2 z-10">
<JournalEntry_AITools <AeCompJournalEntryAiTools
content={tmp_entry_obj.content} content={tmp_entry_obj.content}
bind:summary={tmp_entry_obj.summary} bind:summary={tmp_entry_obj.summary}
onSave={update_journal_entry} on_save={() => update_journal_entry()}
{log_lvl} {log_lvl}
/> />
</div> </div>
<JournalEntry_Editor <AeCompJournalEntryEditor
entry={$lq__journal_entry_obj} entry={$lq__journal_entry_obj}
journal={$lq__journal_obj} journal={$lq__journal_obj}
bind:tmp_entry_obj bind:tmp_entry_obj
bind:editorView bind:editor_view
has_changed={has_unsaved_changes} has_changed={has_unsaved_changes}
updated_idb={false} updated_idb={false}
onSave={update_journal_entry} on_save={() => update_journal_entry()}
onForceReset={handle_force_reset} on_force_reset={handle_force_reset}
/> />
</section> </section>
<JournalEntry_Metadata entry={tmp_entry_obj as any} /> <AeCompJournalEntryObjFileLi
{log_lvl}
link_to_type="journal_entry"
link_to_id={$lq__journal_entry_obj.journal_entry_id}
{lq__journal_entry_obj}
/>
<AeCompJournalEntryMetadata entry={tmp_entry_obj as any} />
<AeCompModalJournalEntryAppend <AeCompModalJournalEntryAppend
bind:open={show_append_modal} bind:open={show_append_modal}
journal_entry={$lq__journal_entry_obj} journal_entry={$lq__journal_entry_obj}
journal_config={$lq__journal_obj?.cfg_json} journal_config={$lq__journal_obj?.cfg_json}
mode={modal_mode} mode={modal_mode}
onClose={() => (show_append_modal = false)} on_close={() => (show_append_modal = false)}
onUpdate={() => { show_append_modal = false; }} on_update={() => { show_append_modal = false; }}
{log_lvl} {log_lvl}
/> />
<ModalJournalEntryConfig <AeCompModalJournalEntryConfig
bind:show={show_config_modal} bind:show={show_config_modal}
entry={$lq__journal_entry_obj} entry={$lq__journal_entry_obj}
journal={$lq__journal_obj} journal={$lq__journal_obj}
bind:tmp_entry_obj bind:tmp_entry_obj
onSave={update_journal_entry} on_save={() => update_journal_entry()}
onForceReset={handle_force_reset} on_force_reset={handle_force_reset}
{onShowExport} on_show_export={on_show_export}
onAppend={() => { modal_mode = 'append'; show_append_modal = true; }} on_append={() => { modal_mode = 'append'; show_append_modal = true; }}
onPrepend={() => { modal_mode = 'prepend'; show_append_modal = true; }} on_prepend={() => { modal_mode = 'prepend'; show_append_modal = true; }}
{log_lvl} {log_lvl}
/> />
{:else} {:else}

View File

@@ -31,7 +31,7 @@
Tags, Tags,
TypeOutline, TypeOutline,
X X
} from '@lucide/svelte'; } from 'lucide-svelte';
// *** Import Aether specific variables and functions // *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores'; import type { key_val } from '$lib/stores/ae_stores';
@@ -253,10 +253,12 @@
log_lvl: log_lvl log_lvl: log_lvl
}) })
.then((result) => { .then((result) => {
alert('Journal entry cloned successfully!'); if (result?.journal_id_random && result?.journal_entry_id_random) {
goto( alert('Journal entry cloned successfully!');
`/journals/${result.journal_id_random}/entry/${result.journal_entry_id_random}` goto(
); `/journals/${result.journal_id_random}/entry/${result.journal_entry_id_random}`
);
}
}) })
.catch((error) => { .catch((error) => {
console.error( console.error(
@@ -551,8 +553,8 @@
bind:open={show_append_modal} bind:open={show_append_modal}
journal_entry={tmp_entry_obj} journal_entry={tmp_entry_obj}
journal_config={$lq__journal_obj?.cfg_json} journal_config={$lq__journal_obj?.cfg_json}
onClose={handle_modal_close} on_close={handle_modal_close}
onUpdate={handle_modal_update} on_update={handle_modal_update}
{log_lvl} {log_lvl}
/> />
{/if} {/if}

View File

@@ -77,7 +77,7 @@
if (log_lvl) { if (log_lvl) {
console.log( console.log(
`Triggered: $journals_trig.journal_entry_qry: ${$journals_loc.entry.qry__search_text}` `Triggered: $journals_trig.journal_entry_qry: text="${$journals_loc.entry.qry__search_text}" cat="${$journals_loc.entry.qry__category_code}"`
); );
} }
@@ -86,6 +86,7 @@
api_cfg: $ae_api, api_cfg: $ae_api,
journal_id: $lq__journal_obj?.journal_id ?? '', journal_id: $lq__journal_obj?.journal_id ?? '',
qry_str: $journals_loc.entry.qry__search_text, qry_str: $journals_loc.entry.qry__search_text,
qry_category_code: $journals_loc.entry.qry__category_code,
// qry_created_on: null, // qry_created_on: null,
// qry_alert: null, // qry_alert: null,
@@ -100,8 +101,8 @@
log_lvl: log_lvl log_lvl: log_lvl
}); });
if (!$journals_loc.entry.qry__search_text) { if (!$journals_loc.entry.qry__search_text && !$journals_loc.entry.qry__category_code) {
// If search text was cleared or empty, reset to default view (null) // If search text and category were cleared or empty, reset to default view (null)
$journals_sess.entry_li = null; $journals_sess.entry_li = null;
} else if ($journals_prom.load__journal_entry_obj_li && $journals_prom.load__journal_entry_obj_li.length > 0) { } else if ($journals_prom.load__journal_entry_obj_li && $journals_prom.load__journal_entry_obj_li.length > 0) {
$journals_sess.entry_li = $journals_prom.load__journal_entry_obj_li; $journals_sess.entry_li = $journals_prom.load__journal_entry_obj_li;
@@ -146,10 +147,11 @@
<!-- Clear search text button --> <!-- Clear search text button -->
<button <button
type="button" type="button"
class:hidden={!$journals_loc.entry.qry__search_text} class:hidden={!$journals_loc.entry.qry__search_text && !$journals_loc.entry.qry__category_code}
onclick={() => { onclick={() => {
console.log(`TESTING - 1 - Cleared search query: ${$journals_loc.entry.qry__search_text}`); console.log(`TESTING - 1 - Cleared search query: ${$journals_loc.entry.qry__search_text}`);
$journals_loc.entry.qry__search_text = ''; $journals_loc.entry.qry__search_text = '';
$journals_loc.entry.qry__category_code = '';
console.log(`TESTING - 2 - Cleared search query: ${$journals_loc.entry.qry__search_text}`); console.log(`TESTING - 2 - Cleared search query: ${$journals_loc.entry.qry__search_text}`);
$journals_trig.journal_entry_qry = true; $journals_trig.journal_entry_qry = true;
}} }}
@@ -208,8 +210,8 @@
onchange={(event) => { onchange={(event) => {
// WARNING: This will cause pages to reset if the journal entry list is being filtered by category. This is a bug that should be fixed. // WARNING: This will cause pages to reset if the journal entry list is being filtered by category. This is a bug that should be fixed.
$journals_loc.entry.qry__category_code = (event.target as HTMLInputElement).value; $journals_loc.entry.qry__category_code = (event.target as HTMLInputElement).value;
// $journals_loc.entry.qry__category_code = (event.target as HTMLInputElement).value; // Trigger the query search instead of the full list load
$journals_trig.journal_entry_li = true; $journals_trig.journal_entry_qry = true;
console.log('Selected category:', $journals_loc.entry.qry__category_code); console.log('Selected category:', $journals_loc.entry.qry__category_code);
}} }}
title="Select a category for the new journal entry" title="Select a category for the new journal entry"

View File

@@ -3,7 +3,7 @@
import { ae_api } from '$lib/stores/ae_stores'; import { ae_api } from '$lib/stores/ae_stores';
import { journals_slct, journals_loc, journals_trig } from '$lib/ae_journals/ae_journals_stores'; import { journals_slct, journals_loc, journals_trig } from '$lib/ae_journals/ae_journals_stores';
import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { BookType } from '@lucide/svelte'; import { BookType } from 'lucide-svelte';
// Props // Props
let { let {

View File

@@ -38,7 +38,7 @@
CalendarClock, CalendarClock,
MousePointerClick, MousePointerClick,
Zap Zap
} from '@lucide/svelte'; } from 'lucide-svelte';
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { untrack } from 'svelte'; import { untrack } from 'svelte';
@@ -60,18 +60,18 @@
log_lvl?: number; log_lvl?: number;
lq__journal_obj: any; lq__journal_obj: any;
show?: boolean; show?: boolean;
onNewEntry?: () => void; on_new_entry?: () => void;
onShowExport?: () => void; on_show_export?: () => void;
onShowImport?: () => void; on_show_import?: () => void;
} }
let { let {
log_lvl = $bindable(0), log_lvl = $bindable(0),
lq__journal_obj, lq__journal_obj,
show = $bindable(false), show = $bindable(false),
onNewEntry, on_new_entry,
onShowExport, on_show_export,
onShowImport on_show_import
}: Props = $props(); }: Props = $props();
// *** Internal State // *** Internal State
@@ -201,13 +201,13 @@
{#if tab === 'actions'} {#if tab === 'actions'}
<div class="space-y-6 animate-in fade-in duration-300"> <div class="space-y-6 animate-in fade-in duration-300">
<section class="grid grid-cols-1 md:grid-cols-2 gap-4"> <section class="grid grid-cols-1 md:grid-cols-2 gap-4">
<button class="btn variant-soft-secondary w-full py-4 text-lg font-bold" onclick={() => { show = false; onNewEntry?.(); }}> <button class="btn variant-soft-secondary w-full py-4 text-lg font-bold" onclick={() => { show = false; on_new_entry?.(); }}>
<FilePlus size="1.5em" class="mr-2"/> New Journal Entry <FilePlus size="1.5em" class="mr-2"/> New Journal Entry
</button> </button>
<button class="btn variant-soft-surface w-full py-4" onclick={() => { show = false; onShowExport?.(); }}> <button class="btn variant-soft-surface w-full py-4" onclick={() => { show = false; on_show_export?.(); }}>
<FileDown size="1.5em" class="mr-2"/> Export Entries <FileDown size="1.5em" class="mr-2"/> Export Entries
</button> </button>
<button class="btn variant-soft-surface w-full py-4" onclick={() => { show = false; onShowImport?.(); }}> <button class="btn variant-soft-surface w-full py-4" onclick={() => { show = false; on_show_import?.(); }}>
<FileUp size="1.5em" class="mr-2"/> Import Entries <FileUp size="1.5em" class="mr-2"/> Import Entries
</button> </button>
</section> </section>

View File

@@ -3,7 +3,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
// *** Import other supporting libraries // *** Import other supporting libraries
import { BookPlus, BookOpenText, FilePlus, Menu, Pencil, FileDown, FileUp, Settings } from '@lucide/svelte'; import { BookPlus, BookOpenText, FilePlus, Menu, Pencil, FileDown, FileUp, Settings } from 'lucide-svelte';
// *** Import Aether specific variables and functions // *** Import Aether specific variables and functions
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
@@ -30,11 +30,11 @@
log_lvl?: number; log_lvl?: number;
lq__journal_obj: any; lq__journal_obj: any;
lq__journal_entry_obj_li: any; lq__journal_entry_obj_li: any;
onShowExport?: () => void; on_show_export?: () => void;
onShowImport?: () => void; on_show_import?: () => void;
} }
let { log_lvl = 0, lq__journal_obj, lq__journal_entry_obj_li, onShowExport, onShowImport }: Props = $props(); let { log_lvl = 0, lq__journal_obj, lq__journal_entry_obj_li, on_show_export, on_show_import }: Props = $props();
// let ae_promises: key_val = {}; // let ae_promises: key_val = {};
// let ae_tmp: key_val = {}; // let ae_tmp: key_val = {};
@@ -232,7 +232,7 @@
{log_lvl} {log_lvl}
{lq__journal_obj} {lq__journal_obj}
show={$journals_sess.show__modal_edit__journal_obj} show={$journals_sess.show__modal_edit__journal_obj}
onNewEntry={handle_new_entry} on_new_entry={handle_new_entry}
onShowExport={onShowExport} on_show_export={on_show_export}
onShowImport={onShowImport} on_show_import={on_show_import}
/> />

View File

@@ -5,7 +5,7 @@
* Layout: Responsive Grid (1 col mobile, 2 col tablet, 3 col desktop) * Layout: Responsive Grid (1 col mobile, 2 col tablet, 3 col desktop)
* Style: Tailwind 4 + Skeleton UI Reference Standard * Style: Tailwind 4 + Skeleton UI Reference Standard
*/ */
import { BookOpenText, BookType, Hash, Calendar, Clock } from '@lucide/svelte'; import { BookOpenText, BookType, Hash, Calendar, Clock } from 'lucide-svelte';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
/** /**
* modal_journals_config.svelte * ae_comp__modal_journal_config.svelte
* Standardized Module-level settings for Journals. * Standardized Module-level settings for Journals.
* Fixed Svelte 5 state placement and reactivity loops. * Fixed Svelte 5 state placement and reactivity loops.
*/ */
@@ -17,7 +17,7 @@
MousePointerClick, MousePointerClick,
Database, Database,
Wrench Wrench
} from '@lucide/svelte'; } from 'lucide-svelte';
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { untrack } from 'svelte'; import { untrack } from 'svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { Check, X } from '@lucide/svelte'; import { Check, X } from 'lucide-svelte';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { ae_api } from '$lib/stores/ae_stores'; import { ae_api } from '$lib/stores/ae_stores';
@@ -11,8 +11,8 @@
journal_entry: key_val; journal_entry: key_val;
journal_config: key_val; // The cfg_json from the journal object journal_config: key_val; // The cfg_json from the journal object
mode?: 'append' | 'prepend' | 'auto'; mode?: 'append' | 'prepend' | 'auto';
onClose: () => void; on_close: () => void;
onUpdate: () => void; on_update: () => void;
log_lvl?: number; log_lvl?: number;
} }
@@ -21,8 +21,8 @@
journal_entry, journal_entry,
journal_config, journal_config,
mode = 'auto', mode = 'auto',
onClose, on_close,
onUpdate, on_update,
log_lvl = 0 log_lvl = 0
}: Props = $props(); }: Props = $props();
// Local State // Local State
@@ -107,7 +107,7 @@ async function handle_save() {
if (update_result) { if (update_result) {
// Success // Success
onUpdate(); on_update();
open = false; open = false;
} else { } else {
alert('Failed to update journal entry.'); alert('Failed to update journal entry.');
@@ -185,7 +185,7 @@ async function handle_save() {
</button> </button>
<button <button
type="button" type="button"
onclick={onClose} onclick={on_close}
class="btn preset-tonal-surface border border-surface-500 hover:preset-filled-surface-500 transition" class="btn preset-tonal-surface border border-surface-500 hover:preset-filled-surface-500 transition"
> >
<X class="mr-1" /> <X class="mr-1" />

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
/** /**
* modal_journal_entry_config.svelte * ae_comp__modal_journal_entry_config.svelte
* Standardized Journal Entry-level configuration. * Standardized Journal Entry-level configuration.
*/ */
import { import {
@@ -23,7 +23,7 @@
Plus, Plus,
Minus, Minus,
Zap Zap
} from '@lucide/svelte'; } from 'lucide-svelte';
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores'; import { journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores';
@@ -37,11 +37,11 @@
entry: any; entry: any;
journal: any; journal: any;
tmp_entry_obj: any; // Bindable tmp_entry_obj: any; // Bindable
onSave: () => void; on_save: () => void;
onForceReset?: () => void; on_force_reset?: () => void;
onShowExport?: () => void; on_show_export?: () => void;
onAppend?: () => void; on_append?: () => void;
onPrepend?: () => void; on_prepend?: () => void;
} }
let { let {
@@ -50,11 +50,11 @@
entry, entry,
journal, journal,
tmp_entry_obj = $bindable(), tmp_entry_obj = $bindable(),
onSave, on_save,
onForceReset, on_force_reset,
onShowExport, on_show_export,
onAppend, on_append,
onPrepend on_prepend
}: Props = $props(); }: Props = $props();
let tab: 'actions' | 'meta' | 'security' | 'json' = $state('actions'); let tab: 'actions' | 'meta' | 'security' | 'json' = $state('actions');
@@ -138,13 +138,13 @@
{#if tab === 'actions'} {#if tab === 'actions'}
<div class="space-y-6 animate-in fade-in duration-300"> <div class="space-y-6 animate-in fade-in duration-300">
<section class="grid grid-cols-1 md:grid-cols-2 gap-4"> <section class="grid grid-cols-1 md:grid-cols-2 gap-4">
<button class="btn variant-soft-secondary w-full" onclick={() => { show = false; onPrepend?.(); }}> <button class="btn variant-soft-secondary w-full" onclick={() => { show = false; on_prepend?.(); }}>
<ArrowUpToLine size="1.2em" class="mr-2"/> Prepend Content <ArrowUpToLine size="1.2em" class="mr-2"/> Prepend Content
</button> </button>
<button class="btn variant-soft-secondary w-full" onclick={() => { show = false; onAppend?.(); }}> <button class="btn variant-soft-secondary w-full" onclick={() => { show = false; on_append?.(); }}>
<ArrowDownToLine size="1.2em" class="mr-2"/> Append Content <ArrowDownToLine size="1.2em" class="mr-2"/> Append Content
</button> </button>
<button class="btn variant-soft-surface w-full" onclick={() => { show = false; onShowExport?.(); }}> <button class="btn variant-soft-surface w-full" onclick={() => { show = false; on_show_export?.(); }}>
<FileDown size="1.2em" class="mr-2"/> Export Entry <FileDown size="1.2em" class="mr-2"/> Export Entry
</button> </button>
<button class="btn variant-soft-surface w-full" onclick={() => { /* Clone logic here */ alert('Clone not yet implemented in modal'); }}> <button class="btn variant-soft-surface w-full" onclick={() => { /* Clone logic here */ alert('Clone not yet implemented in modal'); }}>
@@ -158,7 +158,7 @@
{#each journal?.cfg_json?.category_li ?? [] as cat} {#each journal?.cfg_json?.category_li ?? [] as cat}
<button <button
class="btn btn-sm {tmp_entry_obj.category_code === cat.code ? 'variant-filled-primary' : 'variant-soft-primary'}" class="btn btn-sm {tmp_entry_obj.category_code === cat.code ? 'variant-filled-primary' : 'variant-soft-primary'}"
onclick={() => { tmp_entry_obj.category_code = cat.code; handle_update_entry(); onSave(); }} onclick={() => { tmp_entry_obj.category_code = cat.code; handle_update_entry(); on_save(); }}
> >
{cat.name} {cat.name}
</button> </button>
@@ -171,7 +171,7 @@
<div class="space-y-6 animate-in fade-in duration-300"> <div class="space-y-6 animate-in fade-in duration-300">
<label class="label"> <label class="label">
<span class="text-sm font-bold opacity-70">Category</span> <span class="text-sm font-bold opacity-70">Category</span>
<select class="select variant-form-material" bind:value={tmp_entry_obj.category_code} onchange={() => { handle_update_entry(); onSave(); }}> <select class="select variant-form-material" bind:value={tmp_entry_obj.category_code} onchange={() => { handle_update_entry(); on_save(); }}>
<option value="">None</option> <option value="">None</option>
{#each journal?.cfg_json?.category_li ?? [] as cat} {#each journal?.cfg_json?.category_li ?? [] as cat}
<option value={cat.code}>{cat.name}</option> <option value={cat.code}>{cat.name}</option>
@@ -183,21 +183,21 @@
<span class="text-sm font-bold opacity-70">Tags (Comma separated)</span> <span class="text-sm font-bold opacity-70">Tags (Comma separated)</span>
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
<Tag size="1.2em" class="opacity-30" /> <Tag size="1.2em" class="opacity-30" />
<input type="text" bind:value={tmp_entry_obj.tags} class="input variant-form-material grow" placeholder="meeting, urgent, ideas" onchange={() => { handle_update_entry(); onSave(); }} /> <input type="text" bind:value={tmp_entry_obj.tags} class="input variant-form-material grow" placeholder="meeting, urgent, ideas" onchange={() => { handle_update_entry(); on_save(); }} />
</div> </div>
</label> </label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="label"> <label class="label">
<span class="text-sm font-bold opacity-70">Archive On</span> <span class="text-sm font-bold opacity-70">Archive On</span>
<input type="datetime-local" value={normalize_date(tmp_entry_obj.archive_on)} onchange={(e) => { tmp_entry_obj.archive_on = e.currentTarget.value; handle_update_entry(); onSave(); }} class="input variant-form-material" /> <input type="datetime-local" value={normalize_date(tmp_entry_obj.archive_on)} onchange={(e) => { tmp_entry_obj.archive_on = e.currentTarget.value; handle_update_entry(); on_save(); }} class="input variant-form-material" />
</label> </label>
<label class="label"> <label class="label">
<span class="text-sm font-bold opacity-70">Sort Priority</span> <span class="text-sm font-bold opacity-70">Sort Priority</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) - 1; handle_update_entry(); onSave(); }}><Minus size="1em"/></button> <button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) - 1; handle_update_entry(); on_save(); }}><Minus size="1em"/></button>
<span class="font-mono font-bold w-8 text-center">{tmp_entry_obj.sort ?? 0}</span> <span class="font-mono font-bold w-8 text-center">{tmp_entry_obj.sort ?? 0}</span>
<button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) + 1; handle_update_entry(); onSave(); }}><Plus size="1em"/></button> <button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) + 1; handle_update_entry(); on_save(); }}><Plus size="1em"/></button>
</div> </div>
</label> </label>
</div> </div>
@@ -212,21 +212,21 @@
</h2> </h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="flex items-center space-x-3 cursor-pointer p-3 rounded-lg bg-surface-500/5 border border-surface-500/10 transition-colors hover:bg-surface-500/10"> <label class="flex items-center space-x-3 cursor-pointer p-3 rounded-lg bg-surface-500/5 border border-surface-500/10 transition-colors hover:bg-surface-500/10">
<input type="checkbox" bind:checked={tmp_entry_obj.enable} onchange={() => { handle_update_entry(); onSave(); }} class="checkbox checkbox-primary" /> <input type="checkbox" bind:checked={tmp_entry_obj.enable} onchange={() => { handle_update_entry(); on_save(); }} class="checkbox checkbox-primary" />
<div class="flex flex-col"> <div class="flex flex-col">
<span class="font-bold">Enabled</span> <span class="font-bold">Enabled</span>
<span class="text-xs opacity-60">Allow access to this entry</span> <span class="text-xs opacity-60">Allow access to this entry</span>
</div> </div>
</label> </label>
<label class="flex items-center space-x-3 cursor-pointer p-3 rounded-lg bg-surface-500/5 border border-surface-500/10 transition-colors hover:bg-surface-500/10"> <label class="flex items-center space-x-3 cursor-pointer p-3 rounded-lg bg-surface-500/5 border border-surface-500/10 transition-colors hover:bg-surface-500/10">
<input type="checkbox" bind:checked={tmp_entry_obj.hide} onchange={() => { handle_update_entry(); onSave(); }} class="checkbox checkbox-primary" /> <input type="checkbox" bind:checked={tmp_entry_obj.hide} onchange={() => { handle_update_entry(); on_save(); }} class="checkbox checkbox-primary" />
<div class="flex flex-col"> <div class="flex flex-col">
<span class="font-bold">Hidden</span> <span class="font-bold">Hidden</span>
<span class="text-xs opacity-60">Hide from standard lists</span> <span class="text-xs opacity-60">Hide from standard lists</span>
</div> </div>
</label> </label>
<label class="flex items-center space-x-3 cursor-pointer p-3 rounded-lg bg-surface-500/5 border border-surface-500/10 transition-colors hover:bg-surface-500/10"> <label class="flex items-center space-x-3 cursor-pointer p-3 rounded-lg bg-surface-500/5 border border-surface-500/10 transition-colors hover:bg-surface-500/10">
<input type="checkbox" bind:checked={tmp_entry_obj.priority} onchange={() => { handle_update_entry(); onSave(); }} class="checkbox checkbox-primary" /> <input type="checkbox" bind:checked={tmp_entry_obj.priority} onchange={() => { handle_update_entry(); on_save(); }} class="checkbox checkbox-primary" />
<div class="flex flex-col"> <div class="flex flex-col">
<span class="font-bold">Priority Entry</span> <span class="font-bold">Priority Entry</span>
<span class="text-xs opacity-60">Star or pin to top</span> <span class="text-xs opacity-60">Star or pin to top</span>
@@ -238,9 +238,9 @@
<span class="text-xs opacity-60">Manual list position</span> <span class="text-xs opacity-60">Manual list position</span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) - 1; handle_update_entry(); onSave(); }}><Minus size="1em"/></button> <button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) - 1; handle_update_entry(); on_save(); }}><Minus size="1em"/></button>
<span class="font-mono font-bold w-8 text-center text-lg">{tmp_entry_obj.sort ?? 0}</span> <span class="font-mono font-bold w-8 text-center text-lg">{tmp_entry_obj.sort ?? 0}</span>
<button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) + 1; handle_update_entry(); onSave(); }}><Plus size="1em"/></button> <button class="btn-icon btn-icon-sm variant-soft-surface" onclick={() => { tmp_entry_obj.sort = (tmp_entry_obj.sort ?? 0) + 1; handle_update_entry(); on_save(); }}><Plus size="1em"/></button>
</div> </div>
</div> </div>
</div> </div>
@@ -250,7 +250,7 @@
<h4 class="text-xs font-bold uppercase opacity-50">Privacy Flags</h4> <h4 class="text-xs font-bold uppercase opacity-50">Privacy Flags</h4>
<AE_ObjectFlags <AE_ObjectFlags
bind:obj={tmp_entry_obj} bind:obj={tmp_entry_obj}
onToggle={() => { handle_update_entry(); onSave(); }} onToggle={() => { handle_update_entry(); on_save(); }}
hideAlert={journal?.cfg_json?.hide_btn_alert} hideAlert={journal?.cfg_json?.hide_btn_alert}
hidePrivate={journal?.cfg_json?.hide_btn_private} hidePrivate={journal?.cfg_json?.hide_btn_private}
hidePublic={journal?.cfg_json?.hide_btn_public} hidePublic={journal?.cfg_json?.hide_btn_public}
@@ -267,7 +267,7 @@
<RefreshCcw size="1.2em" /> Disaster Recovery <RefreshCcw size="1.2em" /> Disaster Recovery
</h4> </h4>
<p class="text-xs opacity-70 mb-4 italic">If the encryption passcode is lost, the data is unrecoverable. You can force a reset to plain text to reuse this entry ID.</p> <p class="text-xs opacity-70 mb-4 italic">If the encryption passcode is lost, the data is unrecoverable. You can force a reset to plain text to reuse this entry ID.</p>
<button class="btn btn-sm variant-filled-error w-full font-bold" onclick={() => { show = false; onForceReset?.(); }}> <button class="btn btn-sm variant-filled-error w-full font-bold" onclick={() => { show = false; on_force_reset?.(); }}>
Force Reset to Plain Text Force Reset to Plain Text
</button> </button>
</div> </div>

View File

@@ -7,7 +7,7 @@
*/ */
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { Download, Copy, FileJson, FileType, Code, Settings2 } from '@lucide/svelte'; import { Download, Copy, FileJson, FileType, Code, Settings2 } from 'lucide-svelte';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import type { ae_JournalEntry, ae_Journal } 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'; import { EXPORT_TEMPLATES, type ExportTemplate } from '$lib/ae_journals/ae_journals_export_templates';
@@ -16,10 +16,10 @@
open: boolean; open: boolean;
entries: ae_JournalEntry[]; entries: ae_JournalEntry[];
journal?: ae_Journal; journal?: ae_Journal;
onClose: () => void; on_close: () => void;
} }
let { open = $bindable(false), entries = [], journal, onClose }: Props = $props(); let { open = $bindable(false), entries = [], journal, on_close }: Props = $props();
// State // State
let selected_template_id: string = $state('standard_markdown'); let selected_template_id: string = $state('standard_markdown');
@@ -144,7 +144,7 @@
<!-- Actions --> <!-- Actions -->
<div class="modal-action flex justify-between items-center"> <div class="modal-action flex justify-between items-center">
<button class="btn preset-tonal-secondary" onclick={onClose}>Close</button> <button class="btn preset-tonal-secondary" onclick={on_close}>Close</button>
<div class="flex gap-2"> <div class="flex gap-2">
<button class="btn variant-soft-primary" onclick={handle_copy}> <button class="btn variant-soft-primary" onclick={handle_copy}>

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { Upload, FileText, AlertCircle, Check, X, RefreshCw } from '@lucide/svelte'; import { Upload, FileText, AlertCircle, Check, X, RefreshCw } from 'lucide-svelte';
import { PARSERS, type AeJournalEntryInput } from '$lib/ae_journals/ae_journals_parsers'; import { PARSERS, type AeJournalEntryInput } from '$lib/ae_journals/ae_journals_parsers';
import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { ae_api } from '$lib/stores/ae_stores'; import { ae_api } from '$lib/stores/ae_stores';
@@ -8,11 +8,11 @@
interface Props { interface Props {
open: boolean; open: boolean;
onClose: () => void; on_close: () => void;
onImportComplete: () => void; on_import_complete: () => void;
} }
let { open = $bindable(false), onClose, onImportComplete }: Props = $props(); let { open = $bindable(false), on_close, on_import_complete }: Props = $props();
let files: FileList | null = $state(null); let files: FileList | null = $state(null);
let selected_parser: keyof typeof PARSERS = $state('standard'); let selected_parser: keyof typeof PARSERS = $state('standard');
@@ -123,7 +123,7 @@
is_importing = false; is_importing = false;
alert(`Import complete! ${success_count}/${parsed_entries.length} imported.`); alert(`Import complete! ${success_count}/${parsed_entries.length} imported.`);
onImportComplete(); on_import_complete();
open = false; open = false;
} }
</script> </script>
@@ -232,7 +232,7 @@
{/if} {/if}
<div class="modal-action"> <div class="modal-action">
<button class="btn preset-tonal-secondary" onclick={onClose}>Cancel</button> <button class="btn preset-tonal-secondary" onclick={on_close}>Cancel</button>
<button <button
class="btn preset-filled-primary" class="btn preset-filled-primary"
disabled={parsed_entries.length === 0 || is_importing} disabled={parsed_entries.length === 0 || is_importing}