Compare commits
7 Commits
e3a3ab7de8
...
1ef9080cda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ef9080cda | ||
|
|
66c0be65c4 | ||
|
|
bdba092de0 | ||
|
|
0fa93d7ee5 | ||
|
|
847d653b5e | ||
|
|
cd01a87143 | ||
|
|
60ecd221b4 |
@@ -157,15 +157,23 @@ function handle_save() {
|
||||
<!-- Trigger Button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={generate_ai_result}
|
||||
onclick={() => {
|
||||
if (summary) {
|
||||
tmp_summary = summary;
|
||||
active_tab = 'result';
|
||||
show_modal = true;
|
||||
} else {
|
||||
generate_ai_result();
|
||||
}
|
||||
}}
|
||||
class={buttonClass}
|
||||
title="Generate AI summary/analysis">
|
||||
title={summary ? 'View existing AI summary' : 'Generate AI summary/analysis'}>
|
||||
{#await ae_promises}
|
||||
<Loader class="mr-1 inline-block animate-spin" size="1.2em" />
|
||||
<span class="text-sm">Processing...</span>
|
||||
{:then}
|
||||
<BotMessageSquare class="mr-1 inline-block" size="1.2em" />
|
||||
<span class="text-sm">Summarize</span>
|
||||
<span class="text-sm hidden">Summarize</span>
|
||||
{:catch}
|
||||
<span class="text-sm text-red-500">Error</span>
|
||||
{/await}
|
||||
@@ -181,6 +189,7 @@ function handle_save() {
|
||||
class="btn btn-sm preset-tonal-surface shadow-md"
|
||||
title="AI Settings">
|
||||
<Settings size="1.2em" />
|
||||
<span class="text-sm hidden">Settings</span>
|
||||
</button>
|
||||
|
||||
<!-- Unified AI Modal -->
|
||||
|
||||
@@ -81,6 +81,7 @@ type CMCache = {
|
||||
languages?: any;
|
||||
oneDark?: any;
|
||||
placeholderExt?: any;
|
||||
Compartment?: any;
|
||||
} | null;
|
||||
|
||||
const GLOBAL_KEY = '__cm_singleton_modules_v1';
|
||||
@@ -135,6 +136,7 @@ export async function ensure_CodeMirror_modules(): Promise<CMCache> {
|
||||
EditorState_allowMultipleSelections:
|
||||
stateMod.EditorState.allowMultipleSelections,
|
||||
EditorState_readOnly: stateMod.EditorState.readOnly,
|
||||
Compartment: stateMod.Compartment,
|
||||
|
||||
markdown: markdownMod?.markdown,
|
||||
markdownLanguage: markdownMod?.markdownLanguage,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ensure_CodeMirror_modules } from './codemirror_modules';
|
||||
// import type { key_val } from '$lib/stores/ae_stores';
|
||||
|
||||
// Icons (Standardized to Lucide where possible, or FontAwesome placeholders)
|
||||
import { Bold, Code, Italic, List } from '@lucide/svelte';
|
||||
import { Bold, Code, Hash, Italic, List } from '@lucide/svelte';
|
||||
interface Props {
|
||||
content?: string | null;
|
||||
new_content?: string;
|
||||
@@ -42,6 +42,8 @@ let {
|
||||
|
||||
let editor_container: HTMLDivElement | undefined = $state();
|
||||
let cm: any = $state(); // CodeMirror modules cache
|
||||
let show_line_nums = $state(untrack(() => show_line_numbers));
|
||||
let ln_compartment: any = null;
|
||||
|
||||
async function create_editor() {
|
||||
if (!browser) return;
|
||||
@@ -55,6 +57,8 @@ async function create_editor() {
|
||||
editor_view = null;
|
||||
}
|
||||
|
||||
ln_compartment = new cm.Compartment();
|
||||
|
||||
const extensions = [
|
||||
cm.highlightSpecialChars(),
|
||||
cm.history(),
|
||||
@@ -70,8 +74,9 @@ async function create_editor() {
|
||||
cm.highlightActiveLine(),
|
||||
cm.highlightActiveLineGutter(),
|
||||
|
||||
// Keymaps
|
||||
// Keymaps — indentWithTab must come before defaultKeymap
|
||||
cm.keymap.of([
|
||||
cm.indentWithTab,
|
||||
...cm.defaultKeymap,
|
||||
...cm.searchKeymap,
|
||||
...cm.historyKeymap,
|
||||
@@ -80,6 +85,9 @@ async function create_editor() {
|
||||
...cm.lintKeymap
|
||||
]),
|
||||
|
||||
// 4-space indentation unit
|
||||
cm.indentUnit.of(' '),
|
||||
|
||||
// Language Support
|
||||
language === 'markdown'
|
||||
? cm.markdown({ base: cm.markdownLanguage })
|
||||
@@ -94,7 +102,7 @@ async function create_editor() {
|
||||
readonly
|
||||
? cm.EditorState.readOnly.of(true)
|
||||
: cm.EditorView.editable.of(true),
|
||||
show_line_numbers ? cm.lineNumbers() : null,
|
||||
ln_compartment.of(show_line_nums ? cm.lineNumbers() : []),
|
||||
wrap_lines ? cm.EditorView_lineWrapping : null,
|
||||
placeholder ? cm.placeholderExt(placeholder) : null,
|
||||
|
||||
@@ -144,6 +152,14 @@ $effect(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle line numbers without rebuilding the editor
|
||||
$effect(() => {
|
||||
if (!editor_view || !ln_compartment || !cm) return;
|
||||
editor_view.dispatch({
|
||||
effects: ln_compartment.reconfigure(show_line_nums ? cm.lineNumbers() : [])
|
||||
});
|
||||
});
|
||||
|
||||
// *** Toolbar Helpers
|
||||
const wrap_selection = (before: string, after: string = before) => {
|
||||
if (!editor_view) return;
|
||||
@@ -244,6 +260,15 @@ const toggle_list = () => {
|
||||
</button>
|
||||
|
||||
<div class="ml-auto flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm {show_line_nums
|
||||
? 'variant-filled-primary'
|
||||
: 'variant-soft'} hover:variant-filled-primary"
|
||||
onclick={() => (show_line_nums = !show_line_nums)}
|
||||
title="Toggle Line Numbers">
|
||||
<Hash size="14" />
|
||||
</button>
|
||||
<span
|
||||
class="mr-2 self-center text-[10px] font-bold uppercase opacity-50"
|
||||
>{language}</span>
|
||||
|
||||
@@ -303,12 +303,18 @@ if (browser) {
|
||||
}
|
||||
|
||||
import { LoaderCircle } from '@lucide/svelte';
|
||||
|
||||
const title_journal = $derived(
|
||||
$lq__journal_obj?.name
|
||||
? $lq__journal_obj.name.length > 60
|
||||
? $lq__journal_obj.name.slice(0, 60) + '…'
|
||||
: $lq__journal_obj.name
|
||||
: 'Journal'
|
||||
);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
Æ Journals: {$lq__journal_obj?.name ?? ''} - {$ae_loc?.title}
|
||||
</title>
|
||||
<title>{title_journal} - OSIT's AE Journals</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if $lq__journal_obj === undefined}
|
||||
|
||||
@@ -258,15 +258,26 @@ $effect(() => {
|
||||
// log_lvl = 1;
|
||||
}
|
||||
});
|
||||
|
||||
const title_entry = $derived(
|
||||
$lq__journal_entry_obj?.name
|
||||
? $lq__journal_entry_obj.name.length > 50
|
||||
? $lq__journal_entry_obj.name.slice(0, 50) + '…'
|
||||
: $lq__journal_entry_obj.name
|
||||
: 'Entry'
|
||||
);
|
||||
const title_journal = $derived(
|
||||
$lq__journal_obj?.name
|
||||
? $lq__journal_obj.name.length > 30
|
||||
? $lq__journal_obj.name.slice(0, 30) + '…'
|
||||
: $lq__journal_obj.name
|
||||
: 'Journal'
|
||||
);
|
||||
</script>
|
||||
|
||||
<!-- <svelte:head>
|
||||
<title>
|
||||
Æ Journals:
|
||||
{$lq__journal_entry_obj?.name ? ae_util.shorten_string({ string: $lq__journal_entry_obj?.name, max_length: 20, begin_length: 10, end_length: 4 }) : ''}
|
||||
- {$ae_loc?.title}
|
||||
</title>
|
||||
</svelte:head> -->
|
||||
<svelte:head>
|
||||
<title>{title_entry} - {title_journal} - OSIT's AE Journals</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if $ae_loc.person_id == $lq__journal_obj?.person_id || $lq__journal_entry_obj?.public}
|
||||
<section
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
* Extracted 2026-01-08 to modularize the massive Journal Entry view.
|
||||
* Handles: CodeMirror vs Plain vs Rendered HTML for both View and Edit modes.
|
||||
*/
|
||||
import { LockKeyhole, RefreshCcw, Save } from '@lucide/svelte';
|
||||
import {
|
||||
LockKeyhole, RefreshCcw, Save
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { journals_loc } from '$lib/ae_journals/ae_journals_stores';
|
||||
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
||||
@@ -67,6 +69,7 @@ const preferred_viewer = $derived(
|
||||
<div
|
||||
class="prose dark:prose-invert w-full max-w-none overflow-x-auto rounded-lg border border-gray-200 bg-white p-4 shadow-lg dark:border-gray-700 dark:bg-gray-900">
|
||||
<!-- The rendered HTML branch is intentionally trusted input from the journal editor pipeline. -->
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html tmp_entry_obj?.content_md_html || ''}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -120,21 +123,30 @@ const preferred_viewer = $derived(
|
||||
placeholder="Edit content..."></textarea>
|
||||
{/if}
|
||||
|
||||
<!-- Floating Save Button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={on_save}
|
||||
disabled={!has_changed}
|
||||
class="btn btn-sm md:btn-md lg:btn-lg preset-filled-success fixed top-84 right-6 z-20 hidden min-w-32 shadow-xl transition-all md:inline-flex"
|
||||
class:hidden={!has_changed}>
|
||||
<Save size="1.2em" class="mr-2" /> Save
|
||||
</button>
|
||||
<!-- Floating Save Button (desktop only) -->
|
||||
{#if has_changed}
|
||||
<button
|
||||
type="button"
|
||||
onclick={on_save}
|
||||
|
||||
class="
|
||||
btn btn-sm md:btn-md lg:btn-lg preset-tonal-warning hover:preset-filled-warning-500
|
||||
fixed top-84 right-6 z-20
|
||||
min-w-32 shadow-xl
|
||||
max-sm:hidden
|
||||
"
|
||||
title="Save changes"
|
||||
>
|
||||
<Save size="1.2em" class="" />Save
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Inline Save Button (Mobile/Context) -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={on_save}
|
||||
disabled={!has_changed}
|
||||
title="Save changes"
|
||||
class="btn preset-tonal-warning hover:preset-filled-warning-500 mt-4 w-full max-w-96"
|
||||
class:invisible={!has_changed}>
|
||||
<Save size="1.2em" class="mr-2" /> Save Changes
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
CircleCheck,
|
||||
CircleX,
|
||||
Eye,
|
||||
Fingerprint,
|
||||
// Fingerprint,
|
||||
LoaderCircle,
|
||||
LockKeyhole,
|
||||
LockKeyholeOpen,
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Settings
|
||||
} from '@lucide/svelte';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import {
|
||||
journals_loc,
|
||||
journals_sess
|
||||
@@ -83,10 +84,13 @@ function toggle_edit_mode() {
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggle_edit_mode}
|
||||
title={$journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current'
|
||||
? (has_changed ? 'Save & exit edit mode' : 'Exit edit mode')
|
||||
: 'Edit entry'}
|
||||
class="btn-icon btn-icon-sm transition-colors duration-150 {has_changed &&
|
||||
$journals_loc.entry.edit_kv[entry.journal_entry_id] ===
|
||||
'current'
|
||||
? 'preset-filled-success'
|
||||
? 'preset-tonal-warning hover:preset-filled-warning-500'
|
||||
: 'preset-tonal-surface'}">
|
||||
{#if $journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current'}
|
||||
{#if has_changed}<Save size="1.2em" />{:else}<Eye
|
||||
@@ -160,24 +164,34 @@ function toggle_edit_mode() {
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<div class="bg-surface-500/20 mx-1 h-6 w-px"></div>
|
||||
|
||||
<!-- Unified Config Button -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-primary font-bold transition-colors duration-150"
|
||||
onclick={on_show_config}>
|
||||
<Settings size="1.1em" class="mr-2" /> Config
|
||||
onclick={on_show_config}
|
||||
class:hidden={!$ae_loc.edit_mode}
|
||||
class="
|
||||
btn btn-sm preset-tonal-primary font-bold transition-colors duration-150
|
||||
"
|
||||
title="Entry Configuration"
|
||||
>
|
||||
<Settings size="1.1em" />Config
|
||||
</button>
|
||||
|
||||
<!-- Explicit Save (Mobile/Backup) -->
|
||||
{#if has_changed && save_status !== 'saving'}
|
||||
<!-- {#if has_changed && save_status !== 'saving'}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-filled-primary transition-colors duration-150"
|
||||
onclick={on_save}>
|
||||
<Save size="1.1em" class="mr-2" /> Save
|
||||
onclick={on_save}
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-filled-primary
|
||||
transition-colors duration-150
|
||||
md:hidden
|
||||
"
|
||||
title="Save changes"
|
||||
>
|
||||
<Save size="1.1em" class="" />Save
|
||||
</button>
|
||||
{/if}
|
||||
{/if} -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -154,33 +154,38 @@ $effect(() => {
|
||||
|
||||
// 2. Auto-Save Debounce
|
||||
$effect(() => {
|
||||
// Isolate logic from secondary dependencies
|
||||
const should_save = untrack(
|
||||
() => has_unsaved_changes && !is_processing && save_status !== 'saving'
|
||||
);
|
||||
// Track content and name directly so this effect re-runs on every keystroke,
|
||||
// resetting the debounce timer each time (fires 2 s after the LAST change).
|
||||
// Tracking has_unsaved_changes ensures the effect also wakes up when changes
|
||||
// are cleared (e.g. after a save) so the status indicator resets correctly.
|
||||
// All side-effects (save_status writes, $journals_loc reads) stay in untrack
|
||||
// to avoid creating reactive loops.
|
||||
void tmp_entry_obj.content;
|
||||
void tmp_entry_obj.name;
|
||||
const changed = has_unsaved_changes;
|
||||
|
||||
if (should_save) {
|
||||
if (save_status !== 'saving') save_status = 'unsaved';
|
||||
clearTimeout(auto_save_timer);
|
||||
|
||||
const auto_save_enabled = untrack(() => $journals_loc.entry.auto_save);
|
||||
if (auto_save_enabled) {
|
||||
clearTimeout(auto_save_timer);
|
||||
auto_save_timer = setTimeout(() => {
|
||||
if (
|
||||
untrack(
|
||||
() =>
|
||||
has_unsaved_changes &&
|
||||
!is_processing &&
|
||||
save_status !== 'saving'
|
||||
)
|
||||
) {
|
||||
update_journal_entry();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
} else if (save_status === 'unsaved' && !has_unsaved_changes) {
|
||||
save_status = 'saved';
|
||||
if (!changed) {
|
||||
untrack(() => {
|
||||
if (save_status === 'unsaved') save_status = 'saved';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
untrack(() => {
|
||||
if (save_status !== 'saving') save_status = 'unsaved';
|
||||
});
|
||||
|
||||
if (!untrack(() => $journals_loc.entry.auto_save)) return;
|
||||
|
||||
auto_save_timer = setTimeout(() => {
|
||||
untrack(() => {
|
||||
if (has_unsaved_changes && !is_processing && save_status !== 'saving') {
|
||||
update_journal_entry();
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// 3. Auto-Decryption Workflow
|
||||
@@ -482,13 +487,15 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
] == 'current'
|
||||
? 'ring-primary-500/40 ring-2 ring-inset'
|
||||
: ''}">
|
||||
<AE_Comp_Journal_Entry_AiTools
|
||||
content={typeof tmp_entry_obj.content === 'string'
|
||||
? tmp_entry_obj.content
|
||||
: ''}
|
||||
bind:summary={tmp_entry_obj.summary}
|
||||
on_save={() => update_journal_entry()}
|
||||
{log_lvl} />
|
||||
{#if $ae_loc.edit_mode && $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'}
|
||||
<AE_Comp_Journal_Entry_AiTools
|
||||
content={typeof tmp_entry_obj.content === 'string'
|
||||
? tmp_entry_obj.content
|
||||
: ''}
|
||||
bind:summary={tmp_entry_obj.summary}
|
||||
on_save={() => update_journal_entry()}
|
||||
{log_lvl} />
|
||||
{/if}
|
||||
|
||||
<AE_Comp_Journal_Entry_Editor
|
||||
entry={$lq__journal_entry_obj}
|
||||
|
||||
Reference in New Issue
Block a user