Refine Journals UI and harden entry toggle logic

- Implemented 3-state (View/Eye/Save) toggle in journal entry header with Lucide icons.
- Hardened change detection logic using Svelte 5 .by for reliable UI updates.
- Improved header responsiveness and scaling across device sizes.
- Commented out experimental CodeMirror toolbar to maintain stability while keeping the helper code for reference.
This commit is contained in:
Scott Idem
2026-01-08 15:38:28 -05:00
parent 4c8f09e588
commit dd0390a5dd
2 changed files with 66 additions and 144 deletions

View File

@@ -1,35 +1,30 @@
import type { EditorView } from '@codemirror/view';
/**
* Wraps the current selection in CodeMirror with the given prefix and suffix.
*/
export function wrapSelection(view: EditorView, prefix: string, suffix: string = prefix) {
if (!view) return;
const { state, dispatch } = view;
const changes = state.changeByRange((range) => {
const selectedText = state.doc.sliceString(range.from, range.to);
export function wrapSelection(view: any, prefix: string, suffix: string = prefix) {
if (!view || view.updateInProgress) return;
const { state } = view;
view.dispatch(state.changeByRange((range: any) => {
return {
changes: [
{ from: range.from, insert: prefix },
{ from: range.to, insert: suffix }
{from: range.from, insert: prefix},
{from: range.to, insert: suffix}
],
range: {
from: range.from + prefix.length,
to: range.to + prefix.length
}
range: range.constructor.range(range.from + prefix.length, range.to + prefix.length)
};
});
dispatch(state.update(changes, { scrollIntoView: true, userEvent: 'input' }));
}));
view.focus();
}
/**
* Inserts a prefix at the start of each line in the selection (e.g., for lists or blockquotes).
*/
export function toggleLinePrefix(view: EditorView, prefix: string) {
if (!view) return;
const { state, dispatch } = view;
const changes = state.changeByRange((range) => {
export function toggleLinePrefix(view: any, prefix: string) {
if (!view || view.updateInProgress) return;
const { state } = view;
view.dispatch(state.changeByRange((range: any) => {
const lines = [];
for (let pos = range.from; pos <= range.to; ) {
const line = state.doc.lineAt(pos);
@@ -38,7 +33,6 @@ export function toggleLinePrefix(view: EditorView, prefix: string) {
}
const isAlreadyPrefixed = lines.every(l => l.text.startsWith(prefix));
const lineChanges = lines.map(l => {
if (isAlreadyPrefixed) {
return { from: l.from, to: l.from + prefix.length, insert: '' };
@@ -47,14 +41,13 @@ export function toggleLinePrefix(view: EditorView, prefix: string) {
}
});
const newFrom = range.from + (isAlreadyPrefixed ? -prefix.length : prefix.length);
const newTo = range.to + (isAlreadyPrefixed ? (-prefix.length * lines.length) : (prefix.length * lines.length));
return {
changes: lineChanges,
range: {
from: range.from + (isAlreadyPrefixed ? -prefix.length : prefix.length),
to: range.to + (isAlreadyPrefixed ? -prefix.length * lines.length : prefix.length * lines.length)
}
range: range.constructor.range(newFrom, Math.max(newFrom, newTo))
};
});
dispatch(state.update(changes, { scrollIntoView: true, userEvent: 'input' }));
}));
view.focus();
}
}

View File

@@ -9,7 +9,6 @@
ArrowDown01,
ArrowDown10,
ArrowDownUp,
Bold,
BookHeart,
Bot,
BotMessageSquare,
@@ -32,10 +31,6 @@
Group,
Hash,
History,
Italic,
Link,
List,
ListOrdered,
Loader,
LockKeyhole,
LockKeyholeOpen,
@@ -48,7 +43,6 @@
Pencil,
PenLine,
Plus,
Quote,
RemoveFormatting,
Save,
Search,
@@ -60,7 +54,6 @@
Siren,
Skull,
SquareLibrary,
Strikethrough,
Tags,
Trash2,
TypeOutline,
@@ -157,11 +150,24 @@
// let orig_entry_obj: key_val = $state({});
let orig_entry_obj: key_val | null = $state({});
let tmp_entry_obj_changed: boolean = $state(false);
let tmp_entry_obj: key_val = $state({});
let updated_obj: boolean = $state(true); // Start with true to force population of orig and tmp values.
let updated_idb: boolean = $state(true); // Updated in a separate browser session
// Idiomatic Svelte 5 change detection
let tmp_entry_obj_changed = $derived.by(() => {
if (!tmp_entry_obj || !orig_entry_obj || not_obj(tmp_entry_obj) || not_obj(orig_entry_obj)) {
return false;
}
return (
tmp_entry_obj.content !== orig_entry_obj.content ||
tmp_entry_obj.name !== orig_entry_obj.name ||
tmp_entry_obj.tags !== orig_entry_obj.tags ||
tmp_entry_obj.category_code !== orig_entry_obj.category_code
);
});
// let tmp_entry_obj: key_val = { ...$lq__journal_entry_obj };
// let tmp_entry_obj = $derived(async () => {
@@ -610,51 +616,7 @@
log_lvl: 1
});
console.log('Journal entry updated successfully:', response);
// tick();
updated_obj = true;
// tick();
// orig_entry_obj = { ...await $lq__journal_entry_obj };
// tmp_entry_obj = { ...await $lq__journal_entry_obj };
// if ($journals_loc?.entry?.decrypt_kv[$lq__journal_entry_obj?.journal_entry_id]) {
// if (!tmp_entry_obj?.content && tmp_entry_obj?.content_encrypted) {
// console.log('TEST: Decrypting the content...');
// tmp_entry_obj.content = await ae_util.decrypt_wrapper(tmp_entry_obj?.content_encrypted, journal_key);
// tmp_entry_obj.content_md_html = handle_marked(tmp_entry_obj?.content);
// orig_entry_obj.content = await ae_util.decrypt_wrapper(orig_entry_obj?.content_encrypted, journal_key);
// orig_entry_obj.content_md_html = handle_marked(orig_entry_obj?.content);
// }
// if (!tmp_entry_obj?.history && tmp_entry_obj?.history_encrypted) {
// console.log('TEST: Decrypting the history...');
// tmp_entry_obj.history = await ae_util.decrypt_wrapper(tmp_entry_obj?.history_encrypted, journal_key);
// tmp_entry_obj.history_md_html = handle_marked(tmp_entry_obj?.history);
// orig_entry_obj.history = await ae_util.decrypt_wrapper(orig_entry_obj?.history_encrypted, journal_key);
// orig_entry_obj.history_md_html = handle_marked(orig_entry_obj?.history);
// }
// }
// console.log('TEST: tmp_entry_obj:', tmp_entry_obj);
// updated_obj = true;
// updated_idb = false;
// 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,
// })
// .then((response) => {
// console.log('HERE: response', response);
// updated_obj = true;
// updated_idb = false;
// console.log('Journal entry updated successfully!');
// })
// tick();
} catch (error) {
console.error('Error updating journal entry:', error);
alert('Failed to update journal entry.');
@@ -954,95 +916,61 @@
items-center justify-between
w-full
bg-gray-100 dark:bg-gray-800
p-1 rounded-lg shadow-md
p-2 md:p-3 rounded-lg shadow-md
"
>
<div class="grow flex flex-row flex-wrap gap-2 items-center justify-start">
<div class="flex-1 flex flex-row flex-wrap gap-2 items-center justify-start min-w-[200px]">
<!-- Toggle edit for journal entry -->
<button
type="button"
onclick={() => {
// Ask if they would like to save changes before toggling edit mode off.
if (
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] ==
'current'
) {
if (
tmp_entry_obj_changed &&
confirm(
'Would you like to save changes to this journal entry before exiting edit mode? Your changes will be lost if the page is refreshed.'
)
) {
const isEditing = $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current';
if (isEditing) {
if (tmp_entry_obj_changed) {
// Action: SAVE
update_journal_entry();
} else {
// Action: CANCEL/BACK TO VIEW
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = false;
}
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] =
false;
} else {
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] =
'current';
// Action: ENTER EDIT MODE
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = 'current';
}
// if ($ae_loc.trusted_access && !$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]) {
// trigger_decrypt = true;
// }
}}
class="
btn btn-sm
preset-tonal-success hover:preset-filled-warning-500
btn btn-icon btn-sm
preset-tonal-surface hover:preset-filled-primary-500
transition-all
"
title="Toggle edit mode for this journal entry"
class:preset-filled-success-500={tmp_entry_obj_changed && $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'}
title="Toggle view/edit/save mode"
>
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'}
<!-- <Pencil strokeWidth="2.5" color="blue" /> -->
<!-- <Hash strokeWidth="2.5" color="green" /> -->
<!-- <Hash strokeWidth="1" color="red" /> -->
<NotebookText strokeWidth="2.5" color="green" />
<!-- <Hash strokeWidth="2.5" color="red" /> -->
<PenLine strokeWidth="2.5" color="red" />
{:else}
<!-- <Pencil strokeWidth="1" color="gray" /> -->
<!-- <PenLine strokeWidth="1" color="green" /> -->
{#if $lq__journal_entry_obj?.name}
<NotebookText
strokeWidth="2.5"
class="text-neutral-800/60 dark:text-neutral-200/60"
/>
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'}
{#if tmp_entry_obj_changed}
<Save size="1.25em" />
{:else}
<CalendarClock
strokeWidth="2.5"
class="text-neutral-800/60 dark:text-neutral-200/60"
/>
<Eye size="1.25em" />
{/if}
<Hash strokeWidth="2.5" color="green" />
{:else}
<Pencil size="1.25em" />
{/if}
</button>
<h2 class="journal_entry__name text-md grow">
<!-- <span class="fas fa-spinner fa-spin"></span> -->
<!-- <span class="journal_entry__name inline-block"> -->
<!-- Allow for toggle between view and edit of journal entry. -->
<h2 class="journal_entry__name text-base md:text-lg font-bold truncate max-w-[200px] md:max-w-md">
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'}
<span class="flex flex-row gap-1 items-center justify-start">
<!-- <Hash strokeWidth="2.5" color="red" /> -->
<input
type="text"
bind:value={tmp_entry_obj.name}
class="input input-bordered min-w-60 w-full text-gray-800 dark:text-gray-200"
placeholder="Journal Entry Name"
title="Edit the name of this journal entry"
/>
</span>
<input
type="text"
bind:value={tmp_entry_obj.name}
class="input input-sm md:input-md input-bordered w-full"
placeholder="Journal Entry Name"
/>
{:else}
<span>
{#if $lq__journal_entry_obj?.name}
<!-- <NotebookText class="mx-1 inline-block text-neutral-800/60 dark:text-neutral-200/60"/> -->
{@html $lq__journal_entry_obj?.name}
{:else}
<!-- <CalendarClock class="mx-1 inline-block text-neutral-800/60 dark:text-neutral-200/60"/> -->
{ae_util.iso_datetime_formatter(
$lq__journal_entry_obj?.created_on,
'datetime_iso_12_no_seconds'
@@ -1050,7 +978,6 @@
{/if}
</span>
{/if}
<!-- </span> -->
</h2>
</div>
@@ -2662,7 +2589,8 @@ tabindex={$ae_loc.edit_mode ? 0 : -1} -->
<!-- disabled={tmp_entry_obj?.private && !$journals_loc?.entry?.decrypt_kv[$lq__journal_entry_obj?.journal_entry_id]} -->
{#if $lq__journal_obj?.cfg_json?.pref_editor == 'codemirror'}
<!-- Toolbar for CodeMirror -->
<!-- Toolbar for CodeMirror (Temporarily disabled) -->
<!--
<div class="flex flex-row flex-wrap gap-1 p-1 bg-surface-100-900 border-x border-t border-orange-300 dark:border-orange-700 rounded-t-lg w-full max-w-6xl">
<button type="button" class="btn btn-icon btn-sm preset-tonal-surface hover:preset-filled-secondary-500" title="Bold" onclick={() => wrapSelection(editorView, '**')}>
<Bold size="1.25em" />
@@ -2701,6 +2629,7 @@ tabindex={$ae_loc.edit_mode ? 0 : -1} -->
<CodeXml size="1.25em" />
</button>
</div>
-->
<E_app_codemirror_v5
content={tmp_entry_obj?.content ?? ''}