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:**
- [ ] Audit Jitsi meeting integration for connection stability.
- [ ] 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.
3. **Hardening V3 Search:**
- [ ] **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.
---
## 🚀 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)
- [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)

View File

@@ -302,6 +302,7 @@ export async function qry__journal_entry({
api_cfg,
journal_id,
qry_str = null, // Example: 'name:contains:"test"'
qry_category_code = null,
qry_created_on = null, // Example greater than: '2024-10-24'
qry_alert = null,
qry_priority = null,
@@ -325,6 +326,7 @@ export async function qry__journal_entry({
api_cfg: any;
journal_id: any;
qry_str?: null | string;
qry_category_code?: null | string;
qry_created_on?: null | string;
qry_alert?: null | string;
qry_priority?: null | number;
@@ -351,6 +353,10 @@ export async function qry__journal_entry({
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) {
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,
limit,
offset,
params,
log_lvl
})
.then(async function (journal_entry_obj_li_get_result) {

View File

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

View File

@@ -11,7 +11,7 @@
import {
BookPlus, SquareLibrary, Wrench,
FileUp, Loader2, Sparkles
} from '@lucide/svelte';
} from 'lucide-svelte';
// *** Libraries & Stores
import { liveQuery } from 'dexie';
@@ -27,7 +27,7 @@
} from '$lib/ae_journals/ae_journals_stores';
// *** 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 AeCompJournalEntryQuickAdd from './ae_comp__journal_entry_quick_add.svelte';
import AeCompModalJournalImport from './ae_comp__modal_journal_import.svelte';
@@ -212,11 +212,11 @@
{/if}
{#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}
<AeCompModalJournalImport
bind:open={show_import_modal}
onClose={() => (show_import_modal = false)}
onImportComplete={() => {}}
on_close={() => (show_import_modal = false)}
on_import_complete={() => {}}
/>

View File

@@ -60,7 +60,7 @@
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_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 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 AeCompModalJournalExport from '../ae_comp__modal_journal_export.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_tmp: key_val = {};
@@ -163,7 +163,7 @@
let lq__journal_entry_obj_li = $derived(
liveQuery(async () => {
let results;
let results: any[] = [];
// 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) {
@@ -378,11 +378,11 @@
"
> -->
<Journal_view
<AeCompJournalObjIdView
{lq__journal_obj}
{lq__journal_entry_obj_li}
onShowExport={() => show_export_modal = true}
onShowImport={() => show_import_modal = true}
on_show_export={() => show_export_modal = true}
on_show_import={() => show_import_modal = true}
/>
<Journal_entry_obj_li {lq__journal_obj} {lq__journal_entry_obj_li} />
@@ -410,14 +410,14 @@
bind:open={show_export_modal}
entries={$lq__journal_entry_obj_li ?? []}
journal={$lq__journal_obj}
onClose={() => show_export_modal = false}
on_close={() => show_export_modal = false}
/>
<!-- Modal: Bulk Import -->
<AeCompModalJournalImport
bind:open={show_import_modal}
onClose={() => show_import_modal = false}
onImportComplete={handle_import_complete}
on_close={() => show_import_modal = false}
on_import_complete={handle_import_complete}
/>
{:else}
<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} -->
<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} -->
</section>
@@ -326,7 +326,7 @@
bind:open={show_export_modal}
entries={$lq__journal_entry_obj ? [$lq__journal_entry_obj] : []}
journal={$lq__journal_obj}
onClose={() => show_export_modal = false}
on_close={() => show_export_modal = false}
/>
{:else}
<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">
/**
* JournalEntry_AITools.svelte
* ae_comp__journal_entry_ai_tools.svelte
* Journal-specific wrapper for the generic AE_AITools.
* Handles layout/positioning and specific save behavior for journal entries.
*/
@@ -9,14 +9,14 @@
interface Props {
content: string;
summary: string; // Bindable
onSave: () => void;
on_save: () => void;
log_lvl?: number;
}
let {
content,
summary = $bindable(),
onSave,
on_save,
log_lvl = 0
}: Props = $props();
</script>
@@ -27,7 +27,7 @@
bind:summary={summary}
onSave={(newSummary) => {
summary = newSummary;
onSave();
on_save();
}}
{log_lvl}
/>

View File

@@ -1,10 +1,10 @@
<script lang="ts">
/**
* JournalEntry_Editor.svelte
* ae_comp__journal_entry_editor.svelte
* Extracted 2026-01-08 to modularize the massive Journal Entry view.
* 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 { 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';
@@ -14,22 +14,22 @@
entry: ae_JournalEntry;
journal: ae_Journal;
tmp_entry_obj: any; // Bindable
editorView?: any; // Bindable
editor_view?: any; // Bindable
has_changed: boolean;
updated_idb: boolean;
onSave: () => void;
onForceReset?: () => void;
on_save: () => void;
on_force_reset?: () => void;
}
let {
entry,
journal,
tmp_entry_obj = $bindable(),
editorView = $bindable(),
editor_view = $bindable(),
has_changed,
updated_idb,
onSave,
onForceReset
on_save,
on_force_reset
}: Props = $props();
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">
{#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}
<!-- EDIT MODE -->
{#if !tmp_entry_obj?.content && tmp_entry_obj?.content_encrypted}
@@ -55,13 +57,13 @@
{/if}
</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">
<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
type="button"
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
</button>
@@ -74,7 +76,7 @@
<E_app_codemirror_v5
content={tmp_entry_obj?.content ?? ''}
bind:new_content={tmp_entry_obj.content}
bind:editorView
bind:editorView={editor_view}
theme_mode={$ae_loc.theme_mode}
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"
@@ -90,7 +92,7 @@
<!-- Floating Save Button -->
<button
type="button"
onclick={onSave}
onclick={on_save}
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:hidden={!has_changed}
@@ -101,7 +103,7 @@
<!-- Inline Save Button (Mobile/Context) -->
<button
type="button"
onclick={onSave}
onclick={on_save}
disabled={!has_changed}
class="btn variant-filled-warning w-full max-w-96 mt-4"
class:invisible={!has_changed}

View File

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

View File

@@ -1,8 +1,21 @@
<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 type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { api } from '$lib/api/api';
import {
ae_snip,
ae_loc,
@@ -33,320 +46,246 @@
let { log_lvl = 0, link_to_type, link_to_id, lq__journal_entry_obj }: Props = $props();
let ae_promises: key_val = $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 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([]);
// *** State
let ae_promises: Record<string, any> = $state({});
let upload_complete: boolean = $state(false);
// Selection state for "Select Existing" mode
let slct_hosted_file_kv: key_val = $state({});
let slct_hosted_file_id: string | null = $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() {
// hosted_file_kv = lq__journal_entry_obj.data_json.hosted_file_kv;
// slct_hosted_file_obj_li = [... $lq__journal_entry_obj?.data_json?.slct_hosted_file_obj_li];
// hosted_file_kv = {
// ...$lq__journal_entry_obj?.data_json?.hosted_file_kv,
// };
let data_kv: key_val = { data_json: $lq__journal_entry_obj?.data_json ?? {} };
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
async function update_journal_entry(updated_files: any[]) {
let data_kv: key_val = {
linked_li_json: JSON.stringify(updated_files),
// Maintain data_json.hosted_file_kv for backward compatibility
data_json: {
...$lq__journal_entry_obj?.data_json,
hosted_file_kv: Object.fromEntries(
updated_files.map(f => [f.hosted_file_id_random || f.id || f.hosted_file_id, f])
)
}
};
console.log('data_kv', data_kv);
try {
const response = await journals_func.update_ae_obj__journal_entry({
await journals_func.update_ae_obj__journal_entry({
api_cfg: $ae_api,
journal_entry_id: $lq__journal_entry_obj?.journal_entry_id,
data_kv: data_kv,
log_lvl: 1
});
console.log('Journal entry updated successfully:', response);
} catch (error) {
console.error('Error updating journal entry:', error);
alert('Failed to update journal entry.');
console.error('Error updating journal entry files:', error);
}
}
// *** Effects for File Management
// Handle "Select Existing" completion
$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 (log_lvl) {
console.log(`slct_hosted_file_id=${slct_hosted_file_id}`, slct_hosted_file_obj);
}
hosted_file_kv = {
...hosted_file_kv,
[slct_hosted_file_id]: slct_hosted_file_obj
};
const new_file = { ...slct_hosted_file_obj, id: slct_hosted_file_id };
const updated_li = [...unified_file_li, new_file];
slct_hosted_file_id = null;
slct_hosted_file_obj = null;
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);
}
// 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?
update_journal_entry(updated_li);
}
});
// 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>
<section
class:hidden={!$ae_loc.edit_mode}
class="
ae_section journal_entry__hosted_file
border border-gray-200 rounded
p-2 space-y-2
w-full
"
>
<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>
<section class="ae_section journal_entry_files w-full space-y-4 my-2">
<!-- Header -->
<div class="flex items-center justify-between border-b border-surface-500/20 pb-2">
<h3 class="h3 flex items-center gap-2 text-lg font-bold">
<Paperclip size="1.1em" />
Attachments
{#if unified_file_li.length}
<span class="badge variant-soft-surface text-xs">{unified_file_li.length}</span>
{/if}
<input
type="text"
id="file_path"
name="file_path"
value={(slct_hosted_file_obj.file_path ? slct_hosted_file_obj.file_path : '')}
readonly={!$ae_loc.administrator_access}
class="input w-full"
</h3>
{#if $ae_loc.edit_mode}
<button
type="button"
class="btn btn-sm variant-soft-warning font-semibold"
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}
</section>

View File

@@ -3,6 +3,7 @@
* ae_comp__journal_entry_obj_id_view.svelte
* Reference Implementation for Journal Entry View/Edit
* Corrected for V3 API Strictness & Robust Decryption
* Uses strictly snake_case.
*/
// *** Import Svelte core
@@ -20,15 +21,16 @@
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { decrypt_journal_entry } from '$lib/ae_journals/ae_journals_decryption';
import JournalEntry_Editor from './JournalEntry_Editor.svelte';
import JournalEntry_Header from './JournalEntry_Header.svelte';
import JournalEntry_Metadata from './JournalEntry_Metadata.svelte';
import JournalEntry_AITools from './JournalEntry_AITools.svelte';
import AeCompJournalEntryEditor from './ae_comp__journal_entry_editor.svelte';
import AeCompJournalEntryHeader from './ae_comp__journal_entry_header.svelte';
import AeCompJournalEntryMetadata from './ae_comp__journal_entry_metadata.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 ModalJournalEntryConfig from './modal_journal_entry_config.svelte';
import AeCompModalJournalEntryConfig from './ae_comp__modal_journal_entry_config.svelte';
// Icons
import { AlertCircle, XCircle, Loader2 } from '@lucide/svelte';
import { AlertCircle, XCircle, Loader2 } from 'lucide-svelte';
// *** Props
interface Props {
@@ -36,7 +38,7 @@
lq__journal_obj: any;
lq__journal_obj_li: any;
lq__journal_entry_obj: any;
onShowExport?: () => void;
on_show_export?: () => void;
}
let {
@@ -44,11 +46,11 @@
lq__journal_obj,
lq__journal_obj_li,
lq__journal_entry_obj,
onShowExport
on_show_export
}: Props = $props();
// *** State
let editorView: any = $state();
let editor_view: any = $state();
let orig_entry_obj: key_val | null = $state(null);
let tmp_entry_obj: key_val = $state({});
let save_status: 'saved' | 'unsaved' | 'saving' = $state('saved');
@@ -230,13 +232,14 @@
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;
is_processing = true;
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,
content: tmp_entry_obj.content,
history: tmp_entry_obj.history,
@@ -255,19 +258,23 @@
sort: tmp_entry_obj.sort,
group: tmp_entry_obj.group,
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;
if (tmp_entry_obj.private) {
if (tmp_entry_obj.content) {
data_kv.content_encrypted = await ae_util.encrypt_wrapper(tmp_entry_obj.content, decrypt_key);
data_kv.content = null;
// Handle encryption logic for content only on full updates
if (!fields_kv) {
if (tmp_entry_obj.private) {
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 {
@@ -278,8 +285,10 @@
log_lvl: 1
});
// CRITICAL: Sync ORIG after save to clear unsaved changes
orig_entry_obj = deep_copy(tmp_entry_obj);
// Sync ORIG after save to clear unsaved changes flag
if (!fields_kv) {
orig_entry_obj = deep_copy(tmp_entry_obj);
}
save_status = 'saved';
} catch (error) {
console.error('Update failed:', error);
@@ -317,16 +326,6 @@
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() {
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;
@@ -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}>
{#if $lq__journal_entry_obj && $lq__journal_obj}
<JournalEntry_Header
<AeCompJournalEntryHeader
entry={$lq__journal_entry_obj}
journal={$lq__journal_obj}
journals_li={$lq__journal_obj_li}
bind:tmp_entry_obj
has_changed={has_unsaved_changes}
onSave={update_journal_entry}
onDecrypt={handle_content_decryption}
onShowConfig={() => show_config_modal = true}
on_save={() => update_journal_entry()}
on_decrypt={handle_content_decryption}
on_show_config={() => show_config_modal = true}
{save_status}
{log_lvl}
/>
@@ -387,48 +386,55 @@
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">
<JournalEntry_AITools
<AeCompJournalEntryAiTools
content={tmp_entry_obj.content}
bind:summary={tmp_entry_obj.summary}
onSave={update_journal_entry}
on_save={() => update_journal_entry()}
{log_lvl}
/>
</div>
<JournalEntry_Editor
<AeCompJournalEntryEditor
entry={$lq__journal_entry_obj}
journal={$lq__journal_obj}
bind:tmp_entry_obj
bind:editorView
bind:editor_view
has_changed={has_unsaved_changes}
updated_idb={false}
onSave={update_journal_entry}
onForceReset={handle_force_reset}
on_save={() => update_journal_entry()}
on_force_reset={handle_force_reset}
/>
</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
bind:open={show_append_modal}
journal_entry={$lq__journal_entry_obj}
journal_config={$lq__journal_obj?.cfg_json}
mode={modal_mode}
onClose={() => (show_append_modal = false)}
onUpdate={() => { show_append_modal = false; }}
on_close={() => (show_append_modal = false)}
on_update={() => { show_append_modal = false; }}
{log_lvl}
/>
<ModalJournalEntryConfig
<AeCompModalJournalEntryConfig
bind:show={show_config_modal}
entry={$lq__journal_entry_obj}
journal={$lq__journal_obj}
bind:tmp_entry_obj
onSave={update_journal_entry}
onForceReset={handle_force_reset}
{onShowExport}
onAppend={() => { modal_mode = 'append'; show_append_modal = true; }}
onPrepend={() => { modal_mode = 'prepend'; show_append_modal = true; }}
on_save={() => update_journal_entry()}
on_force_reset={handle_force_reset}
on_show_export={on_show_export}
on_append={() => { modal_mode = 'append'; show_append_modal = true; }}
on_prepend={() => { modal_mode = 'prepend'; show_append_modal = true; }}
{log_lvl}
/>
{:else}

View File

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

View File

@@ -77,7 +77,7 @@
if (log_lvl) {
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,
journal_id: $lq__journal_obj?.journal_id ?? '',
qry_str: $journals_loc.entry.qry__search_text,
qry_category_code: $journals_loc.entry.qry__category_code,
// qry_created_on: null,
// qry_alert: null,
@@ -100,8 +101,8 @@
log_lvl: log_lvl
});
if (!$journals_loc.entry.qry__search_text) {
// If search text was cleared or empty, reset to default view (null)
if (!$journals_loc.entry.qry__search_text && !$journals_loc.entry.qry__category_code) {
// If search text and category were cleared or empty, reset to default view (null)
$journals_sess.entry_li = null;
} 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;
@@ -146,10 +147,11 @@
<!-- Clear search text button -->
<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={() => {
console.log(`TESTING - 1 - Cleared search query: ${$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}`);
$journals_trig.journal_entry_qry = true;
}}
@@ -208,8 +210,8 @@
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.
$journals_loc.entry.qry__category_code = (event.target as HTMLInputElement).value;
// $journals_loc.entry.qry__category_code = (event.target as HTMLInputElement).value;
$journals_trig.journal_entry_li = true;
// Trigger the query search instead of the full list load
$journals_trig.journal_entry_qry = true;
console.log('Selected category:', $journals_loc.entry.qry__category_code);
}}
title="Select a category for the new journal entry"

View File

@@ -3,7 +3,7 @@
import { ae_api } from '$lib/stores/ae_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 { BookType } from '@lucide/svelte';
import { BookType } from 'lucide-svelte';
// Props
let {

View File

@@ -38,7 +38,7 @@
CalendarClock,
MousePointerClick,
Zap
} from '@lucide/svelte';
} from 'lucide-svelte';
import { Modal } from 'flowbite-svelte';
import { goto } from '$app/navigation';
import { untrack } from 'svelte';
@@ -60,18 +60,18 @@
log_lvl?: number;
lq__journal_obj: any;
show?: boolean;
onNewEntry?: () => void;
onShowExport?: () => void;
onShowImport?: () => void;
on_new_entry?: () => void;
on_show_export?: () => void;
on_show_import?: () => void;
}
let {
log_lvl = $bindable(0),
lq__journal_obj,
show = $bindable(false),
onNewEntry,
onShowExport,
onShowImport
on_new_entry,
on_show_export,
on_show_import
}: Props = $props();
// *** Internal State
@@ -201,13 +201,13 @@
{#if tab === 'actions'}
<div class="space-y-6 animate-in fade-in duration-300">
<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
</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
</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
</button>
</section>

View File

@@ -3,7 +3,7 @@
import { goto } from '$app/navigation';
// *** 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 { ae_util } from '$lib/ae_utils/ae_utils';
@@ -30,11 +30,11 @@
log_lvl?: number;
lq__journal_obj: any;
lq__journal_entry_obj_li: any;
onShowExport?: () => void;
onShowImport?: () => void;
on_show_export?: () => 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_tmp: key_val = {};
@@ -232,7 +232,7 @@
{log_lvl}
{lq__journal_obj}
show={$journals_sess.show__modal_edit__journal_obj}
onNewEntry={handle_new_entry}
onShowExport={onShowExport}
onShowImport={onShowImport}
on_new_entry={handle_new_entry}
on_show_export={on_show_export}
on_show_import={on_show_import}
/>

View File

@@ -5,7 +5,7 @@
* Layout: Responsive Grid (1 col mobile, 2 col tablet, 3 col desktop)
* 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_loc } from '$lib/stores/ae_stores';

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<script lang="ts">
/**
* modal_journal_entry_config.svelte
* ae_comp__modal_journal_entry_config.svelte
* Standardized Journal Entry-level configuration.
*/
import {
@@ -23,7 +23,7 @@
Plus,
Minus,
Zap
} from '@lucide/svelte';
} from 'lucide-svelte';
import { Modal } from 'flowbite-svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores';
@@ -37,11 +37,11 @@
entry: any;
journal: any;
tmp_entry_obj: any; // Bindable
onSave: () => void;
onForceReset?: () => void;
onShowExport?: () => void;
onAppend?: () => void;
onPrepend?: () => void;
on_save: () => void;
on_force_reset?: () => void;
on_show_export?: () => void;
on_append?: () => void;
on_prepend?: () => void;
}
let {
@@ -50,11 +50,11 @@
entry,
journal,
tmp_entry_obj = $bindable(),
onSave,
onForceReset,
onShowExport,
onAppend,
onPrepend
on_save,
on_force_reset,
on_show_export,
on_append,
on_prepend
}: Props = $props();
let tab: 'actions' | 'meta' | 'security' | 'json' = $state('actions');
@@ -138,13 +138,13 @@
{#if tab === 'actions'}
<div class="space-y-6 animate-in fade-in duration-300">
<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
</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
</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
</button>
<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}
<button
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}
</button>
@@ -171,7 +171,7 @@
<div class="space-y-6 animate-in fade-in duration-300">
<label class="label">
<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>
{#each journal?.cfg_json?.category_li ?? [] as cat}
<option value={cat.code}>{cat.name}</option>
@@ -183,21 +183,21 @@
<span class="text-sm font-bold opacity-70">Tags (Comma separated)</span>
<div class="flex gap-2 items-center">
<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>
</label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="label">
<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 class="label">
<span class="text-sm font-bold opacity-70">Sort Priority</span>
<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>
<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>
</label>
</div>
@@ -212,21 +212,21 @@
</h2>
<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">
<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">
<span class="font-bold">Enabled</span>
<span class="text-xs opacity-60">Allow access to this entry</span>
</div>
</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">
<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">
<span class="font-bold">Hidden</span>
<span class="text-xs opacity-60">Hide from standard lists</span>
</div>
</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">
<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">
<span class="font-bold">Priority Entry</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>
</div>
<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>
<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>
@@ -250,7 +250,7 @@
<h4 class="text-xs font-bold uppercase opacity-50">Privacy Flags</h4>
<AE_ObjectFlags
bind:obj={tmp_entry_obj}
onToggle={() => { handle_update_entry(); onSave(); }}
onToggle={() => { handle_update_entry(); on_save(); }}
hideAlert={journal?.cfg_json?.hide_btn_alert}
hidePrivate={journal?.cfg_json?.hide_btn_private}
hidePublic={journal?.cfg_json?.hide_btn_public}
@@ -267,7 +267,7 @@
<RefreshCcw size="1.2em" /> Disaster Recovery
</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>
<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
</button>
</div>

View File

@@ -7,7 +7,7 @@
*/
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 type { ae_JournalEntry, ae_Journal } from '$lib/types/ae_types';
import { EXPORT_TEMPLATES, type ExportTemplate } from '$lib/ae_journals/ae_journals_export_templates';
@@ -16,10 +16,10 @@
open: boolean;
entries: ae_JournalEntry[];
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
let selected_template_id: string = $state('standard_markdown');
@@ -144,7 +144,7 @@
<!-- Actions -->
<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">
<button class="btn variant-soft-primary" onclick={handle_copy}>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
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 { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { ae_api } from '$lib/stores/ae_stores';
@@ -8,11 +8,11 @@
interface Props {
open: boolean;
onClose: () => void;
onImportComplete: () => void;
on_close: () => 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 selected_parser: keyof typeof PARSERS = $state('standard');
@@ -123,7 +123,7 @@
is_importing = false;
alert(`Import complete! ${success_count}/${parsed_entries.length} imported.`);
onImportComplete();
on_import_complete();
open = false;
}
</script>
@@ -232,7 +232,7 @@
{/if}
<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
class="btn preset-filled-primary"
disabled={parsed_entries.length === 0 || is_importing}