feat(journals): implement Quick Add and unified Append/Prepend shared component
- Created AeCompJournalEntryQuickAdd for high-velocity note creation - Extracted robust append/prepend logic from List View into AeCompModalJournalEntryAppend - Unified List and Detail views to use the shared modal for content manipulation - Added explicit Append/Prepend actions to Journal Entry settings menu - Updated TODO.md and Journals module documentation
This commit is contained in:
4
TODO.md
4
TODO.md
@@ -29,8 +29,8 @@ This is a list of tasks to be completed before the next event/show/conference.
|
|||||||
- [ ] **Error Transparency**: Update backend to return specific SQLAlchemy/Pydantic errors in `meta.details`.
|
- [ ] **Error Transparency**: Update backend to return specific SQLAlchemy/Pydantic errors in `meta.details`.
|
||||||
- [ ] **Automated Source of Truth**: Generate `V3_OBJECT_MODELS.md` automatically in `agents_sync/Aether/`.
|
- [ ] **Automated Source of Truth**: Generate `V3_OBJECT_MODELS.md` automatically in `agents_sync/Aether/`.
|
||||||
- [ ] **Phase 2: UI/UX Excellence**
|
- [ ] **Phase 2: UI/UX Excellence**
|
||||||
- [ ] Implement "Quick Add" for high-velocity entry.
|
- [x] Implement "Quick Add" for high-velocity entry.
|
||||||
- [ ] Add rapid append/prepend functionality to existing entries.
|
- [x] Add rapid append/prepend functionality to existing entries.
|
||||||
- [ ] Ensure full cross-platform responsiveness (Mobile -> Workstation).
|
- [ ] Ensure full cross-platform responsiveness (Mobile -> Workstation).
|
||||||
- [ ] **Phase 3: Content Portability**
|
- [ ] **Phase 3: Content Portability**
|
||||||
- [ ] Implement Structured Markdown import (with metadata).
|
- [ ] Implement Structured Markdown import (with metadata).
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ This document outlines the modernization of the Journals module UI in the Svelte
|
|||||||
- [x] Verify frontend uses V3 API (`ae_journals__journal.ts`).
|
- [x] Verify frontend uses V3 API (`ae_journals__journal.ts`).
|
||||||
|
|
||||||
### Phase 2: Rapid Entry (Active)
|
### Phase 2: Rapid Entry (Active)
|
||||||
- [ ] Create `ae_comp__journal_entry_quick_add.svelte`.
|
- [x] Create `ae_comp__journal_entry_quick_add.svelte`.
|
||||||
- [ ] Integrate Quick Add into `+page.svelte`.
|
- [x] Integrate Quick Add into `+page.svelte`.
|
||||||
- [ ] Update `ae_journals_stores.ts` to manage Quick Add state/visibility.
|
- [ ] Update `ae_journals_stores.ts` to manage Quick Add state/visibility.
|
||||||
|
|
||||||
### Phase 3: Content Manipulation
|
### Phase 3: Content Manipulation
|
||||||
- [ ] Update `JournalEntry_SettingsMenu.svelte` with Append/Prepend actions.
|
- [x] Update `JournalEntry_SettingsMenu.svelte` with Append/Prepend actions.
|
||||||
- [ ] Implement Append/Prepend logic in `JournalEntry_Editor.svelte` or helper functions.
|
- [x] Implement Append/Prepend logic in `ae_comp__journal_entry_obj_id_view.svelte`.
|
||||||
|
|
||||||
### Phase 4: Polish & Security
|
### Phase 4: Polish & Security
|
||||||
- [ ] Audit encryption flow for Quick Added entries.
|
- [ ] Audit encryption flow for Quick Added entries.
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
import Modal_journals_cfg from './modal_journals_config.svelte';
|
import Modal_journals_cfg from './modal_journals_config.svelte';
|
||||||
import Journal_obj_li from './ae_comp__journal_obj_li.svelte';
|
import Journal_obj_li from './ae_comp__journal_obj_li.svelte';
|
||||||
|
import AeCompJournalEntryQuickAdd from './ae_comp__journal_entry_quick_add.svelte';
|
||||||
|
|
||||||
// import Element_data_store from '$lib/element_data_store_v2.svelte';
|
// import Element_data_store from '$lib/element_data_store_v2.svelte';
|
||||||
|
|
||||||
@@ -175,6 +176,11 @@
|
|||||||
{$ae_loc.person.given_name ? `- ${$ae_loc.person.given_name}` : ''}
|
{$ae_loc.person.given_name ? `- ${$ae_loc.person.given_name}` : ''}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
<!-- Quick Add Section -->
|
||||||
|
<div class="w-full max-w-2xl mx-auto px-4 pb-4">
|
||||||
|
<AeCompJournalEntryQuickAdd class="shadow-lg" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-row flex-wrap gap-1 items-center justify-center w-full border-gray-200 border-y-2 py-2"
|
class="flex flex-row flex-wrap gap-1 items-center justify-center w-full border-gray-200 border-y-2 py-2"
|
||||||
class:hidden={!$ae_loc.edit_mode}
|
class:hidden={!$ae_loc.edit_mode}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
onDecrypt: () => void;
|
onDecrypt: () => void;
|
||||||
onDecryptHistory: () => void;
|
onDecryptHistory: () => void;
|
||||||
onChangeJournal: () => void;
|
onChangeJournal: () => void;
|
||||||
|
onAppend?: () => void;
|
||||||
|
onPrepend?: () => void;
|
||||||
log_lvl?: number;
|
log_lvl?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +41,8 @@
|
|||||||
onDecrypt,
|
onDecrypt,
|
||||||
onDecryptHistory,
|
onDecryptHistory,
|
||||||
onChangeJournal,
|
onChangeJournal,
|
||||||
|
onAppend,
|
||||||
|
onPrepend,
|
||||||
log_lvl = 0
|
log_lvl = 0
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
@@ -220,6 +224,8 @@ p-2 md:p-3 rounded-lg shadow-md
|
|||||||
{onSave}
|
{onSave}
|
||||||
{onDecryptHistory}
|
{onDecryptHistory}
|
||||||
{onChangeJournal}
|
{onChangeJournal}
|
||||||
|
{onAppend}
|
||||||
|
{onPrepend}
|
||||||
{log_lvl}
|
{log_lvl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
Eye, EyeOff, ShieldCheck, ShieldMinus,
|
Eye, EyeOff, ShieldCheck, ShieldMinus,
|
||||||
Clock, X, Trash2, Settings, Shapes,
|
Clock, X, Trash2, Settings, Shapes,
|
||||||
Copy, RemoveFormatting, CodeXml, TypeOutline,
|
Copy, RemoveFormatting, CodeXml, TypeOutline,
|
||||||
History, Pencil, PenLine, FileX, SquareLibrary
|
History, Pencil, PenLine, FileX, SquareLibrary,
|
||||||
|
ArrowUpToLine, ArrowDownToLine
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||||
import { journals_slct, journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores';
|
import { journals_slct, journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores';
|
||||||
@@ -26,6 +27,8 @@
|
|||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
onDecryptHistory: () => void;
|
onDecryptHistory: () => void;
|
||||||
onChangeJournal: () => void;
|
onChangeJournal: () => void;
|
||||||
|
onAppend?: () => void;
|
||||||
|
onPrepend?: () => void;
|
||||||
log_lvl?: number;
|
log_lvl?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +40,8 @@
|
|||||||
onSave,
|
onSave,
|
||||||
onDecryptHistory,
|
onDecryptHistory,
|
||||||
onChangeJournal,
|
onChangeJournal,
|
||||||
|
onAppend,
|
||||||
|
onPrepend,
|
||||||
log_lvl = 0
|
log_lvl = 0
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
@@ -83,6 +88,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-4 min-w-[280px]">
|
<div class="space-y-4 min-w-[280px]">
|
||||||
|
<!-- Append/Prepend Actions -->
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<button class="btn btn-sm variant-soft-secondary" onclick={onPrepend} title="Prepend to Note">
|
||||||
|
<ArrowUpToLine size="1.2em" class="mr-1"/> Prepend
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm variant-soft-secondary" onclick={onAppend} title="Append to Note">
|
||||||
|
<ArrowDownToLine size="1.2em" class="mr-1"/> Append
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Category selection -->
|
<!-- Category selection -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Shapes size="1.1em" class="text-surface-500" />
|
<Shapes size="1.1em" class="text-surface-500" />
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
import AE_MetadataFooter from '$lib/ae_elements/AE_MetadataFooter.svelte';
|
import AE_MetadataFooter from '$lib/ae_elements/AE_MetadataFooter.svelte';
|
||||||
import JournalEntry_Editor from './JournalEntry_Editor.svelte';
|
import JournalEntry_Editor from './JournalEntry_Editor.svelte';
|
||||||
import JournalEntry_Header from './JournalEntry_Header.svelte';
|
import JournalEntry_Header from './JournalEntry_Header.svelte';
|
||||||
|
import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte';
|
||||||
|
|
||||||
// *** Props
|
// *** Props
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -486,6 +487,22 @@
|
|||||||
slct_hosted_file_obj = null;
|
slct_hosted_file_obj = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Shared Append / Prepend Modal Logic ---
|
||||||
|
let show_append_modal = $state(false);
|
||||||
|
let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||||
|
|
||||||
|
function handle_append_start() {
|
||||||
|
modal_mode = 'append';
|
||||||
|
show_append_modal = true;
|
||||||
|
$journals_sess.entry.show_menu = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_prepend_start() {
|
||||||
|
modal_mode = 'prepend';
|
||||||
|
show_append_modal = true;
|
||||||
|
$journals_sess.entry.show_menu = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@@ -508,6 +525,8 @@
|
|||||||
onDecrypt={handle_content_decryption}
|
onDecrypt={handle_content_decryption}
|
||||||
onDecryptHistory={handle_history_decryption}
|
onDecryptHistory={handle_history_decryption}
|
||||||
onChangeJournal={change_journal_id}
|
onChangeJournal={change_journal_id}
|
||||||
|
onAppend={handle_append_start}
|
||||||
|
onPrepend={handle_prepend_start}
|
||||||
{log_lvl}
|
{log_lvl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -596,7 +615,18 @@
|
|||||||
|
|
||||||
<AE_MetadataFooter obj={tmp_entry_obj} />
|
<AE_MetadataFooter obj={tmp_entry_obj} />
|
||||||
|
|
||||||
|
<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;
|
||||||
|
updated_obj = true; // Force refresh of the view
|
||||||
|
}}
|
||||||
|
{log_lvl}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<section class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-center w-full">
|
<section class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-center w-full">
|
||||||
<span class="text-lg text-orange-900 dark:text-orange-100">
|
<span class="text-lg text-orange-900 dark:text-orange-100">
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
let { log_lvl = $bindable(0), lq__journal_obj, lq__journal_entry_obj_li }: Props = $props();
|
let { log_lvl = $bindable(0), lq__journal_obj, lq__journal_entry_obj_li }: Props = $props();
|
||||||
|
|
||||||
// *** Import Svelte specific
|
// *** Import Svelte specific
|
||||||
import { goto, invalidate, pushState, replaceState } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { Modal } from 'flowbite-svelte';
|
|
||||||
import {
|
import {
|
||||||
CalendarClock,
|
CalendarClock,
|
||||||
Check,
|
Check,
|
||||||
@@ -17,11 +16,9 @@
|
|||||||
Copy,
|
Copy,
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
FileLock,
|
|
||||||
Files,
|
Files,
|
||||||
Fingerprint,
|
Fingerprint,
|
||||||
Flag,
|
Flag,
|
||||||
FlagOff,
|
|
||||||
Group,
|
Group,
|
||||||
ListPlus,
|
ListPlus,
|
||||||
Lock,
|
Lock,
|
||||||
@@ -39,50 +36,45 @@
|
|||||||
// *** Import Aether specific variables and functions
|
// *** Import Aether specific variables and functions
|
||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||||
import { api } from '$lib/api/api';
|
|
||||||
import {
|
import {
|
||||||
ae_snip,
|
|
||||||
ae_loc,
|
ae_loc,
|
||||||
ae_sess,
|
ae_sess,
|
||||||
ae_api,
|
ae_api,
|
||||||
ae_trig,
|
ae_trig,
|
||||||
slct,
|
slct
|
||||||
slct_trigger
|
|
||||||
} from '$lib/stores/ae_stores';
|
} from '$lib/stores/ae_stores';
|
||||||
import {
|
import {
|
||||||
journals_loc,
|
|
||||||
journals_sess,
|
journals_sess,
|
||||||
journals_slct,
|
journals_slct,
|
||||||
journals_trig
|
journals_trig,
|
||||||
|
journals_loc
|
||||||
} from '$lib/ae_journals/ae_journals_stores';
|
} from '$lib/ae_journals/ae_journals_stores';
|
||||||
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
|
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
|
||||||
|
import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte';
|
||||||
let ae_promises: key_val = $state({});
|
|
||||||
// let ae_tmp: key_val = {};
|
|
||||||
// let ae_triggers: key_val = {};
|
|
||||||
|
|
||||||
let tmp_entry_obj: key_val = $state({});
|
let tmp_entry_obj: key_val = $state({});
|
||||||
let tmp_entry_obj_add_timestamp_header: boolean = $state(true);
|
|
||||||
let tmp_entry_obj_add_timestamp_header_w_day_of_week: boolean = $state(true);
|
// Derived state for modal visibility
|
||||||
let tmp_entry_obj_add_text_header: string = $state('');
|
// We cast to boolean for the prop, but we need to handle the close event to clear the store ID
|
||||||
let tmp_entry_obj_add_text: string = $state('');
|
let show_append_modal = $state(false);
|
||||||
let tmp_entry_obj_changed: boolean = $state(false);
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (tmp_entry_obj_add_text_header.length || tmp_entry_obj_add_text) {
|
// Sync local boolean with store ID presence
|
||||||
tmp_entry_obj_changed = true;
|
show_append_modal = !!$journals_sess.show__modal_append__journal_entry_id;
|
||||||
} else {
|
|
||||||
tmp_entry_obj_changed = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handle_modal_close() {
|
||||||
|
$journals_sess.show__modal_append__journal_entry_id = null;
|
||||||
|
show_append_modal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_modal_update() {
|
||||||
|
handle_modal_close();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="journal_list flex flex-col gap-1 md:gap-2 items-center justify-center w-full">
|
<section class="journal_list flex flex-col gap-1 md:gap-2 items-center justify-center w-full">
|
||||||
{#if $lq__journal_entry_obj_li && $lq__journal_entry_obj_li.length}
|
{#if $lq__journal_entry_obj_li && $lq__journal_entry_obj_li.length}
|
||||||
<!-- <div class="ae_group">
|
|
||||||
<span class="ae_label">Group:</span>
|
|
||||||
<span class="ae_value">{current_group}</span> -->
|
|
||||||
|
|
||||||
{#each $lq__journal_entry_obj_li as journals_journal_entry_obj, index}
|
{#each $lq__journal_entry_obj_li as journals_journal_entry_obj, index}
|
||||||
<div
|
<div
|
||||||
class="
|
class="
|
||||||
@@ -163,11 +155,9 @@
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
class:hidden={$lq__journal_obj?.cfg_json?.hide_copy_plain_md}
|
class:hidden={$lq__journal_obj?.cfg_json?.hide_copy_plain_md}
|
||||||
class="btn btn-sm p-1 preset-tonal-secondary hover:preset-filled-secondary-500 *:hover:inline text-xs lg:text-sm"
|
class="btn btn-sm p-1 preset-tonal-surface hover:preset-filled-secondary-500 *:hover:inline text-xs lg:text-sm"
|
||||||
title="Copy the markdown content"
|
title="Copy the markdown content"
|
||||||
>
|
>
|
||||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
|
||||||
<Copy size="1em" />
|
|
||||||
<RemoveFormatting size="1.25em" />
|
<RemoveFormatting size="1.25em" />
|
||||||
<span class="hidden"> Copy Plaintext Markdown </span>
|
<span class="hidden"> Copy Plaintext Markdown </span>
|
||||||
</button>
|
</button>
|
||||||
@@ -176,8 +166,6 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
// Copy the rendered HTML content to clipboard
|
|
||||||
// const htmlContent = $lq__journal_entry_obj?.content_md_html || '';
|
|
||||||
let htmlContent =
|
let htmlContent =
|
||||||
journals_journal_entry_obj.content_md_html || '';
|
journals_journal_entry_obj.content_md_html || '';
|
||||||
|
|
||||||
@@ -196,11 +184,9 @@
|
|||||||
}}
|
}}
|
||||||
class:hidden={journals_journal_entry_obj.template ||
|
class:hidden={journals_journal_entry_obj.template ||
|
||||||
$lq__journal_obj?.cfg_json?.hide_copy_html}
|
$lq__journal_obj?.cfg_json?.hide_copy_html}
|
||||||
class="btn btn-sm p-1 preset-tonal-secondary hover:preset-filled-secondary-500 *:hover:inline lg:text-xs"
|
class="btn btn-sm p-1 preset-tonal-surface hover:preset-filled-secondary-500 *:hover:inline lg:text-xs"
|
||||||
title="Copy the rendered HTML content"
|
title="Copy the rendered HTML content"
|
||||||
>
|
>
|
||||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
|
||||||
<Copy size="1em" />
|
|
||||||
<CodeXml size="1.25em" />
|
<CodeXml size="1.25em" />
|
||||||
<span class="hidden"> Copy HTML Markup </span>
|
<span class="hidden"> Copy HTML Markup </span>
|
||||||
</button>
|
</button>
|
||||||
@@ -221,10 +207,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the rendered HTML content
|
|
||||||
const htmlContent = element.innerHTML;
|
const htmlContent = element.innerHTML;
|
||||||
|
|
||||||
// Use the Clipboard API to write the HTML content as rich text
|
|
||||||
await navigator.clipboard.write([
|
await navigator.clipboard.write([
|
||||||
new ClipboardItem({
|
new ClipboardItem({
|
||||||
'text/html': new Blob([htmlContent], {
|
'text/html': new Blob([htmlContent], {
|
||||||
@@ -241,10 +224,9 @@
|
|||||||
}}
|
}}
|
||||||
class:hidden={journals_journal_entry_obj.template ||
|
class:hidden={journals_journal_entry_obj.template ||
|
||||||
$lq__journal_obj?.cfg_json?.hide_copy_rich}
|
$lq__journal_obj?.cfg_json?.hide_copy_rich}
|
||||||
class="btn btn-sm p-1 preset-tonal-secondary *:hover:inline lg:text-xs"
|
class="btn btn-sm p-1 preset-tonal-surface hover:preset-filled-secondary-500 *:hover:inline lg:text-xs"
|
||||||
title="Copy the rich text (rendered HTML) content"
|
title="Copy the rich text (rendered HTML) content"
|
||||||
>
|
>
|
||||||
<Copy size="1em" />
|
|
||||||
<TypeOutline size="1.25em" />
|
<TypeOutline size="1.25em" />
|
||||||
<span class="hidden">Copy Rich Text</span>
|
<span class="hidden">Copy Rich Text</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -253,8 +235,6 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
// Clone the journal entry
|
|
||||||
// We only want to clone certain fields from the original journal entry object to avoid conflicts.
|
|
||||||
let data_kv = {
|
let data_kv = {
|
||||||
code: journals_journal_entry_obj.code,
|
code: journals_journal_entry_obj.code,
|
||||||
category_code: journals_journal_entry_obj.category_code,
|
category_code: journals_journal_entry_obj.category_code,
|
||||||
@@ -287,23 +267,17 @@
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
class:hidden={!journals_journal_entry_obj.template}
|
class:hidden={!journals_journal_entry_obj.template}
|
||||||
class="btn btn-sm p-1 preset-tonal-secondary hover:preset-filled-secondary-500 *:hover:inline lg:text-xs"
|
class="btn btn-sm p-1 preset-tonal-surface hover:preset-filled-secondary-500 *:hover:inline lg:text-xs"
|
||||||
title="Clone this journal entry"
|
title="Clone this journal entry"
|
||||||
>
|
>
|
||||||
<!-- class="btn btn-sm variant-soft-surface hover:variant-filled-warning transition py-1 px-2" -->
|
|
||||||
<!-- <Copy strokeWidth="1" /> -->
|
|
||||||
<Copy size="1.25em" />
|
<Copy size="1.25em" />
|
||||||
<span class="hidden md:inline">Clone</span>
|
<span class="hidden md:inline">Clone</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- </span> -->
|
|
||||||
{:else}
|
{:else}
|
||||||
<!-- <span class="flex flex-row flex-wrap gap-1 items-center justify-center"> -->
|
|
||||||
<Lock
|
<Lock
|
||||||
size="1.25em"
|
size="1.25em"
|
||||||
class="mx-1 inline-block text-red-400 dark:text-red-600"
|
class="mx-1 inline-block text-red-400 dark:text-red-600"
|
||||||
/>
|
/>
|
||||||
<!-- <EyeOff size="1.25em" class="mx-1 inline-block text-red-400 dark:text-red-600" /> -->
|
|
||||||
<span class="text-xs text-gray-500 hidden">Private</span>
|
<span class="text-xs text-gray-500 hidden">Private</span>
|
||||||
|
|
||||||
<!-- Button to copy the Markdown version -->
|
<!-- Button to copy the Markdown version -->
|
||||||
@@ -323,16 +297,12 @@
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
class:hidden={$lq__journal_obj?.cfg_json?.hide_copy_encrypted}
|
class:hidden={$lq__journal_obj?.cfg_json?.hide_copy_encrypted}
|
||||||
class="btn btn-sm p-1 preset-tonal-secondary hover:preset-filled-secondary-500 *:hover:inline text-xs lg:text-sm"
|
class="btn btn-sm p-1 preset-tonal-surface hover:preset-filled-secondary-500 *:hover:inline text-xs lg:text-sm"
|
||||||
title="Copy the encrypted content"
|
title="Copy the encrypted content"
|
||||||
>
|
>
|
||||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
|
||||||
<Copy size="1em" />
|
|
||||||
<Fingerprint size="1.25em" />
|
<Fingerprint size="1.25em" />
|
||||||
<span class="hidden"> Copy Encrypted </span>
|
<span class="hidden"> Copy Encrypted </span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- </span> -->
|
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -344,8 +314,7 @@
|
|||||||
class:hidden={!journals_journal_entry_obj?.data_json?.hosted_file_kv}
|
class:hidden={!journals_journal_entry_obj?.data_json?.hosted_file_kv}
|
||||||
>
|
>
|
||||||
<Files class="mx-1 inline-block" />
|
<Files class="mx-1 inline-block" />
|
||||||
<span class="text-xs text-gray-500 hidden md:inline">Linked files:</span
|
<span class="text-xs text-gray-500 hidden md:inline">Linked files:</span>
|
||||||
>
|
|
||||||
<span class="font-semibold text-sm text-gray-500">
|
<span class="font-semibold text-sm text-gray-500">
|
||||||
{journals_journal_entry_obj?.data_json?.hosted_file_kv
|
{journals_journal_entry_obj?.data_json?.hosted_file_kv
|
||||||
? Object.keys(
|
? Object.keys(
|
||||||
@@ -376,11 +345,9 @@
|
|||||||
|
|
||||||
<!-- Category code for journal entry -->
|
<!-- Category code for journal entry -->
|
||||||
{#if journals_journal_entry_obj.category_code}
|
{#if journals_journal_entry_obj.category_code}
|
||||||
<!-- When clicked, this will filter by the category code. -->
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
// 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.
|
|
||||||
if (
|
if (
|
||||||
$journals_loc.filter__category_code ==
|
$journals_loc.filter__category_code ==
|
||||||
journals_journal_entry_obj.category_code
|
journals_journal_entry_obj.category_code
|
||||||
@@ -389,17 +356,10 @@
|
|||||||
} else {
|
} else {
|
||||||
$journals_loc.filter__category_code =
|
$journals_loc.filter__category_code =
|
||||||
journals_journal_entry_obj.category_code;
|
journals_journal_entry_obj.category_code;
|
||||||
// We also want to set the category code used when creating a new journal entry.
|
|
||||||
$journals_loc.qry__category_code =
|
$journals_loc.qry__category_code =
|
||||||
journals_journal_entry_obj.category_code;
|
journals_journal_entry_obj.category_code;
|
||||||
}
|
}
|
||||||
$journals_trig.journal_entry_li = true;
|
$journals_trig.journal_entry_li = true;
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
'$journals_loc.filter__category_code',
|
|
||||||
$journals_loc.filter__category_code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
class:bg-green-100={$journals_loc.filter__category_code ==
|
class:bg-green-100={$journals_loc.filter__category_code ==
|
||||||
journals_journal_entry_obj.category_code}
|
journals_journal_entry_obj.category_code}
|
||||||
@@ -427,7 +387,6 @@
|
|||||||
onclick={() => {
|
onclick={() => {
|
||||||
$journals_sess.show__modal_append__journal_entry_id =
|
$journals_sess.show__modal_append__journal_entry_id =
|
||||||
journals_journal_entry_obj?.id;
|
journals_journal_entry_obj?.id;
|
||||||
// Create a deep copy of the object for editing to prevent direct mutation of the liveQuery result.
|
|
||||||
tmp_entry_obj = JSON.parse(
|
tmp_entry_obj = JSON.parse(
|
||||||
JSON.stringify(journals_journal_entry_obj)
|
JSON.stringify(journals_journal_entry_obj)
|
||||||
);
|
);
|
||||||
@@ -438,13 +397,11 @@
|
|||||||
: 'Prepend to Journal Entry'}
|
: 'Prepend to Journal Entry'}
|
||||||
>
|
>
|
||||||
<ListPlus />
|
<ListPlus />
|
||||||
<!-- Append -->
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if journals_journal_entry_obj.content}
|
{#if journals_journal_entry_obj.content}
|
||||||
<!-- hover:border-gray-500 dark:hover:border-gray-500 -->
|
|
||||||
<div
|
<div
|
||||||
class:hidden={journals_journal_entry_obj.hide ||
|
class:hidden={journals_journal_entry_obj.hide ||
|
||||||
(journals_journal_entry_obj.private &&
|
(journals_journal_entry_obj.private &&
|
||||||
@@ -483,7 +440,6 @@
|
|||||||
{@html journals_journal_entry_obj.content}
|
{@html journals_journal_entry_obj.content}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- This needs to remain hidden for the copy function to work and us not seeing the rendered HTML version. -->
|
|
||||||
<article
|
<article
|
||||||
class="prose hidden"
|
class="prose hidden"
|
||||||
id="rendered_journal_entry_content_{journals_journal_entry_obj.journal_entry_id}"
|
id="rendered_journal_entry_content_{journals_journal_entry_obj.journal_entry_id}"
|
||||||
@@ -497,28 +453,6 @@
|
|||||||
!journals_journal_entry_obj?.original_timezone}
|
!journals_journal_entry_obj?.original_timezone}
|
||||||
class="ae_section journal_entry__entry"
|
class="ae_section journal_entry__entry"
|
||||||
>
|
>
|
||||||
<!-- {#if journals_journal_entry_obj?.description}
|
|
||||||
<div
|
|
||||||
class="journal_entry__description ae_description"
|
|
||||||
>
|
|
||||||
<div class="ae_label journal_entry__description text-sm">Description:</div>
|
|
||||||
<div class="ae_value journal_entry__description">
|
|
||||||
{@html journals_journal_entry_obj?.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
|
|
||||||
<!-- {#if journals_journal_entry_obj?.entry_html}
|
|
||||||
<div
|
|
||||||
class="journal_entry__entry_html ae_entry_html"
|
|
||||||
>
|
|
||||||
<div class="ae_label journal_entry__entry_html text-sm">Content:</div>
|
|
||||||
<div class="ae_value journal_entry__entry_html">
|
|
||||||
{@html journals_journal_entry_obj?.entry_html}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="ae_group"
|
class="ae_group"
|
||||||
class:hidden={!journals_journal_entry_obj?.original_datetime &&
|
class:hidden={!journals_journal_entry_obj?.original_datetime &&
|
||||||
@@ -545,12 +479,6 @@
|
|||||||
<section
|
<section
|
||||||
class="ae_meta mt-2 flex flex-col sm:flex-row gap-2 items-center justify-center text-xs text-gray-500"
|
class="ae_meta mt-2 flex flex-col sm:flex-row gap-2 items-center justify-center text-xs text-gray-500"
|
||||||
>
|
>
|
||||||
<!-- <span
|
|
||||||
class="journal_entry__journal_entry_type"
|
|
||||||
class:hidden={!journals_journal_entry_obj.journal_entry_type}
|
|
||||||
>
|
|
||||||
Type: {journals_journal_entry_obj.journal_entry_type}
|
|
||||||
</span> -->
|
|
||||||
<span
|
<span
|
||||||
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}
|
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}
|
||||||
class="flex flex-row gap-1 items-center justify-center"
|
class="flex flex-row gap-1 items-center justify-center"
|
||||||
@@ -589,8 +517,6 @@
|
|||||||
log_lvl: log_lvl
|
log_lvl: log_lvl
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Optionally, you can provide feedback to the user
|
|
||||||
// alert('Journal entry updated successfully!');
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error updating journal entry:', error);
|
console.error('Error updating journal entry:', error);
|
||||||
@@ -622,298 +548,17 @@
|
|||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<!-- Modal for quick append to Journal Entry -->
|
<!-- Modal for quick append to Journal Entry -->
|
||||||
<!-- This should only have a textarea, Save button, and Cancel button. -->
|
|
||||||
{#if $journals_sess.show__modal_append__journal_entry_id}
|
{#if $journals_sess.show__modal_append__journal_entry_id}
|
||||||
<Modal
|
<AeCompModalJournalEntryAppend
|
||||||
title="{$lq__journal_obj?.cfg_json?.entry_add_text == 'append'
|
bind:open={show_append_modal}
|
||||||
? 'Append to Journal Entry'
|
journal_entry={tmp_entry_obj}
|
||||||
: 'Prepend to Journal Entry'}: {tmp_entry_obj?.name ??
|
journal_config={$lq__journal_obj?.cfg_json}
|
||||||
tmp_entry_obj?.created_on} ({tmp_entry_obj?.journal_entry_id})"
|
onClose={handle_modal_close}
|
||||||
bind:open={$journals_sess.show__modal_append__journal_entry_id}
|
onUpdate={handle_modal_update}
|
||||||
autoclose={false}
|
{log_lvl}
|
||||||
placement="top-center"
|
/>
|
||||||
size="xl"
|
|
||||||
class="
|
|
||||||
top-center
|
|
||||||
bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200
|
|
||||||
rounded-lg border-gray-200 dark:border-gray-700
|
|
||||||
divide-gray-200 dark:divide-gray-700
|
|
||||||
shadow-md relative
|
|
||||||
flex flex-col gap-1 mx-auto w-full"
|
|
||||||
>
|
|
||||||
<div class="modal">
|
|
||||||
<div class="modal-box">
|
|
||||||
<!-- <h3 class="font-bold text-lg">Edit Journal</h3> -->
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<!-- Checkbox for using the timestamp as the Markdown header. This will be pre-pended to any text header given, if any. -->
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="append_timestamp_header"
|
|
||||||
bind:checked={tmp_entry_obj_add_timestamp_header}
|
|
||||||
class:border-orange-200={$ae_loc.edit_mode}
|
|
||||||
class:hover:border-orange-500={$ae_loc.edit_mode}
|
|
||||||
class="
|
|
||||||
p-2
|
|
||||||
bg-slate-100 text-gray-900
|
|
||||||
dark:bg-slate-900 dark:text-gray-100
|
|
||||||
shadow-lg rounded-lg
|
|
||||||
border border-gray-200 dark:border-gray-700
|
|
||||||
hover:border-gray-500 dark:hover:border-gray-500
|
|
||||||
inline-block
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
for="append_timestamp_header"
|
|
||||||
class:border-orange-200={$ae_loc.edit_mode}
|
|
||||||
class:hover:border-orange-500={$ae_loc.edit_mode}
|
|
||||||
class="
|
|
||||||
p-2
|
|
||||||
bg-slate-100 text-gray-900
|
|
||||||
dark:bg-slate-900 dark:text-gray-100
|
|
||||||
shadow-lg rounded-lg
|
|
||||||
border border-gray-200 dark:border-gray-700
|
|
||||||
hover:border-gray-500 dark:hover:border-gray-500
|
|
||||||
|
|
||||||
inline-block
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Use timestamp as Markdown header
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="append_timestamp_header_w_day_of_week"
|
|
||||||
bind:checked={tmp_entry_obj_add_timestamp_header_w_day_of_week}
|
|
||||||
class:border-orange-200={$ae_loc.edit_mode}
|
|
||||||
class:hover:border-orange-500={$ae_loc.edit_mode}
|
|
||||||
class="
|
|
||||||
p-2
|
|
||||||
bg-slate-100 text-gray-900
|
|
||||||
dark:bg-slate-900 dark:text-gray-100
|
|
||||||
shadow-lg rounded-lg
|
|
||||||
border border-gray-200 dark:border-gray-700
|
|
||||||
hover:border-gray-500 dark:hover:border-gray-500
|
|
||||||
inline-block
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
for="append_timestamp_header_w_day_of_week"
|
|
||||||
class:border-orange-200={$ae_loc.edit_mode}
|
|
||||||
class:hover:border-orange-500={$ae_loc.edit_mode}
|
|
||||||
class="
|
|
||||||
p-2
|
|
||||||
bg-slate-100 text-gray-900
|
|
||||||
dark:bg-slate-900 dark:text-gray-100
|
|
||||||
shadow-lg rounded-lg
|
|
||||||
border border-gray-200 dark:border-gray-700
|
|
||||||
hover:border-gray-500 dark:hover:border-gray-500
|
|
||||||
|
|
||||||
inline-block
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Include day of week
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Markdown header for appended content"
|
|
||||||
bind:value={tmp_entry_obj_add_text_header}
|
|
||||||
onchange={() => {
|
|
||||||
// tmp_entry_obj_changed = true;
|
|
||||||
}}
|
|
||||||
class:border-orange-200={$ae_loc.edit_mode}
|
|
||||||
class:hover:border-orange-500={$ae_loc.edit_mode}
|
|
||||||
class="
|
|
||||||
grow min-h-12 h-full w-full
|
|
||||||
p-2
|
|
||||||
bg-slate-100 text-gray-900
|
|
||||||
dark:bg-slate-900 dark:text-gray-100
|
|
||||||
shadow-lg rounded-lg
|
|
||||||
border border-gray-200 dark:border-gray-700
|
|
||||||
hover:border-gray-500 dark:hover:border-gray-500
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<textarea
|
|
||||||
bind:value={tmp_entry_obj_add_text}
|
|
||||||
onchange={() => {
|
|
||||||
// tmp_entry_obj_changed = true;
|
|
||||||
}}
|
|
||||||
class:border-orange-200={$ae_loc.edit_mode}
|
|
||||||
class:hover:border-orange-500={$ae_loc.edit_mode}
|
|
||||||
class="
|
|
||||||
grow min-h-48 h-full w-full
|
|
||||||
p-2
|
|
||||||
bg-slate-100 text-gray-900
|
|
||||||
dark:bg-slate-900 dark:text-gray-100
|
|
||||||
shadow-lg rounded-lg
|
|
||||||
border border-gray-200 dark:border-gray-700
|
|
||||||
hover:border-gray-500 dark:hover:border-gray-500
|
|
||||||
"
|
|
||||||
placeholder="Append to journal entry content..."
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-action">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={!tmp_entry_obj_changed}
|
|
||||||
onclick={async () => {
|
|
||||||
let current_entry_content = tmp_entry_obj?.content;
|
|
||||||
let add_content = ''; // tmp_entry_obj?.content + '\n\n';
|
|
||||||
let new_content = current_entry_content;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Append or Prepend? ${$lq__journal_obj?.cfg_json?.entry_add_text}`
|
|
||||||
);
|
|
||||||
console.log(tmp_entry_obj_add_text_header);
|
|
||||||
console.log(tmp_entry_obj_add_text);
|
|
||||||
|
|
||||||
// if ($lq__journal_obj?.cfg_json?.entry_add_text == 'prepend') {
|
|
||||||
if (
|
|
||||||
tmp_entry_obj_add_timestamp_header &&
|
|
||||||
tmp_entry_obj_add_text_header
|
|
||||||
) {
|
|
||||||
add_content =
|
|
||||||
'## ' +
|
|
||||||
ae_util.iso_datetime_formatter(
|
|
||||||
new Date(),
|
|
||||||
'datetime_iso_12_no_seconds'
|
|
||||||
) +
|
|
||||||
(tmp_entry_obj_add_timestamp_header_w_day_of_week
|
|
||||||
? ' (' +
|
|
||||||
ae_util.iso_datetime_formatter(
|
|
||||||
new Date(),
|
|
||||||
'week_long'
|
|
||||||
) +
|
|
||||||
')'
|
|
||||||
: '') +
|
|
||||||
' - ' +
|
|
||||||
tmp_entry_obj_add_text_header.trim() +
|
|
||||||
'\n' +
|
|
||||||
tmp_entry_obj_add_text.trim() +
|
|
||||||
'\n\n';
|
|
||||||
} else if (tmp_entry_obj_add_timestamp_header) {
|
|
||||||
add_content =
|
|
||||||
'## ' +
|
|
||||||
ae_util.iso_datetime_formatter(
|
|
||||||
new Date(),
|
|
||||||
'datetime_iso_12_no_seconds'
|
|
||||||
) +
|
|
||||||
(tmp_entry_obj_add_timestamp_header_w_day_of_week
|
|
||||||
? ' (' +
|
|
||||||
ae_util.iso_datetime_formatter(
|
|
||||||
new Date(),
|
|
||||||
'week_long'
|
|
||||||
) +
|
|
||||||
')'
|
|
||||||
: '') +
|
|
||||||
'\n' +
|
|
||||||
tmp_entry_obj_add_text.trim() +
|
|
||||||
'\n\n';
|
|
||||||
} else if (tmp_entry_obj_add_text_header) {
|
|
||||||
add_content =
|
|
||||||
'## ' +
|
|
||||||
tmp_entry_obj_add_text_header.trim() +
|
|
||||||
(tmp_entry_obj_add_timestamp_header_w_day_of_week
|
|
||||||
? ' (' +
|
|
||||||
ae_util.iso_datetime_formatter(
|
|
||||||
new Date(),
|
|
||||||
'week_long'
|
|
||||||
) +
|
|
||||||
')'
|
|
||||||
: '') +
|
|
||||||
'\n' +
|
|
||||||
tmp_entry_obj_add_text.trim() +
|
|
||||||
'\n\n';
|
|
||||||
}
|
|
||||||
// } else {
|
|
||||||
// if (tmp_entry_obj_add_timestamp_header && tmp_entry_obj_add_text_header) {
|
|
||||||
// new_content = new_content + '## ' + ae_util.iso_datetime_formatter(new Date(), 'datetime_iso_12_no_seconds') + ' - ' + tmp_entry_obj_add_text_header.trim() + '\n';
|
|
||||||
// } else if (tmp_entry_obj_add_timestamp_header) {
|
|
||||||
// new_content = new_content + '## ' + ae_util.iso_datetime_formatter(new Date(), 'datetime_iso_12_no_seconds') + '\n';
|
|
||||||
// } else if (tmp_entry_obj_add_text_header) {
|
|
||||||
// new_content = new_content + '## ' + tmp_entry_obj_add_text_header.trim() + '\n';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (tmp_entry_obj_add_text_header) {
|
|
||||||
// new_content = new_content + '# ' + tmp_entry_obj_add_text_header.trim() + '\n';
|
|
||||||
// }
|
|
||||||
// if (tmp_entry_obj_add_text) {
|
|
||||||
if ($lq__journal_obj?.cfg_json?.entry_add_text == 'prepend') {
|
|
||||||
// Handle prepending content
|
|
||||||
new_content = add_content + new_content;
|
|
||||||
} else {
|
|
||||||
// Handle appending content
|
|
||||||
// Add one more line break before the content to be appended
|
|
||||||
new_content = new_content + '\n' + add_content;
|
|
||||||
}
|
|
||||||
// new_content = new_content + tmp_entry_obj_add_text.trim();
|
|
||||||
// }
|
|
||||||
new_content = new_content.trim() + '\n';
|
|
||||||
|
|
||||||
let data_kv = {
|
|
||||||
content: new_content
|
|
||||||
};
|
|
||||||
let update_result = await journals_func
|
|
||||||
.update_ae_obj__journal_entry({
|
|
||||||
api_cfg: $ae_api,
|
|
||||||
journal_entry_id: tmp_entry_obj?.journal_entry_id,
|
|
||||||
data_kv: data_kv,
|
|
||||||
log_lvl: log_lvl
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
// Optionally, you can provide feedback to the user
|
|
||||||
// alert('Journal entry updated successfully!');
|
|
||||||
return result;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error updating journal entry:', error);
|
|
||||||
alert('Failed to update journal entry.');
|
|
||||||
})
|
|
||||||
.finally(() => {});
|
|
||||||
if (update_result) {
|
|
||||||
// alert('Journal entry updated successfully!');
|
|
||||||
// Reset the form
|
|
||||||
tmp_entry_obj_add_text_header = '';
|
|
||||||
tmp_entry_obj_add_text = '';
|
|
||||||
tmp_entry_obj_changed = false;
|
|
||||||
$journals_sess.show__modal_append__journal_entry_id = false;
|
|
||||||
} else {
|
|
||||||
alert('Failed to update journal entry.');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
class:preset-filled-error-500={tmp_entry_obj_changed}
|
|
||||||
class="
|
|
||||||
btn btn-sm md:btn-md lg:btn-lg
|
|
||||||
min-w-72 w-full lg:min-w-96
|
|
||||||
max-w-96
|
|
||||||
hover:variant-outline-success
|
|
||||||
hover:preset-filled-success-500
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<Check />
|
|
||||||
Update
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={$journals_sess.show__modal_append__journal_entry_id ??
|
|
||||||
false}
|
|
||||||
class="btn preset-tonal-surface border border-surface-500 hover:preset-filled-surface-500 transition"
|
|
||||||
>
|
|
||||||
<X />
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p>No journal entry available to show.</p>
|
<p>No journal entry available to show.</p>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
<!-- {/if} -->
|
|
||||||
|
|||||||
106
src/routes/journals/ae_comp__journal_entry_quick_add.svelte
Normal file
106
src/routes/journals/ae_comp__journal_entry_quick_add.svelte
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { api } from '$lib/api/api';
|
||||||
|
import { ae_api } from '$lib/stores/ae_stores';
|
||||||
|
import { journals_slct, journals_loc, journals_trig } from '$lib/ae_journals/ae_journals_stores';
|
||||||
|
|
||||||
|
// Props
|
||||||
|
let {
|
||||||
|
class: className = "",
|
||||||
|
placeholder = "Type your quick note... (First line = Title)"
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
// State
|
||||||
|
let note_content = $state("");
|
||||||
|
let is_submitting = $state(false);
|
||||||
|
|
||||||
|
// Derived
|
||||||
|
// Determine target journal: Selected > Default Preference > First Available?
|
||||||
|
let target_journal_id = $derived($journals_slct.journal_id || $journals_loc.qry__journal_id);
|
||||||
|
|
||||||
|
async function handle_submit() {
|
||||||
|
if (!note_content.trim()) return;
|
||||||
|
if (!target_journal_id) {
|
||||||
|
alert("No journal selected!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_submitting = true;
|
||||||
|
|
||||||
|
const lines = note_content.trim().split('\n');
|
||||||
|
let name = lines[0].substring(0, 100);
|
||||||
|
if (lines[0].length > 100) name += "...";
|
||||||
|
|
||||||
|
// If content is just one line, name is content, content is content.
|
||||||
|
// If multiple lines, name is line 0, content is full text.
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
journal_id: target_journal_id,
|
||||||
|
name: name,
|
||||||
|
content: note_content,
|
||||||
|
type_code: 'note', // Default to note
|
||||||
|
// created_on: handled by backend usually, or we can send ISO
|
||||||
|
enabled: true,
|
||||||
|
hidden: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the store value directly via $ prefix
|
||||||
|
const api_cfg = $ae_api;
|
||||||
|
|
||||||
|
const res = await api.create_ae_obj_v3({
|
||||||
|
api_cfg: api_cfg,
|
||||||
|
obj_type: 'journal_entry',
|
||||||
|
fields: payload
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
note_content = "";
|
||||||
|
// Trigger refresh
|
||||||
|
$journals_trig.journal_entry_li = true;
|
||||||
|
} else {
|
||||||
|
alert("Failed to create note.");
|
||||||
|
}
|
||||||
|
|
||||||
|
is_submitting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_keydown(e: KeyboardEvent) {
|
||||||
|
if (e.ctrlKey && e.key === 'Enter') {
|
||||||
|
handle_submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="card p-4 space-y-4 variant-filled-surface {className}">
|
||||||
|
<header class="flex justify-between items-center">
|
||||||
|
<h3 class="h3">Quick Add</h3>
|
||||||
|
{#if !target_journal_id}
|
||||||
|
<span class="badge variant-filled-error">No Journal Selected</span>
|
||||||
|
{/if}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
class="textarea"
|
||||||
|
rows="3"
|
||||||
|
bind:value={note_content}
|
||||||
|
{placeholder}
|
||||||
|
onkeydown={handle_keydown}
|
||||||
|
disabled={is_submitting}
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
<div class="flex justify-end space-x-2">
|
||||||
|
<button
|
||||||
|
class="btn variant-ghost-surface"
|
||||||
|
onclick={() => note_content = ""}
|
||||||
|
disabled={is_submitting || note_content.length === 0}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn variant-filled-primary"
|
||||||
|
onclick={handle_submit}
|
||||||
|
disabled={is_submitting || !target_journal_id || note_content.length === 0}
|
||||||
|
>
|
||||||
|
{#if is_submitting}Saving...{:else}Add Note{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
197
src/routes/journals/ae_comp__modal_journal_entry_append.svelte
Normal file
197
src/routes/journals/ae_comp__modal_journal_entry_append.svelte
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Modal } from 'flowbite-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';
|
||||||
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
journal_entry: key_val;
|
||||||
|
journal_config: key_val; // The cfg_json from the journal object
|
||||||
|
mode?: 'append' | 'prepend' | 'auto';
|
||||||
|
onClose: () => void;
|
||||||
|
onUpdate: () => void;
|
||||||
|
log_lvl?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
open = $bindable(false),
|
||||||
|
journal_entry,
|
||||||
|
journal_config,
|
||||||
|
mode = 'auto',
|
||||||
|
onClose,
|
||||||
|
onUpdate,
|
||||||
|
log_lvl = 0
|
||||||
|
}: Props = $props();
|
||||||
|
// Local State
|
||||||
|
let tmp_entry_obj: key_val = $state({});
|
||||||
|
|
||||||
|
// Header Options
|
||||||
|
let add_timestamp_header: boolean = $state(true);
|
||||||
|
let add_timestamp_header_w_day_of_week: boolean = $state(true);
|
||||||
|
let add_text_header: string = $state('');
|
||||||
|
let add_text: string = $state('');
|
||||||
|
|
||||||
|
// Change detection
|
||||||
|
let has_changes: boolean = $derived(add_text_header.length > 0 || add_text.length > 0);
|
||||||
|
|
||||||
|
// Initialize tmp object when entry changes or modal opens
|
||||||
|
$effect(() => {
|
||||||
|
if (open && journal_entry) {
|
||||||
|
tmp_entry_obj = JSON.parse(JSON.stringify(journal_entry));
|
||||||
|
// Reset fields
|
||||||
|
add_text_header = '';
|
||||||
|
add_text = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handle_save() {
|
||||||
|
let current_entry_content = tmp_entry_obj?.content || '';
|
||||||
|
let add_content = '';
|
||||||
|
let new_content = current_entry_content;
|
||||||
|
|
||||||
|
// Construct the header/content to add (Following original logic)
|
||||||
|
let timestamp_str = ae_util.iso_datetime_formatter(
|
||||||
|
new Date(),
|
||||||
|
'datetime_iso_12_no_seconds'
|
||||||
|
);
|
||||||
|
let day_of_week_str = add_timestamp_header_w_day_of_week
|
||||||
|
? ' (' + ae_util.iso_datetime_formatter(new Date(), 'week_long') + ')'
|
||||||
|
: '';
|
||||||
|
|
||||||
|
if (add_timestamp_header && add_text_header) {
|
||||||
|
add_content =
|
||||||
|
'## ' +
|
||||||
|
timestamp_str +
|
||||||
|
day_of_week_str +
|
||||||
|
' - ' +
|
||||||
|
add_text_header.trim() +
|
||||||
|
'\n' +
|
||||||
|
add_text.trim() +
|
||||||
|
'\n\n';
|
||||||
|
} else if (add_timestamp_header) {
|
||||||
|
add_content = '## ' + timestamp_str + day_of_week_str + '\n' + add_text.trim() + '\n\n';
|
||||||
|
} else if (add_text_header) {
|
||||||
|
add_content =
|
||||||
|
'## ' + add_text_header.trim() + day_of_week_str + '\n' + add_text.trim() + '\n\n';
|
||||||
|
} else {
|
||||||
|
add_content = add_text.trim() + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine Append or Prepend
|
||||||
|
let effective_mode = mode;
|
||||||
|
if (effective_mode === 'auto') {
|
||||||
|
effective_mode = journal_config?.entry_add_text || 'append';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effective_mode == 'prepend') {
|
||||||
|
new_content = add_content + new_content;
|
||||||
|
} else {
|
||||||
|
// Append
|
||||||
|
new_content = new_content.trim() + '\n\n' + add_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_content = new_content.trim() + '\n';
|
||||||
|
|
||||||
|
let data_kv = { content: new_content };
|
||||||
|
|
||||||
|
try {
|
||||||
|
let update_result = await journals_func.update_ae_obj__journal_entry({
|
||||||
|
api_cfg: $ae_api,
|
||||||
|
journal_entry_id: tmp_entry_obj?.journal_entry_id,
|
||||||
|
data_kv: data_kv,
|
||||||
|
log_lvl: log_lvl
|
||||||
|
});
|
||||||
|
|
||||||
|
if (update_result) {
|
||||||
|
// Success
|
||||||
|
onUpdate();
|
||||||
|
open = false;
|
||||||
|
} else {
|
||||||
|
alert('Failed to update journal entry.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating journal entry:', error);
|
||||||
|
alert('Failed to update journal entry.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="{(mode === 'auto' ? journal_config?.entry_add_text : mode) == 'append' ? 'Append to' : 'Prepend to'} Journal Entry: {journal_entry?.name ?? journal_entry?.created_on}"
|
||||||
|
bind:open={open}
|
||||||
|
autoclose={false}
|
||||||
|
placement="top-center"
|
||||||
|
size="xl"
|
||||||
|
class="top-center bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative flex flex-col gap-1 mx-auto w-full"
|
||||||
|
>
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<!-- Checkbox Options -->
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="append_timestamp_header"
|
||||||
|
bind:checked={add_timestamp_header}
|
||||||
|
class="p-2 bg-slate-100 text-gray-900 dark:bg-slate-900 dark:text-gray-100 shadow-lg rounded-lg border border-gray-200 dark:border-gray-700 hover:border-gray-500 dark:hover:border-gray-500 inline-block"
|
||||||
|
/>
|
||||||
|
<label for="append_timestamp_header" class="p-2 inline-block">
|
||||||
|
Use timestamp as Markdown header
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="append_timestamp_header_w_day_of_week"
|
||||||
|
bind:checked={add_timestamp_header_w_day_of_week}
|
||||||
|
class="p-2 bg-slate-100 text-gray-900 dark:bg-slate-900 dark:text-gray-100 shadow-lg rounded-lg border border-gray-200 dark:border-gray-700 hover:border-gray-500 dark:hover:border-gray-500 inline-block"
|
||||||
|
/>
|
||||||
|
<label for="append_timestamp_header_w_day_of_week" class="p-2 inline-block">
|
||||||
|
Include day of week
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Text Header Input -->
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Markdown header for content (Optional)"
|
||||||
|
bind:value={add_text_header}
|
||||||
|
class="grow min-h-12 h-full w-full p-2 bg-slate-100 text-gray-900 dark:bg-slate-900 dark:text-gray-100 shadow-lg rounded-lg border border-gray-200 dark:border-gray-700 hover:border-gray-500 dark:hover:border-gray-500"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Main Content Area -->
|
||||||
|
<textarea
|
||||||
|
bind:value={add_text}
|
||||||
|
class="grow min-h-48 h-full w-full p-2 bg-slate-100 text-gray-900 dark:bg-slate-900 dark:text-gray-100 shadow-lg rounded-lg border border-gray-200 dark:border-gray-700 hover:border-gray-500 dark:hover:border-gray-500"
|
||||||
|
placeholder="Content to {mode === 'auto'
|
||||||
|
? journal_config?.entry_add_text
|
||||||
|
: mode}...">
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-action flex justify-end gap-2 mt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!has_changes}
|
||||||
|
onclick={handle_save}
|
||||||
|
class="btn btn-sm md:btn-md lg:btn-lg min-w-32 hover:variant-outline-success hover:preset-filled-success-500"
|
||||||
|
class:preset-filled-primary-500={has_changes}
|
||||||
|
class:preset-filled-surface-500={!has_changes}
|
||||||
|
>
|
||||||
|
<Check class="mr-1" />
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={onClose}
|
||||||
|
class="btn preset-tonal-surface border border-surface-500 hover:preset-filled-surface-500 transition"
|
||||||
|
>
|
||||||
|
<X class="mr-1" />
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
Reference in New Issue
Block a user