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:
11
TODO.md
11
TODO.md
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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={() => {}}
|
||||
/>
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user