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:
@@ -1,35 +1,30 @@
|
|||||||
import type { EditorView } from '@codemirror/view';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the current selection in CodeMirror with the given prefix and suffix.
|
* Wraps the current selection in CodeMirror with the given prefix and suffix.
|
||||||
*/
|
*/
|
||||||
export function wrapSelection(view: EditorView, prefix: string, suffix: string = prefix) {
|
export function wrapSelection(view: any, prefix: string, suffix: string = prefix) {
|
||||||
if (!view) return;
|
if (!view || view.updateInProgress) return;
|
||||||
const { state, dispatch } = view;
|
|
||||||
const changes = state.changeByRange((range) => {
|
const { state } = view;
|
||||||
const selectedText = state.doc.sliceString(range.from, range.to);
|
view.dispatch(state.changeByRange((range: any) => {
|
||||||
return {
|
return {
|
||||||
changes: [
|
changes: [
|
||||||
{ from: range.from, insert: prefix },
|
{from: range.from, insert: prefix},
|
||||||
{ from: range.to, insert: suffix }
|
{from: range.to, insert: suffix}
|
||||||
],
|
],
|
||||||
range: {
|
range: range.constructor.range(range.from + prefix.length, range.to + prefix.length)
|
||||||
from: range.from + prefix.length,
|
|
||||||
to: range.to + prefix.length
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
dispatch(state.update(changes, { scrollIntoView: true, userEvent: 'input' }));
|
|
||||||
view.focus();
|
view.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a prefix at the start of each line in the selection (e.g., for lists or blockquotes).
|
* 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) {
|
export function toggleLinePrefix(view: any, prefix: string) {
|
||||||
if (!view) return;
|
if (!view || view.updateInProgress) return;
|
||||||
const { state, dispatch } = view;
|
|
||||||
const changes = state.changeByRange((range) => {
|
const { state } = view;
|
||||||
|
view.dispatch(state.changeByRange((range: any) => {
|
||||||
const lines = [];
|
const lines = [];
|
||||||
for (let pos = range.from; pos <= range.to; ) {
|
for (let pos = range.from; pos <= range.to; ) {
|
||||||
const line = state.doc.lineAt(pos);
|
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 isAlreadyPrefixed = lines.every(l => l.text.startsWith(prefix));
|
||||||
|
|
||||||
const lineChanges = lines.map(l => {
|
const lineChanges = lines.map(l => {
|
||||||
if (isAlreadyPrefixed) {
|
if (isAlreadyPrefixed) {
|
||||||
return { from: l.from, to: l.from + prefix.length, insert: '' };
|
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 {
|
return {
|
||||||
changes: lineChanges,
|
changes: lineChanges,
|
||||||
range: {
|
range: range.constructor.range(newFrom, Math.max(newFrom, newTo))
|
||||||
from: range.from + (isAlreadyPrefixed ? -prefix.length : prefix.length),
|
|
||||||
to: range.to + (isAlreadyPrefixed ? -prefix.length * lines.length : prefix.length * lines.length)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
dispatch(state.update(changes, { scrollIntoView: true, userEvent: 'input' }));
|
|
||||||
view.focus();
|
view.focus();
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
ArrowDown01,
|
ArrowDown01,
|
||||||
ArrowDown10,
|
ArrowDown10,
|
||||||
ArrowDownUp,
|
ArrowDownUp,
|
||||||
Bold,
|
|
||||||
BookHeart,
|
BookHeart,
|
||||||
Bot,
|
Bot,
|
||||||
BotMessageSquare,
|
BotMessageSquare,
|
||||||
@@ -32,10 +31,6 @@
|
|||||||
Group,
|
Group,
|
||||||
Hash,
|
Hash,
|
||||||
History,
|
History,
|
||||||
Italic,
|
|
||||||
Link,
|
|
||||||
List,
|
|
||||||
ListOrdered,
|
|
||||||
Loader,
|
Loader,
|
||||||
LockKeyhole,
|
LockKeyhole,
|
||||||
LockKeyholeOpen,
|
LockKeyholeOpen,
|
||||||
@@ -48,7 +43,6 @@
|
|||||||
Pencil,
|
Pencil,
|
||||||
PenLine,
|
PenLine,
|
||||||
Plus,
|
Plus,
|
||||||
Quote,
|
|
||||||
RemoveFormatting,
|
RemoveFormatting,
|
||||||
Save,
|
Save,
|
||||||
Search,
|
Search,
|
||||||
@@ -60,7 +54,6 @@
|
|||||||
Siren,
|
Siren,
|
||||||
Skull,
|
Skull,
|
||||||
SquareLibrary,
|
SquareLibrary,
|
||||||
Strikethrough,
|
|
||||||
Tags,
|
Tags,
|
||||||
Trash2,
|
Trash2,
|
||||||
TypeOutline,
|
TypeOutline,
|
||||||
@@ -157,11 +150,24 @@
|
|||||||
|
|
||||||
// let orig_entry_obj: key_val = $state({});
|
// let orig_entry_obj: key_val = $state({});
|
||||||
let orig_entry_obj: key_val | null = $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 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_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
|
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: key_val = { ...$lq__journal_entry_obj };
|
||||||
|
|
||||||
// let tmp_entry_obj = $derived(async () => {
|
// let tmp_entry_obj = $derived(async () => {
|
||||||
@@ -610,51 +616,7 @@
|
|||||||
log_lvl: 1
|
log_lvl: 1
|
||||||
});
|
});
|
||||||
console.log('Journal entry updated successfully:', response);
|
console.log('Journal entry updated successfully:', response);
|
||||||
// tick();
|
|
||||||
updated_obj = true;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error updating journal entry:', error);
|
console.error('Error updating journal entry:', error);
|
||||||
alert('Failed to update journal entry.');
|
alert('Failed to update journal entry.');
|
||||||
@@ -954,95 +916,61 @@
|
|||||||
items-center justify-between
|
items-center justify-between
|
||||||
w-full
|
w-full
|
||||||
bg-gray-100 dark:bg-gray-800
|
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 -->
|
<!-- Toggle edit for journal entry -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
// Ask if they would like to save changes before toggling edit mode off.
|
const isEditing = $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current';
|
||||||
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.'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
update_journal_entry();
|
|
||||||
}
|
|
||||||
$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';
|
|
||||||
}
|
|
||||||
|
|
||||||
// if ($ae_loc.trusted_access && !$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]) {
|
if (isEditing) {
|
||||||
// trigger_decrypt = true;
|
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;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Action: ENTER EDIT MODE
|
||||||
|
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = 'current';
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
class="
|
class="
|
||||||
btn btn-sm
|
btn btn-icon btn-sm
|
||||||
preset-tonal-success hover:preset-filled-warning-500
|
preset-tonal-surface hover:preset-filled-primary-500
|
||||||
transition-all
|
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'}
|
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'}
|
||||||
<!-- <Pencil strokeWidth="2.5" color="blue" /> -->
|
{#if tmp_entry_obj_changed}
|
||||||
<!-- <Hash strokeWidth="2.5" color="green" /> -->
|
<Save size="1.25em" />
|
||||||
<!-- <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"
|
|
||||||
/>
|
|
||||||
{:else}
|
{:else}
|
||||||
<CalendarClock
|
<Eye size="1.25em" />
|
||||||
strokeWidth="2.5"
|
|
||||||
class="text-neutral-800/60 dark:text-neutral-200/60"
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else}
|
||||||
<Hash strokeWidth="2.5" color="green" />
|
<Pencil size="1.25em" />
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<h2 class="journal_entry__name text-md grow">
|
<h2 class="journal_entry__name text-base md:text-lg font-bold truncate max-w-[200px] md:max-w-md">
|
||||||
<!-- <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. -->
|
|
||||||
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'}
|
{#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">
|
<input
|
||||||
<!-- <Hash strokeWidth="2.5" color="red" /> -->
|
type="text"
|
||||||
<input
|
bind:value={tmp_entry_obj.name}
|
||||||
type="text"
|
class="input input-sm md:input-md input-bordered w-full"
|
||||||
bind:value={tmp_entry_obj.name}
|
placeholder="Journal Entry 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>
|
|
||||||
{:else}
|
{:else}
|
||||||
<span>
|
<span>
|
||||||
{#if $lq__journal_entry_obj?.name}
|
{#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}
|
{@html $lq__journal_entry_obj?.name}
|
||||||
{:else}
|
{:else}
|
||||||
<!-- <CalendarClock class="mx-1 inline-block text-neutral-800/60 dark:text-neutral-200/60"/> -->
|
|
||||||
{ae_util.iso_datetime_formatter(
|
{ae_util.iso_datetime_formatter(
|
||||||
$lq__journal_entry_obj?.created_on,
|
$lq__journal_entry_obj?.created_on,
|
||||||
'datetime_iso_12_no_seconds'
|
'datetime_iso_12_no_seconds'
|
||||||
@@ -1050,7 +978,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- </span> -->
|
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</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]} -->
|
<!-- 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'}
|
{#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">
|
<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, '**')}>
|
<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" />
|
<Bold size="1.25em" />
|
||||||
@@ -2701,6 +2629,7 @@ tabindex={$ae_loc.edit_mode ? 0 : -1} -->
|
|||||||
<CodeXml size="1.25em" />
|
<CodeXml size="1.25em" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
<E_app_codemirror_v5
|
<E_app_codemirror_v5
|
||||||
content={tmp_entry_obj?.content ?? ''}
|
content={tmp_entry_obj?.content ?? ''}
|
||||||
|
|||||||
Reference in New Issue
Block a user