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`.
|
||||
- [ ] **Automated Source of Truth**: Generate `V3_OBJECT_MODELS.md` automatically in `agents_sync/Aether/`.
|
||||
- [ ] **Phase 2: UI/UX Excellence**
|
||||
- [ ] Implement "Quick Add" for high-velocity entry.
|
||||
- [ ] Add rapid append/prepend functionality to existing entries.
|
||||
- [x] Implement "Quick Add" for high-velocity entry.
|
||||
- [x] Add rapid append/prepend functionality to existing entries.
|
||||
- [ ] Ensure full cross-platform responsiveness (Mobile -> Workstation).
|
||||
- [ ] **Phase 3: Content Portability**
|
||||
- [ ] 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`).
|
||||
|
||||
### Phase 2: Rapid Entry (Active)
|
||||
- [ ] Create `ae_comp__journal_entry_quick_add.svelte`.
|
||||
- [ ] Integrate Quick Add into `+page.svelte`.
|
||||
- [x] Create `ae_comp__journal_entry_quick_add.svelte`.
|
||||
- [x] Integrate Quick Add into `+page.svelte`.
|
||||
- [ ] Update `ae_journals_stores.ts` to manage Quick Add state/visibility.
|
||||
|
||||
### Phase 3: Content Manipulation
|
||||
- [ ] Update `JournalEntry_SettingsMenu.svelte` with Append/Prepend actions.
|
||||
- [ ] Implement Append/Prepend logic in `JournalEntry_Editor.svelte` or helper functions.
|
||||
- [x] Update `JournalEntry_SettingsMenu.svelte` with Append/Prepend actions.
|
||||
- [x] Implement Append/Prepend logic in `ae_comp__journal_entry_obj_id_view.svelte`.
|
||||
|
||||
### Phase 4: Polish & Security
|
||||
- [ ] Audit encryption flow for Quick Added entries.
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
import Modal_journals_cfg from './modal_journals_config.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';
|
||||
|
||||
@@ -175,6 +176,11 @@
|
||||
{$ae_loc.person.given_name ? `- ${$ae_loc.person.given_name}` : ''}
|
||||
</h1>
|
||||
|
||||
<!-- Quick Add Section -->
|
||||
<div class="w-full max-w-2xl mx-auto px-4 pb-4">
|
||||
<AeCompJournalEntryQuickAdd class="shadow-lg" />
|
||||
</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:hidden={!$ae_loc.edit_mode}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
onDecrypt: () => void;
|
||||
onDecryptHistory: () => void;
|
||||
onChangeJournal: () => void;
|
||||
onAppend?: () => void;
|
||||
onPrepend?: () => void;
|
||||
log_lvl?: number;
|
||||
}
|
||||
|
||||
@@ -39,6 +41,8 @@
|
||||
onDecrypt,
|
||||
onDecryptHistory,
|
||||
onChangeJournal,
|
||||
onAppend,
|
||||
onPrepend,
|
||||
log_lvl = 0
|
||||
}: Props = $props();
|
||||
|
||||
@@ -220,6 +224,8 @@ p-2 md:p-3 rounded-lg shadow-md
|
||||
{onSave}
|
||||
{onDecryptHistory}
|
||||
{onChangeJournal}
|
||||
{onAppend}
|
||||
{onPrepend}
|
||||
{log_lvl}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
Eye, EyeOff, ShieldCheck, ShieldMinus,
|
||||
Clock, X, Trash2, Settings, Shapes,
|
||||
Copy, RemoveFormatting, CodeXml, TypeOutline,
|
||||
History, Pencil, PenLine, FileX, SquareLibrary
|
||||
History, Pencil, PenLine, FileX, SquareLibrary,
|
||||
ArrowUpToLine, ArrowDownToLine
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import { journals_slct, journals_loc, journals_sess } from '$lib/ae_journals/ae_journals_stores';
|
||||
@@ -26,6 +27,8 @@
|
||||
onSave: () => void;
|
||||
onDecryptHistory: () => void;
|
||||
onChangeJournal: () => void;
|
||||
onAppend?: () => void;
|
||||
onPrepend?: () => void;
|
||||
log_lvl?: number;
|
||||
}
|
||||
|
||||
@@ -37,6 +40,8 @@
|
||||
onSave,
|
||||
onDecryptHistory,
|
||||
onChangeJournal,
|
||||
onAppend,
|
||||
onPrepend,
|
||||
log_lvl = 0
|
||||
}: Props = $props();
|
||||
|
||||
@@ -83,6 +88,16 @@
|
||||
</script>
|
||||
|
||||
<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 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<Shapes size="1.1em" class="text-surface-500" />
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
import AE_MetadataFooter from '$lib/ae_elements/AE_MetadataFooter.svelte';
|
||||
import JournalEntry_Editor from './JournalEntry_Editor.svelte';
|
||||
import JournalEntry_Header from './JournalEntry_Header.svelte';
|
||||
import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte';
|
||||
|
||||
// *** Props
|
||||
interface Props {
|
||||
@@ -486,6 +487,22 @@
|
||||
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>
|
||||
|
||||
<section
|
||||
@@ -508,6 +525,8 @@
|
||||
onDecrypt={handle_content_decryption}
|
||||
onDecryptHistory={handle_history_decryption}
|
||||
onChangeJournal={change_journal_id}
|
||||
onAppend={handle_append_start}
|
||||
onPrepend={handle_prepend_start}
|
||||
{log_lvl}
|
||||
/>
|
||||
|
||||
@@ -596,7 +615,18 @@
|
||||
|
||||
<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}
|
||||
<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">
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
let { log_lvl = $bindable(0), lq__journal_obj, lq__journal_entry_obj_li }: Props = $props();
|
||||
|
||||
// *** Import Svelte specific
|
||||
import { goto, invalidate, pushState, replaceState } from '$app/navigation';
|
||||
import { Modal } from 'flowbite-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
CalendarClock,
|
||||
Check,
|
||||
@@ -17,11 +16,9 @@
|
||||
Copy,
|
||||
Eye,
|
||||
EyeOff,
|
||||
FileLock,
|
||||
Files,
|
||||
Fingerprint,
|
||||
Flag,
|
||||
FlagOff,
|
||||
Group,
|
||||
ListPlus,
|
||||
Lock,
|
||||
@@ -39,50 +36,45 @@
|
||||
// *** 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 { api } from '$lib/api/api';
|
||||
import {
|
||||
ae_snip,
|
||||
ae_loc,
|
||||
ae_sess,
|
||||
ae_api,
|
||||
ae_trig,
|
||||
slct,
|
||||
slct_trigger
|
||||
slct
|
||||
} from '$lib/stores/ae_stores';
|
||||
import {
|
||||
journals_loc,
|
||||
journals_sess,
|
||||
journals_slct,
|
||||
journals_trig
|
||||
journals_trig,
|
||||
journals_loc
|
||||
} from '$lib/ae_journals/ae_journals_stores';
|
||||
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
|
||||
|
||||
let ae_promises: key_val = $state({});
|
||||
// let ae_tmp: key_val = {};
|
||||
// let ae_triggers: key_val = {};
|
||||
import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte';
|
||||
|
||||
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);
|
||||
let tmp_entry_obj_add_text_header: string = $state('');
|
||||
let tmp_entry_obj_add_text: string = $state('');
|
||||
let tmp_entry_obj_changed: boolean = $state(false);
|
||||
|
||||
// Derived state for modal visibility
|
||||
// We cast to boolean for the prop, but we need to handle the close event to clear the store ID
|
||||
let show_append_modal = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (tmp_entry_obj_add_text_header.length || tmp_entry_obj_add_text) {
|
||||
tmp_entry_obj_changed = true;
|
||||
} else {
|
||||
tmp_entry_obj_changed = false;
|
||||
}
|
||||
// Sync local boolean with store ID presence
|
||||
show_append_modal = !!$journals_sess.show__modal_append__journal_entry_id;
|
||||
});
|
||||
|
||||
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>
|
||||
|
||||
<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}
|
||||
<!-- <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}
|
||||
<div
|
||||
class="
|
||||
@@ -163,11 +155,9 @@
|
||||
});
|
||||
}}
|
||||
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"
|
||||
>
|
||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
||||
<Copy size="1em" />
|
||||
<RemoveFormatting size="1.25em" />
|
||||
<span class="hidden"> Copy Plaintext Markdown </span>
|
||||
</button>
|
||||
@@ -176,8 +166,6 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// Copy the rendered HTML content to clipboard
|
||||
// const htmlContent = $lq__journal_entry_obj?.content_md_html || '';
|
||||
let htmlContent =
|
||||
journals_journal_entry_obj.content_md_html || '';
|
||||
|
||||
@@ -196,11 +184,9 @@
|
||||
}}
|
||||
class:hidden={journals_journal_entry_obj.template ||
|
||||
$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"
|
||||
>
|
||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
||||
<Copy size="1em" />
|
||||
<CodeXml size="1.25em" />
|
||||
<span class="hidden"> Copy HTML Markup </span>
|
||||
</button>
|
||||
@@ -221,10 +207,7 @@
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the rendered HTML content
|
||||
const htmlContent = element.innerHTML;
|
||||
|
||||
// Use the Clipboard API to write the HTML content as rich text
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'text/html': new Blob([htmlContent], {
|
||||
@@ -241,10 +224,9 @@
|
||||
}}
|
||||
class:hidden={journals_journal_entry_obj.template ||
|
||||
$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"
|
||||
>
|
||||
<Copy size="1em" />
|
||||
<TypeOutline size="1.25em" />
|
||||
<span class="hidden">Copy Rich Text</span>
|
||||
</button>
|
||||
@@ -253,8 +235,6 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// Clone the journal entry
|
||||
// We only want to clone certain fields from the original journal entry object to avoid conflicts.
|
||||
let data_kv = {
|
||||
code: journals_journal_entry_obj.code,
|
||||
category_code: journals_journal_entry_obj.category_code,
|
||||
@@ -287,23 +267,17 @@
|
||||
});
|
||||
}}
|
||||
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"
|
||||
>
|
||||
<!-- class="btn btn-sm variant-soft-surface hover:variant-filled-warning transition py-1 px-2" -->
|
||||
<!-- <Copy strokeWidth="1" /> -->
|
||||
<Copy size="1.25em" />
|
||||
<span class="hidden md:inline">Clone</span>
|
||||
</button>
|
||||
|
||||
<!-- </span> -->
|
||||
{:else}
|
||||
<!-- <span class="flex flex-row flex-wrap gap-1 items-center justify-center"> -->
|
||||
<Lock
|
||||
size="1.25em"
|
||||
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>
|
||||
|
||||
<!-- Button to copy the Markdown version -->
|
||||
@@ -323,16 +297,12 @@
|
||||
});
|
||||
}}
|
||||
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"
|
||||
>
|
||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
||||
<Copy size="1em" />
|
||||
<Fingerprint size="1.25em" />
|
||||
<span class="hidden"> Copy Encrypted </span>
|
||||
</button>
|
||||
|
||||
<!-- </span> -->
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
@@ -344,8 +314,7 @@
|
||||
class:hidden={!journals_journal_entry_obj?.data_json?.hosted_file_kv}
|
||||
>
|
||||
<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">
|
||||
{journals_journal_entry_obj?.data_json?.hosted_file_kv
|
||||
? Object.keys(
|
||||
@@ -376,11 +345,9 @@
|
||||
|
||||
<!-- Category code for journal entry -->
|
||||
{#if journals_journal_entry_obj.category_code}
|
||||
<!-- When clicked, this will filter by the category code. -->
|
||||
<button
|
||||
type="button"
|
||||
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 (
|
||||
$journals_loc.filter__category_code ==
|
||||
journals_journal_entry_obj.category_code
|
||||
@@ -389,17 +356,10 @@
|
||||
} else {
|
||||
$journals_loc.filter__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_journal_entry_obj.category_code;
|
||||
}
|
||||
$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 ==
|
||||
journals_journal_entry_obj.category_code}
|
||||
@@ -427,7 +387,6 @@
|
||||
onclick={() => {
|
||||
$journals_sess.show__modal_append__journal_entry_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(
|
||||
JSON.stringify(journals_journal_entry_obj)
|
||||
);
|
||||
@@ -438,13 +397,11 @@
|
||||
: 'Prepend to Journal Entry'}
|
||||
>
|
||||
<ListPlus />
|
||||
<!-- Append -->
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if journals_journal_entry_obj.content}
|
||||
<!-- hover:border-gray-500 dark:hover:border-gray-500 -->
|
||||
<div
|
||||
class:hidden={journals_journal_entry_obj.hide ||
|
||||
(journals_journal_entry_obj.private &&
|
||||
@@ -483,7 +440,6 @@
|
||||
{@html journals_journal_entry_obj.content}
|
||||
</div>
|
||||
|
||||
<!-- This needs to remain hidden for the copy function to work and us not seeing the rendered HTML version. -->
|
||||
<article
|
||||
class="prose hidden"
|
||||
id="rendered_journal_entry_content_{journals_journal_entry_obj.journal_entry_id}"
|
||||
@@ -497,28 +453,6 @@
|
||||
!journals_journal_entry_obj?.original_timezone}
|
||||
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
|
||||
class="ae_group"
|
||||
class:hidden={!journals_journal_entry_obj?.original_datetime &&
|
||||
@@ -545,12 +479,6 @@
|
||||
<section
|
||||
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
|
||||
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}
|
||||
class="flex flex-row gap-1 items-center justify-center"
|
||||
@@ -589,8 +517,6 @@
|
||||
log_lvl: log_lvl
|
||||
})
|
||||
.then(() => {
|
||||
// Optionally, you can provide feedback to the user
|
||||
// alert('Journal entry updated successfully!');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error updating journal entry:', error);
|
||||
@@ -622,298 +548,17 @@
|
||||
{/each}
|
||||
|
||||
<!-- 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}
|
||||
<Modal
|
||||
title="{$lq__journal_obj?.cfg_json?.entry_add_text == 'append'
|
||||
? 'Append to Journal Entry'
|
||||
: 'Prepend to Journal Entry'}: {tmp_entry_obj?.name ??
|
||||
tmp_entry_obj?.created_on} ({tmp_entry_obj?.journal_entry_id})"
|
||||
bind:open={$journals_sess.show__modal_append__journal_entry_id}
|
||||
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">
|
||||
<!-- <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>
|
||||
<AeCompModalJournalEntryAppend
|
||||
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}
|
||||
{log_lvl}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<p>No journal entry available to show.</p>
|
||||
{/if}
|
||||
</section>
|
||||
<!-- {/if} -->
|
||||
</section>
|
||||
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