feat(journals): stabilize V3 API integration and restore cryptographic toggles

This commit is contained in:
Scott Idem
2026-01-14 14:35:55 -05:00
parent 17870bc0a2
commit 283053e4a4
2 changed files with 217 additions and 524 deletions

View File

@@ -1,37 +1,32 @@
<script lang="ts">
// let log_lvl: number = 0;
// *** Import Svelte core and navigation
/**
* ae_comp__journal_entry_obj_id_view.svelte
* Reference Implementation for Journal Entry View/Edit
* Corrected for V3 API Strictness & Robust Decryption
*/
// *** Import Svelte core
import { goto } from '$app/navigation';
import { untrack } from 'svelte';
// *** Import secondary libraries
import { marked } from 'marked';
// *** Import Aether components and helpers
import E_app_codemirror_v5 from '$lib/app_components/e_app_codemirror_v5.svelte';
// *** Import Aether core logic and stores
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_loc,
ae_api,
ae_trig
} from '$lib/stores/ae_stores';
import {
journals_loc,
journals_sess,
journals_slct
} from '$lib/ae_journals/ae_journals_stores';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { journals_loc, journals_sess, journals_slct } from '$lib/ae_journals/ae_journals_stores';
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import Comp_journal_entry_file_li from './ae_comp__journal_entry_obj_file_li.svelte';
import Comp_hosted_files_download_button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import AE_AITools from '$lib/ae_elements/AE_AITools.svelte';
import AE_ObjectFlags from '$lib/ae_elements/AE_ObjectFlags.svelte';
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 AE_MetadataFooter from '$lib/ae_elements/AE_MetadataFooter.svelte';
import AE_AITools from '$lib/ae_elements/AE_AITools.svelte';
import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte';
// Icons
import { AlertCircle, XCircle, Loader2 } from '@lucide/svelte';
// *** Props
interface Props {
@@ -50,589 +45,280 @@
onShowExport
}: Props = $props();
$effect(() => {
if (onShowExport) {
console.log('ae_comp__journal_entry_obj_id_view: onShowExport prop is defined');
} else {
console.warn('ae_comp__journal_entry_obj_id_view: onShowExport prop is UNDEFINED');
}
});
// *** State
let editorView: any = $state();
let ae_promises: any = $state();
let orig_entry_obj: key_val | null = $state({});
let orig_entry_obj: key_val | null = $state(null);
let tmp_entry_obj: key_val = $state({});
let updated_obj: boolean = $state(true); // Start with true to force population
let updated_idb: boolean = $state(true); // Internal session sync flag
// 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;
}
let save_status: 'saved' | 'unsaved' | 'saving' = $state('saved');
let decryption_error: string | null = $state(null);
let auto_save_timer: ReturnType<typeof setTimeout>;
let is_processing = $state(false);
// *** Derived
let has_unsaved_changes = $derived.by(() => {
if (!tmp_entry_obj || !orig_entry_obj || is_processing) 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
(tmp_entry_obj.content ?? null) !== (orig_entry_obj.content ?? null) ||
(tmp_entry_obj.name ?? null) !== (orig_entry_obj.name ?? null) ||
(tmp_entry_obj.tags ?? null) !== (orig_entry_obj.tags ?? null) ||
(tmp_entry_obj.category_code ?? null) !== (orig_entry_obj.category_code ?? null) ||
(tmp_entry_obj.private ?? false) !== (orig_entry_obj.private ?? false) ||
(tmp_entry_obj.public ?? false) !== (orig_entry_obj.public ?? false) ||
(tmp_entry_obj.personal ?? false) !== (orig_entry_obj.personal ?? false) ||
(tmp_entry_obj.professional ?? false) !== (orig_entry_obj.professional ?? false)
);
});
// --- Auto Save Logic ---
let save_status: 'saved' | 'unsaved' | 'saving' = $state('saved');
let auto_save_timer: ReturnType<typeof setTimeout>;
// *** Effects
// 1. Initial Load & Background Sync
$effect(() => {
// Explicitly track these properties so the effect re-runs on every keystroke
const _content = tmp_entry_obj.content;
const _name = tmp_entry_obj.name;
const _tags = tmp_entry_obj.tags;
const _category = tmp_entry_obj.category_code;
const entry = $lq__journal_entry_obj;
if (entry && entry.updated_on) {
// Only sync if saved and not currently processing/editing
if (save_status === 'saved' && !has_unsaved_changes && !is_processing) {
const base = {
...entry,
content: entry.content ?? null,
history: entry.history ?? null
};
orig_entry_obj = { ...base };
tmp_entry_obj = { ...base };
}
}
});
if (tmp_entry_obj_changed) {
// 2. Auto-Save Debounce
$effect(() => {
const _trigger = tmp_entry_obj.content;
if (has_unsaved_changes && !is_processing) {
if (save_status !== 'saving') save_status = 'unsaved';
if ($journals_loc.entry.auto_save) {
// This will now correctly reset on every keystroke
clearTimeout(auto_save_timer);
auto_save_timer = setTimeout(() => {
if (tmp_entry_obj_changed && save_status !== 'saving') {
update_journal_entry();
}
}, 2000); // 2 seconds debounce
}
} else {
clearTimeout(auto_save_timer);
if (save_status !== 'saving') {
save_status = 'saved';
if (untrack(() => has_unsaved_changes)) update_journal_entry();
}, 2000);
}
} else if (save_status !== 'saving' && !has_unsaved_changes) {
save_status = 'saved';
}
});
let decrypted_history: string = $state('');
if (!$journals_loc?.entry?.decrypt_kv) {
$journals_loc.entry.decrypt_kv = {};
$journals_loc.entry.decrypt_kv[$lq__journal_entry_obj?.journal_entry_id] = null;
}
function not_obj(obj) {
return !obj || typeof obj !== 'object' || Object.keys(obj).length === 0;
}
// 3. Auto-Decryption Workflow
$effect(() => {
(async () => {
if ($lq__journal_entry_obj && $lq__journal_entry_obj?.updated_on) {
// Only overwrite local state if we aren't currently editing/unsaved
if (save_status === 'saved' || !tmp_entry_obj_changed) {
if (log_lvl) console.log(`Journal Entry updated:`, $lq__journal_entry_obj);
const journal = $lq__journal_obj;
if (!journal?.id) return;
orig_entry_obj = { ...$lq__journal_entry_obj };
tmp_entry_obj = $lq__journal_entry_obj ? { ...$lq__journal_entry_obj } : {};
updated_idb = false;
}
} else if (updated_obj && (await $lq__journal_entry_obj)) {
// If we forced an update (e.g. after a manual save), we do sync
if (log_lvl) console.log(`Journal Entry forced update:`, $lq__journal_entry_obj);
updated_obj = false;
orig_entry_obj = { ...$lq__journal_entry_obj };
tmp_entry_obj = $lq__journal_entry_obj ? { ...$lq__journal_entry_obj } : {};
if ($journals_loc?.entry?.decrypt_kv[$lq__journal_entry_obj?.journal_entry_id]) {
// ... (decryption logic remains the same)
}
updated_idb = false;
}
})();
});
$effect(() => {
if (
!not_obj(tmp_entry_obj) &&
!not_obj(orig_entry_obj) &&
JSON.stringify(tmp_entry_obj) != JSON.stringify(orig_entry_obj)
) {
console.log(
'TEST: tmp_entry_obj and orig_entry_obj available; marking tmp_entry_obj as changed'
);
tmp_entry_obj_changed = true;
} else {
// console.log('TEST: tmp_entry_obj == orig_entry_obj');
tmp_entry_obj_changed = false;
const session = $journals_sess?.journal_kv[journal.id];
const is_verified = session?.journal_passcode_verified;
const decrypted_status = session?.journal_passcode_decrypted;
if ($lq__journal_entry_obj?.content_encrypted && is_verified && decrypted_status !== true && decrypted_status !== 'processing') {
run_decryption_workflow();
}
});
// Better decrypted content and history handling
$effect(() => {
if (
$lq__journal_entry_obj?.content_encrypted &&
$journals_sess?.journal_kv[$lq__journal_obj?.journal_id]?.journal_passcode_verified &&
!$journals_sess?.journal_kv[$lq__journal_obj?.journal_id]?.journal_passcode_decrypted
) {
let journal_key = null;
if (
$journals_sess.journal_kv[$lq__journal_obj?.journal_id]?.typed_journal_passcode
?.length
) {
journal_key = $journals_sess.journal_kv[$lq__journal_obj?.journal_id]?.typed_journal_passcode;
} else {
journal_key = $lq__journal_obj?.private_passcode;
}
// *** Actions
if (journal_key == null) {
console.log('TEST: No journal key available to decrypt the content.');
return;
}
async function run_decryption_workflow() {
const journal = $lq__journal_obj;
if (!journal?.id) return;
let content_decrypted = null;
let journal_key = $journals_sess.journal_kv[journal.id]?.typed_journal_passcode;
if (!journal_key?.length) journal_key = journal.private_passcode;
if (!journal_key) return;
$journals_sess.journal_kv[$lq__journal_obj?.journal_id].journal_passcode_decrypted =
'processing';
is_processing = true;
decryption_error = null;
journals_sess.update(s => {
if (!s.journal_kv[journal.id]) s.journal_kv[journal.id] = {};
s.journal_kv[journal.id].journal_passcode_decrypted = 'processing';
return s;
});
(async () => {
let result = await ae_util.decrypt_wrapper(
$lq__journal_entry_obj?.content_encrypted,
journal_key
);
content_decrypted = typeof result === 'string' ? result : '';
console.log('TEST: Decrypted content:', content_decrypted);
const decrypt_key = `${journal.passcode ?? ''}:${journal_key}`;
const result = await ae_util.decrypt_wrapper($lq__journal_entry_obj?.content_encrypted, decrypt_key);
$journals_sess.journal_kv[$lq__journal_obj?.journal_id].journal_passcode_decrypted =
true;
tmp_entry_obj.content = content_decrypted;
tmp_entry_obj.content_md_html = handle_marked(content_decrypted);
})();
if (result === false) {
decryption_error = 'Decryption failed. Incorrect passcode or corrupted data.';
journals_sess.update(s => {
s.journal_kv[journal.id].journal_passcode_verified = false;
s.journal_kv[journal.id].journal_passcode_decrypted = false;
return s;
});
is_processing = false;
return;
}
});
async function handle_content_decryption() {
if (
tmp_entry_obj?.private &&
$lq__journal_obj?.combined_passcode &&
tmp_entry_obj?.content_encrypted
) {
if (!tmp_entry_obj?.content) {
let result = await ae_util.decrypt_wrapper(
tmp_entry_obj?.content_encrypted,
$lq__journal_obj?.combined_passcode
);
tmp_entry_obj.content = typeof result === 'string' ? result : '';
tmp_entry_obj.content_md_html = await handle_marked(tmp_entry_obj?.content);
// SUCCESS
const decrypted_text = typeof result === 'string' ? result : '';
tmp_entry_obj.content = decrypted_text;
tmp_entry_obj.content_md_html = handle_marked(decrypted_text);
if (orig_entry_obj) orig_entry_obj.content = decrypted_text;
// Also decrypt history if present
if (tmp_entry_obj?.history_encrypted) {
let h_result = await ae_util.decrypt_wrapper(
tmp_entry_obj?.history_encrypted,
$lq__journal_obj?.combined_passcode
);
tmp_entry_obj.history = typeof h_result === 'string' ? h_result : '';
tmp_entry_obj.history_md_html = await handle_marked(tmp_entry_obj?.history);
}
$journals_sess.entry.decrypt_kv[$lq__journal_entry_obj?.journal_entry_id] = true;
} else {
// Clear
tmp_entry_obj.content = null;
tmp_entry_obj.content_md_html = null;
tmp_entry_obj.history = null;
tmp_entry_obj.history_md_html = null;
$journals_sess.entry.decrypt_kv[$lq__journal_entry_obj?.journal_entry_id] = false;
// Decrypt History
if ($lq__journal_entry_obj?.history_encrypted) {
const h_res = await ae_util.decrypt_wrapper($lq__journal_entry_obj.history_encrypted, decrypt_key);
if (h_res !== false) {
tmp_entry_obj.history = h_res;
if (orig_entry_obj) orig_entry_obj.history = h_res;
}
}
}
async function handle_history_decryption() {
if (tmp_entry_obj?.history_encrypted) {
// Determine key
let key = $lq__journal_obj?.private_passcode;
if (
$journals_sess.journal_kv[$lq__journal_obj?.journal_id]?.typed_journal_passcode
?.length
) {
key =
$journals_sess.journal_kv[$lq__journal_obj?.journal_id]?.typed_journal_passcode;
}
if (!key) key = $lq__journal_obj?.combined_passcode;
let result = await ae_util.decrypt_wrapper(tmp_entry_obj?.history_encrypted, key);
decrypted_history = typeof result === 'string' ? result : '';
tmp_entry_obj.history = decrypted_history;
tmp_entry_obj.history_md_html = await handle_marked(decrypted_history);
}
journals_sess.update(s => {
s.journal_kv[journal.id].journal_passcode_decrypted = true;
return s;
});
is_processing = false;
}
async function update_journal_entry() {
if (!$ae_loc.trusted_access) {
alert('You do not have permission to update this journal entry.');
return;
}
if (!$ae_loc.trusted_access) return;
save_status = 'saving';
let data_kv: key_val = {
alert: tmp_entry_obj?.alert,
personal: tmp_entry_obj?.personal,
private: tmp_entry_obj?.private,
professional: tmp_entry_obj?.professional,
public: tmp_entry_obj?.public,
template: tmp_entry_obj?.template,
hide: tmp_entry_obj?.hide,
priority: tmp_entry_obj?.priority,
enable: tmp_entry_obj?.enable,
// alert_msg: $lq__journal_entry_obj?.alert_msg ? false : true
alert_msg: tmp_entry_obj?.alert_msg,
category_code: tmp_entry_obj?.category_code,
summary: tmp_entry_obj?.summary,
content: tmp_entry_obj?.content,
content_encrypted: null, // This should only be generated below.
history: tmp_entry_obj?.history,
history_encrypted: null, // This should only be generated below.
data_json: tmp_entry_obj?.data_json,
sort: tmp_entry_obj?.sort,
group: tmp_entry_obj?.group,
archive_on: tmp_entry_obj?.archive_on,
name: tmp_entry_obj?.name,
tags: tmp_entry_obj?.tags
// Reference Standard: Explicit whitelist of editable fields
const data_kv: key_val = {
name: tmp_entry_obj.name,
content: tmp_entry_obj.content,
history: tmp_entry_obj.history,
tags: tmp_entry_obj.tags,
category_code: tmp_entry_obj.category_code,
summary: tmp_entry_obj.summary,
priority: tmp_entry_obj.priority,
private: tmp_entry_obj.private,
public: tmp_entry_obj.public,
personal: tmp_entry_obj.personal,
professional: tmp_entry_obj.professional,
alert: tmp_entry_obj.alert,
alert_msg: tmp_entry_obj.alert_msg,
hide: tmp_entry_obj.hide,
enable: tmp_entry_obj.enable,
sort: tmp_entry_obj.sort,
group: tmp_entry_obj.group,
data_json: tmp_entry_obj.data_json
};
if (tmp_entry_obj?.content || tmp_entry_obj?.content_encrypted) {
let decrypt_key = $lq__journal_obj.combined_passcode;
const decrypt_key = $lq__journal_obj.combined_passcode;
if (!tmp_entry_obj?.private) {
if (log_lvl) console.log('JournalEntry: Processing non-private content...');
if (tmp_entry_obj.content) {
data_kv.content = tmp_entry_obj?.content;
} else if (tmp_entry_obj.content_encrypted) {
let decrypted_content = await ae_util.decrypt_wrapper(
tmp_entry_obj?.content_encrypted,
decrypt_key
);
data_kv.content = typeof decrypted_content === 'string' ? decrypted_content : '';
} else {
data_kv.content = '';
}
let { left_over_string, cut_out_string } = handle_cut_string(data_kv.content);
data_kv.content = left_over_string;
data_kv.content_encrypted = null;
if (cut_out_string) {
let cut_prefix = `# Cut on ${ae_util.iso_datetime_formatter(new Date().toISOString(), 'datetime_iso_12_no_seconds')}\n`;
cut_out_string = cut_prefix + cut_out_string;
}
if (tmp_entry_obj?.history?.length && cut_out_string) {
data_kv.history = tmp_entry_obj?.history + '\n' + cut_out_string + '\n';
} else if (cut_out_string) {
data_kv.history = cut_out_string + '\n';
}
data_kv.history_encrypted = null;
} else if (tmp_entry_obj?.private) {
let { left_over_string, cut_out_string } = handle_cut_string(
tmp_entry_obj?.content
);
let content_enc_combined_data = await ae_util.encrypt_wrapper(
left_over_string,
decrypt_key
);
if (log_lvl) console.log('TEST: Encrypted string:', content_enc_combined_data);
data_kv.content_encrypted = content_enc_combined_data;
// Encryption Logic Transition
if (tmp_entry_obj.private) {
if (tmp_entry_obj.content) {
data_kv.content_encrypted = await ae_util.encrypt_wrapper(tmp_entry_obj.content, decrypt_key);
data_kv.content = null;
tmp_entry_obj.content = null;
if (tmp_entry_obj?.history_encrypted) {
let result = await ae_util.decrypt_wrapper(
tmp_entry_obj?.history_encrypted,
decrypt_key
);
decrypted_history = typeof result === 'string' ? result : '';
tmp_entry_obj.history = decrypted_history;
}
if (cut_out_string) {
let cut_prefix = `# Cut on ${ae_util.iso_datetime_formatter(new Date().toISOString(), 'datetime_iso_12_no_seconds')}\n`;
cut_out_string = cut_prefix + cut_out_string;
}
if (tmp_entry_obj?.history?.length && cut_out_string) {
data_kv.history = tmp_entry_obj?.history + '\n' + cut_out_string + '\n';
} else {
data_kv.history = cut_out_string + '\n';
}
if (log_lvl) console.log('TEST: Encrypting history...');
let history_enc_combined_data = await ae_util.encrypt_wrapper(
data_kv.history,
decrypt_key
);
data_kv.history_encrypted = history_enc_combined_data;
data_kv.history = null;
decrypted_history = '';
}
if (tmp_entry_obj.history) {
data_kv.history_encrypted = await ae_util.encrypt_wrapper(tmp_entry_obj.history, decrypt_key);
data_kv.history = null;
}
} else {
// Ensure we clear encrypted fields if privacy is disabled
data_kv.content_encrypted = null;
data_kv.history_encrypted = null;
}
// Call API to save the content
try {
const response = await 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
});
if (log_lvl) console.log('Journal entry updated successfully:', response);
// Sync orig_entry_obj immediately to stop the change detection loop
orig_entry_obj = { ...tmp_entry_obj };
updated_obj = true;
save_status = 'saved';
} catch (error) {
console.error('Error updating journal entry:', error);
save_status = 'unsaved';
alert('Failed to update journal entry.');
}
}
async function change_journal_id() {
if (!$ae_loc.trusted_access) {
alert('You do not have permission to update this journal entry.');
return;
}
let data_kv = { journal_id_random: tmp_entry_obj?.journal_id };
try {
await 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: log_lvl
log_lvl: 1
});
updated_obj = true;
updated_idb = false;
if (log_lvl) console.log('Journal entry journal_id changed successfully!');
orig_entry_obj = { ...tmp_entry_obj };
save_status = 'saved';
} catch (error) {
console.error('Error changing journal ID:', error);
alert('Failed to change journal ID.');
console.error('Update failed:', error);
save_status = 'unsaved';
}
$journals_slct.journal_id = tmp_entry_obj?.journal_id;
goto(`/journals/${tmp_entry_obj?.journal_id}`);
}
// *** Cryptographic Helpers (Simplified)
async function handle_decrypt_string(encrypted_string: string, passcode: string) {
if (log_lvl) console.log(`TEST: handle_decrypt_string: ${passcode}`, encrypted_string);
if (!encrypted_string) return '';
if (!passcode) return false;
let decrypted_string = await ae_util.decrypt_wrapper(encrypted_string, passcode);
if (log_lvl) console.log('TEST: Decrypted string:', decrypted_string);
return decrypted_string;
}
// return new_string and cut_string
function handle_cut_string(
old_string: string,
start_tag: string = '<cut>',
end_tag: string = '</cut>'
) {
let left_over_string = old_string; // Will be for the updated content field
let cut_out_string = ''; // Will be for the history field
if (old_string) {
let cut_index = old_string.indexOf(start_tag);
let cut_end_index = old_string.indexOf(end_tag);
if (cut_index !== -1) {
if (cut_end_index !== -1 && cut_end_index > cut_index) {
// Cut everything between the cut tags
cut_out_string = old_string
.substring(cut_index + start_tag.length, cut_end_index)
.trim();
left_over_string = (
old_string.substring(0, cut_index) +
' ' +
old_string.substring(cut_end_index + end_tag.length)
).trim();
} else {
// Cut everything after the cut tag
cut_out_string = old_string.substring(cut_index + start_tag.length).trim();
left_over_string = old_string.substring(0, cut_index).trim();
}
}
async function handle_content_decryption() {
if (!tmp_entry_obj.content) {
await run_decryption_workflow();
} else {
// Re-lock logic
is_processing = true;
tmp_entry_obj.content = null;
if (orig_entry_obj) orig_entry_obj.content = null;
journals_sess.update(s => {
s.journal_kv[$lq__journal_obj.id].journal_passcode_decrypted = false;
return s;
});
is_processing = false;
}
return { left_over_string, cut_out_string };
}
function handle_marked(text_string: string) {
if (!text_string) return '';
let cleaned_string = text_string.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/, '');
return marked.parse(cleaned_string);
function handle_marked(text: string) {
if (!text) return '';
return marked.parse(text.replace(/^[]/, ''));
}
let slct_hosted_file_kv: key_val = $state({});
let slct_hosted_file_id: any = $state(null);
let slct_hosted_file_obj: any = $state(null);
$effect(() => {
if (slct_hosted_file_id && slct_hosted_file_obj) {
console.log(`*** handle_hosted_file_selected() *** ${slct_hosted_file_id}`);
// We need to update the journal_entry_obj with the new file (for now just the first one).
async function change_journal_id() {
if (!$ae_loc.trusted_access) return;
await journals_func.update_ae_obj__journal_entry({
api_cfg: $ae_api,
journal_entry_id: $lq__journal_entry_obj?.journal_entry_id,
data_kv: { journal_id_random: tmp_entry_obj.journal_id }
});
goto(`/journals/${tmp_entry_obj.journal_id}`);
}
update_journal_entry();
slct_hosted_file_id = null;
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
class="
svelte_component ae_section ae_view journal_entry_obj view__journal_entry_obj
flex flex-col gap-1 grow items-center justify-start
w-full h-full
p-1 m-1
"
bind:clientHeight={$ae_loc.iframe_height_modal_body}
>
<section class="ae_view flex flex-col gap-2 w-full h-full p-1" bind:clientHeight={$ae_loc.iframe_height_modal_body}>
{#if $lq__journal_entry_obj}
<JournalEntry_Header
entry={$lq__journal_entry_obj}
journal={$lq__journal_obj}
journals_li={$lq__journal_obj_li}
bind:tmp_entry_obj
has_changed={tmp_entry_obj_changed}
has_changed={has_unsaved_changes}
onSave={update_journal_entry}
onDecrypt={handle_content_decryption}
onDecryptHistory={handle_history_decryption}
onDecryptHistory={() => {}}
onChangeJournal={change_journal_id}
onAppend={handle_append_start}
onPrepend={handle_prepend_start}
onAppend={() => { modal_mode = 'append'; show_append_modal = true; }}
onPrepend={() => { modal_mode = 'prepend'; show_append_modal = true; }}
{onShowExport}
{save_status}
{log_lvl}
/>
{#if decryption_error}
<div class="w-full p-4 bg-error-500/20 border-2 border-error-500 rounded-lg flex items-center justify-between shadow-2xl z-50 animate-bounce">
<div class="flex items-center gap-4 text-error-700 dark:text-error-300 font-bold">
<AlertCircle size="2.5em" />
<span class="text-xl">{decryption_error}</span>
</div>
<button class="btn btn-sm variant-filled-error shadow-lg font-bold" onclick={() => decryption_error = null}>
<XCircle size="1.2em" class="mr-2" /> Dismiss
</button>
</div>
{/if}
<section
class="
grow
basis-full
flex flex-col flex-wrap gap-1 items-center justify-center
h-full min-h-max max-h-full
w-full max-w-6xl
relative
bg-gray-100 dark:bg-gray-800
p-1 rounded-lg shadow-md
"
class:bg-yellow-50={$journals_loc.entry.edit_kv[
$lq__journal_entry_obj?.journal_entry_id
] == 'current'}
class:dark:bg-yellow-950={$journals_loc.entry.edit_kv[
$lq__journal_entry_obj?.journal_entry_id
] == 'current'}
>
<!-- Generic AI Toolset -->
<section class="grow relative bg-gray-100 dark:bg-gray-800 p-1 rounded-lg shadow-md overflow-hidden"
class:bg-yellow-50={$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'}>
<div class="absolute top-2 right-2 z-10">
<AE_AITools
content={tmp_entry_obj.content}
bind:summary={tmp_entry_obj.summary}
onSave={update_journal_entry}
bind:token={$journals_loc.llm__api_token}
bind:baseUrl={$journals_loc.llm__api_base_url}
bind:model={$journals_loc.llm__api_model}
bind:systemPrompt={$journals_loc.entry.llm__system_prompt}
bind:maxTokens={$journals_loc.entry.llm__max_tokens}
bind:temperature={$journals_loc.entry.llm__temperature}
onSyncConfig={() => {
$journals_loc.llm__api_base_url = $ae_loc.site_cfg_json?.llm__api_base_url ?? 'https://ai.dgrzone.com/api';
$journals_loc.llm__api_model = $ae_loc.site_cfg_json?.llm__api_model ?? 'dgrzone-deepseek-8b-quick';
$journals_loc.llm__api_token = $ae_loc.site_cfg_json?.llm__api_token ?? '';
$journals_loc.entry.llm__system_prompt = $ae_loc.site_cfg_json?.llm__system_prompt ?? '';
}}
{log_lvl}
/>
</div>
<!-- Main Content Editor/Viewer -->
<JournalEntry_Editor
entry={$lq__journal_entry_obj}
journal={$lq__journal_obj}
bind:tmp_entry_obj={tmp_entry_obj}
bind:editorView={editorView}
has_changed={tmp_entry_obj_changed}
updated_idb={updated_idb}
bind:tmp_entry_obj
bind:editorView
has_changed={has_unsaved_changes}
updated_idb={false}
onSave={update_journal_entry}
/>
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'history'}
<div
class="grow basis-full flex flex-col items-center justify-center h-full w-full max-w-6xl"
>
{#if $journals_sess?.show__content__journal_entry_history == 'view'}
<E_app_codemirror_v5
editable={false}
readonly={true}
content={tmp_entry_obj?.history ?? ''}
bind:new_content={tmp_entry_obj.history}
theme_mode={$ae_loc.theme_mode}
placeholder="History content..."
class="p-2 preset-outlined-success-400-600 hover:preset-tonal-surface shadow-lg rounded-lg"
/>
{:else if $journals_sess?.show__content__journal_entry_history == 'edit'}
<E_app_codemirror_v5
content={tmp_entry_obj?.history ?? ''}
bind:new_content={tmp_entry_obj.history}
theme_mode={$ae_loc.theme_mode}
placeholder="Edit history..."
class="p-2 preset-outlined-warning-300-700 shadow-lg rounded-lg bg-gray-100 text-gray-950 dark:bg-gray-800 dark:text-gray-100"
/>
{/if}
</div>
{/if}
</section>
<AE_MetadataFooter obj={tmp_entry_obj} />
@@ -643,17 +329,13 @@
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
}}
onUpdate={() => { show_append_modal = false; updated_obj = true; }}
{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">
The Journal Entry was not found or is not available to show.
</span>
</section>
<div class="p-20 text-center opacity-50 flex flex-col items-center gap-4">
<Loader2 class="animate-spin" size="4em" />
<span class="text-2xl font-bold">Loading Journal Entry...</span>
</div>
{/if}
</section>
</section>

View File

@@ -68,7 +68,15 @@
}
return;
}
console.log('Setting passcode timer');
// Use journal.passcode_timeout (assuming it's in minutes, default to 5)
const timeout_minutes = $lq__journal_obj?.passcode_timeout ?? 5;
const timeout_ms = 1000 * 60 * timeout_minutes;
if (log_lvl) {
console.log(`Setting passcode timer for ${timeout_minutes} minutes (${timeout_ms}ms)`);
}
passcode_timer = setTimeout(
() => {
if (log_lvl) {
@@ -78,12 +86,15 @@
if (!$journals_sess?.journal_kv[$lq__journal_obj?.id]) {
$journals_sess.journal_kv[$lq__journal_obj?.id] = {};
}
$journals_sess.journal_kv[$lq__journal_obj?.id].journal_passcode_verified =
false;
// Reset verification and decryption flags
$journals_sess.journal_kv[$lq__journal_obj?.id].journal_passcode_verified = false;
$journals_sess.journal_kv[$lq__journal_obj?.id].journal_passcode_decrypted = false;
passcode_timer = null;
},
1000 * 60 * 1
); // 1 minutes
// }, 1000 * $lq__journal_obj?.passcode_timeout); // 5 minutes
timeout_ms
);
}
});