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.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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 ?? ''}
|
||||
|
||||
Reference in New Issue
Block a user