Files
OSIT-AE-App-Svelte/src/routes/journals/ae_comp__journal_obj_id_view.svelte
Scott Idem 9b8bc7cb73 feat(journals): redesign list + entry views with glow, dark mode, compact mobile-first layout
- ae_comp__journal_obj_li: compact tap-target rows, glow wrapper, plain gray dark mode (no Skeleton paired utils)
- +page.svelte (journals index): fix 'Managed by Account Name Not Set' bug, responsive spacing, glow on quick-add
- ae_comp__journal_obj_id_view: glow wrapper, explicit bg-white/dark:bg-gray-900, remove broken dynamic color class
- ae_comp__journal_entry_obj_li: glow wrapper, left-accent border on cards, fix dark mode hover
- ae_comp__journal_entry_obj_id_view: glow wrapper, consistent gray dark mode, edit-mode ring indicator
- ae_comp__journal_entry_header: replace Skeleton surface vars with plain grays (dark mode fix)
- e_app_theme: fix theme selector panel readability in dark mode
2026-03-13 19:48:23 -04:00

286 lines
9.4 KiB
Svelte

<script lang="ts">
// *** Import Svelte specific
import { goto } from '$app/navigation';
// *** Import other supporting libraries
import {
BookPlus,
BookOpenText,
FilePlus,
Menu,
Pencil,
FileDown,
FileUp,
Settings,
LoaderCircle
} from 'lucide-svelte';
// *** Import Aether specific variables and functions
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import {
journals_loc,
journals_sess,
journals_slct,
journals_trig,
journals_prom
} from '$lib/ae_journals/ae_journals_stores';
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import Journal_obj_id_edit from './ae_comp__journal_obj_id_edit.svelte';
interface Props {
log_lvl?: number;
lq__journal_obj: any;
lq__journal_entry_obj_li: any;
on_show_export?: () => void;
on_show_import?: () => void;
}
let {
log_lvl = 0,
lq__journal_obj,
lq__journal_entry_obj_li,
on_show_export,
on_show_import
}: Props = $props();
// let ae_promises: key_val = {};
// let ae_tmp: key_val = {};
// let ae_trigger: any = null;
// let ae_triggers: key_val = {};
let typed_journal_passcode: string = $state('');
let passcode_timer: any = $state(null);
$effect(() => {
if (typed_journal_passcode?.length > 4) {
if (!$journals_sess?.journal_kv) {
$journals_sess.journal_kv = {};
}
if (!$journals_sess.journal_kv[$lq__journal_obj?.id]) {
$journals_sess.journal_kv[$lq__journal_obj?.id] = {};
}
verify_journal_passcode();
}
// We need to set a timeout to force the user to re-enter their private passcode
if (
$lq__journal_obj?.id &&
$journals_sess?.journal_kv[$lq__journal_obj?.id] &&
$journals_sess?.journal_kv[$lq__journal_obj?.id]
?.journal_passcode_verified
) {
if (passcode_timer) {
if (log_lvl) {
console.log('Passcode timer already set');
}
return;
}
// 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) {
console.log('Passcode timer expired');
}
typed_journal_passcode = '';
if (!$journals_sess?.journal_kv[$lq__journal_obj?.id]) {
$journals_sess.journal_kv[$lq__journal_obj?.id] = {};
}
// 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;
}, timeout_ms);
}
});
function verify_journal_passcode() {
if (log_lvl) {
console.log(
`verify_journal_passcode: typed_journal_passcode = ${typed_journal_passcode} journal private passcode = ${$lq__journal_obj?.private_passcode}`
);
}
if (typed_journal_passcode === $lq__journal_obj?.private_passcode) {
console.log('Matched journal private passcode');
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] = {
typed_journal_passcode: typed_journal_passcode,
journal_passcode_verified: true
};
typed_journal_passcode = '';
} else {
}
}
async function handle_new_entry() {
let data_kv = {
category_code: null
};
if ($journals_loc.entry.qry__category_code) {
data_kv.category_code = $journals_loc.entry.qry__category_code;
}
try {
const results = await journals_func.create_ae_obj__journal_entry({
api_cfg: $ae_api,
journal_id: $lq__journal_obj?.journal_id,
data_kv: data_kv,
log_lvl: log_lvl
});
if (results?.journal_entry_id) {
$journals_slct.journal_entry_id =
results.journal_entry_id;
$journals_loc.entry.edit_kv[$journals_slct.journal_entry_id] =
'current';
goto(
`/journals/${$lq__journal_obj?.journal_id}/entry/${results.journal_entry_id}`
);
}
} catch (error) {
console.error('Error creating journal entry:', error);
alert('Failed to create new journal entry.');
}
}
</script>
<div class="relative group/view w-full mx-2 my-1">
<!-- Glow ring — mirrors Quick Add and Journal List -->
<div
class="absolute -inset-1 bg-linear-to-r from-primary-500 to-secondary-500
rounded-2xl blur opacity-10 dark:opacity-20
group-hover/view:opacity-25 dark:group-hover/view:opacity-35
transition duration-700 pointer-events-none"
></div>
<section
class="relative rounded-xl p-3 w-full
flex flex-col gap-2 items-center justify-center
bg-white dark:bg-gray-900
border border-gray-200 dark:border-gray-700
text-gray-900 dark:text-gray-100
shadow-xl"
bind:clientHeight={$ae_loc.iframe_height_modal_body}
>
<header
class="ae_header journal__header flex flex-row flex-wrap gap-2 items-center justify-between w-full"
>
<h2 class="journal__name h3 text-center">
<BookOpenText class="inline-block text-primary-500/80" />
{@html $lq__journal_obj?.name ?? 'Loading...'}
{#if $ae_loc.edit_mode}
<span
class="badge preset-tonal-success font-bold text-lg px-2 ml-2"
title="Entries matching current filters"
>
{$lq__journal_entry_obj_li?.length ?? '0'}<span
class="text-xs opacity-50 ml-0.5">&times;</span
>
</span>
{/if}
{#await $journals_prom.load__journal_entry_obj_li}
<LoaderCircle
size="1em"
class="inline-block animate-spin ml-1 text-primary-500"
/>
{/await}
</h2>
<div
class="grow flex flex-row flex-wrap gap-2 items-center justify-end"
>
<!-- Simplified Config Trigger -->
<button
type="button"
onclick={() =>
($journals_sess.show__modal_edit__journal_obj = true)}
class="btn preset-tonal-secondary py-1 px-3 shadow-md"
title="Journal Config & Actions"
>
<Settings size="1.2em" class="mr-2" />
<span class="hidden md:inline">Config</span>
</button>
<!-- Passcode Verification (Condensed) -->
{#if !$journals_sess?.journal_kv[$lq__journal_obj?.id]?.journal_passcode_verified}
<div class="flex gap-1">
<input
autocomplete="off"
type="text"
bind:value={typed_journal_passcode}
placeholder="Passcode"
class="input input-sm w-32"
/>
</div>
{/if}
</div>
</header>
<!-- Show Journal description -->
{#if $lq__journal_obj?.description && $ae_loc.edit_mode}
<div
class="
prose
space-y-1
p-2
w-full max-w-(--breakpoint-sm) md:max-w-(--breakpoint-md)
font-mono
bg-gray-50 text-gray-900
dark:bg-gray-800 dark:text-gray-100
shadow-md rounded-lg
text-sm font-normal text-wrap word-break
prose-p:m-0 prose-p:p-0
prose-h1:underline prose-h1:decoration-double
prose-h2:underline
prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg
prose-h1:m-0 prose-h2:m-0 prose-h3:m-0 prose-h4:m-0 prose-h5:m-0 prose-h6:m-0
prose-li:m-0 prose-li:p-0 prose-li:line-height-none
"
>
{@html $lq__journal_obj.description_md_html}
</div>
{/if}
</section>
</div>
<!-- Standardized Journal Action/Config Modal -->
<Journal_obj_id_edit
{log_lvl}
{lq__journal_obj}
bind:show={$journals_sess.show__modal_edit__journal_obj}
on_new_entry={handle_new_entry}
{on_show_export}
{on_show_import}
/>