Compare commits
5 Commits
07dd213cfd
...
409308d2be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
409308d2be | ||
|
|
62cc26d1f9 | ||
|
|
8b087edeb9 | ||
|
|
54707a00e3 | ||
|
|
e5c8500bc1 |
@@ -191,7 +191,7 @@ This pattern lives in `ae_comp__badge_obj_li.svelte` — move to `ae_utils` if n
|
||||
- Entry configuration admin controls are gated to `trusted_access` and above.
|
||||
- `manager_access` and `administrator_access` see the Delete action, which performs a hard delete.
|
||||
- `trusted_access` users see Remove instead, which follows disable semantics rather than a hard delete.
|
||||
- The Admin section is the place for staff notes, enabled/default access state, and destructive entry actions; visibility/privacy flags remain separate.
|
||||
- The Admin section is the place for staff notes, enabled/default access state, and destructive entry actions; the template toggle belongs in Metadata, while visibility/audience flags remain separate.
|
||||
|
||||
### Events — Badges
|
||||
|
||||
|
||||
@@ -326,9 +326,9 @@ These are real incidents — know them before you start.
|
||||
See `GUIDE__AE_API_V3_for_Frontend.md` → section 3C for the full explanation.
|
||||
|
||||
12. **Broad Dexie result windows get silently clipped** — if a broad "All" view shows fewer
|
||||
rows than a narrower filter, check for a page-level limit or an API revalidation step
|
||||
replacing the local IDB result set. For empty text searches, the full local result set
|
||||
should drive the display; server refreshes should update cache, not shrink visibility.
|
||||
rows than a narrower filter, check for a page-level limit or an API revalidation step
|
||||
replacing the local IDB result set. For empty text searches, the full local result set
|
||||
should drive the display; server refreshes should update cache, not shrink visibility.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -461,6 +461,8 @@ Moderation permissions are controlled by `novi_jitsi_mod_li` in the IDAA store.
|
||||
|
||||
An admin/staff reporting tool that aggregates raw Jitsi activity logs into human-readable meeting sessions. It is **not** a member-facing page — IDAA members do not see it.
|
||||
|
||||
**Reminder:** this page is still incomplete. We still need the Novi UUID filter to work and we still need meeting-name whitelist filtering.
|
||||
|
||||
### View Modes
|
||||
|
||||
Two display modes, toggled via a button in the page header:
|
||||
@@ -501,6 +503,8 @@ A "Reset Filters" button appears whenever any filter is non-default.
|
||||
|
||||
**Why display-name matching (not Novi UUID):** Jitsi participant data (`meta_json.participants`) only contains `displayName` and `role` — the Novi UUID is not passed through to the activity log. UUID-based exclusion would require a Jitsi config change plus a backend schema update and is deferred. Display names for OSIT staff are stable and controlled.
|
||||
|
||||
**Still pending:** the new Jitsi Reports page still needs a real Novi UUID filter and a whitelist of meeting names so staff can narrow the report set without relying only on display names.
|
||||
|
||||
### Summary Stats
|
||||
|
||||
Shown above the meeting list when data is loaded. Stats reflect the **filtered + exclusion-applied** view:
|
||||
|
||||
@@ -151,9 +151,9 @@ Svelte 5 state is backed by Proxies.
|
||||
|
||||
### 5. Journal Entry Config Layout Notes
|
||||
The Entry Config modal now follows a stricter section grammar:
|
||||
* `Metadata` contains category, tags, summary, and archive date.
|
||||
* `Metadata` contains category, tags, summary, archive date, and template.
|
||||
* `Status & Security` contains enabled/hidden/priority/sort.
|
||||
* `Privacy Flags` contains only visibility/audience toggles.
|
||||
* `Visibility & Audience` contains only visibility/audience toggles.
|
||||
* `Alerts & Messaging` contains alert flag + alert message.
|
||||
* `Admin` is gated to trusted access and above, and is the only place for notes plus delete/remove actions.
|
||||
|
||||
|
||||
@@ -118,6 +118,22 @@ suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a pac
|
||||
Run `npx svelte-check 2>&1 | grep ModalProps` to get the current list.
|
||||
Fix pattern: replace `children` prop binding with Svelte snippet syntax per flowbite-svelte docs.
|
||||
|
||||
### [Journals] Journal Entry Config follow-ups
|
||||
|
||||
- [ ] **[Journals] Visibility / audience toggle contrast** — the flag buttons need a clearer
|
||||
selected state in both light and dark mode.
|
||||
- [ ] **[Journals] Footer button style** — the actual `Done` button should read like a real button,
|
||||
not a seamless footer spacer.
|
||||
- [ ] **[Journals] Entry passcode secondary auth** — `passcode_hash` stores a hash; compare the
|
||||
entered passcode hash to the stored hash, gate entry loading, and honor the TTL-based access
|
||||
window. This is secondary entry auth, not a plain-text passcode field.
|
||||
- [ ] **[Journals] Summary AI shortcut** — add an AI summarize button next to Entry Details
|
||||
Summary so staff can generate a summary directly from the modal.
|
||||
- [ ] **[Journals] Archive On sizing** — constrain the Archive On control to a reasonable width
|
||||
instead of letting it expand to full width.
|
||||
- [ ] **[Journals] Archive On behavior** — define what Archive On actually means and wire the
|
||||
behavior; it is currently just a UI field with no live effect.
|
||||
|
||||
- [x] **[IDAA] Do not cache IDAA data in IDB when access is denied (2026-04-19, audited 2026-04-28)**
|
||||
Full audit confirmed all protection layers are in place. No code changes required.
|
||||
- All `+page.ts` / `+layout.ts` under `src/routes/idaa/` are clean — no SWR loads run before auth resolves.
|
||||
@@ -168,6 +184,11 @@ suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a pac
|
||||
(`PATCH /v3/crud/site/{id}/`), reload `$ae_loc.site_cfg_json` on save so it takes
|
||||
effect without re-login.
|
||||
|
||||
### [IDAA] Jitsi Reports still incomplete
|
||||
- [ ] **Finish Jitsi Reports filters** — the new Jitsi Reports page still needs a working Novi UUID
|
||||
filter and meeting-name whitelist filtering so staff can narrow the report set without relying on
|
||||
display-name matching alone.
|
||||
|
||||
### [PWA] Service worker ignoring `chrome-extension://` requests
|
||||
Browser console shows repeated errors:
|
||||
```text
|
||||
|
||||
@@ -24,8 +24,8 @@ import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.s
|
||||
|
||||
interface Props {
|
||||
// Core Props
|
||||
content: string; // The text to summarize/analyze
|
||||
summary: string; // The result (bindable)
|
||||
content: string | null | undefined; // The text to summarize/analyze
|
||||
summary: string | null | undefined; // The result (bindable)
|
||||
|
||||
// Configuration (Bindable for global settings persistence)
|
||||
model?: string;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import {
|
||||
Siren,
|
||||
Fingerprint,
|
||||
FingerprintPattern,
|
||||
Globe,
|
||||
BookHeart,
|
||||
BriefcaseBusiness,
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
Settings
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import type { ae_JournalEntry } from '$lib/types/ae_types';
|
||||
import type { ae_JournalEntryDraft } from '$lib/types/ae_types';
|
||||
|
||||
interface Props {
|
||||
// The object containing the flags (bindable)
|
||||
obj: ae_JournalEntry;
|
||||
obj: ae_JournalEntryDraft;
|
||||
|
||||
// Visibility configuration (optional overrides)
|
||||
show_labels?: boolean;
|
||||
@@ -109,8 +109,8 @@ function toggle_template() {
|
||||
onclick={toggle_private}
|
||||
class="btn btn-sm flex items-center gap-2 px-3 transition preset-tonal-secondary hover:preset-filled-secondary-500"
|
||||
title="Toggle private or encrypted visibility">
|
||||
<Fingerprint size="1.2em" class={obj?.private ? 'text-success-500' : 'opacity-40'} />
|
||||
<span class="whitespace-nowrap text-[10px] font-bold uppercase tracking-wider">Private</span>
|
||||
<FingerprintPattern size="1.2em" class={obj?.private ? 'text-success-500' : 'opacity-40'} />
|
||||
<span class="whitespace-nowrap text-[10px] font-bold uppercase tracking-wider">Private or Encrypt</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ensure_CodeMirror_modules } from './codemirror_modules';
|
||||
// Icons (Standardized to Lucide where possible, or FontAwesome placeholders)
|
||||
import { Bold, Code, Italic, List } from '@lucide/svelte';
|
||||
interface Props {
|
||||
content?: string;
|
||||
content?: string | null;
|
||||
new_content?: string;
|
||||
placeholder?: string;
|
||||
theme_mode?: 'light' | 'dark';
|
||||
|
||||
@@ -230,6 +230,30 @@ export interface ae_JournalEntry extends ae_BaseObj {
|
||||
file_count?: number;
|
||||
}
|
||||
|
||||
export type ae_JournalEntryDraft = Omit<
|
||||
Partial<ae_JournalEntry>,
|
||||
| 'journal_entry_id'
|
||||
| 'journal_id'
|
||||
| 'name'
|
||||
| 'summary'
|
||||
| 'content'
|
||||
| 'content_md_html'
|
||||
| 'content_encrypted'
|
||||
| 'history'
|
||||
| 'history_encrypted'
|
||||
> & {
|
||||
journal_entry_id?: string;
|
||||
journal_id?: string;
|
||||
name?: string | null;
|
||||
summary?: string | null;
|
||||
content?: string | null;
|
||||
content_md_html?: string | null;
|
||||
content_encrypted?: string | null;
|
||||
history?: string | null;
|
||||
history_encrypted?: string | null;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* Person - A human entity
|
||||
*/
|
||||
|
||||
@@ -246,7 +246,7 @@ async function create_journal() {
|
||||
|
||||
{#if $journals_sess.show__modal__journals_config}
|
||||
<AE_Comp_Modal_Journal_Config
|
||||
show={$journals_sess.show__modal__journals_config} />
|
||||
bind:show={$journals_sess.show__modal__journals_config} />
|
||||
{/if}
|
||||
|
||||
<AE_Comp_Modal_Journal_Import
|
||||
|
||||
@@ -83,11 +83,11 @@ $effect(() => {
|
||||
class="
|
||||
ae_journals__journal
|
||||
mx-auto
|
||||
flex w-full max-w-none min-w-0
|
||||
flex min-h-0 w-full max-w-none
|
||||
min-w-0
|
||||
flex-col
|
||||
items-stretch
|
||||
gap-1
|
||||
min-h-0
|
||||
space-y-2
|
||||
">
|
||||
<div
|
||||
@@ -197,12 +197,7 @@ Middle-click to open in new tab`}>
|
||||
.length}× Recent Entries...
|
||||
</option>
|
||||
<!-- loop through each key value -->
|
||||
{#each Object.entries(
|
||||
$journals_loc.entry_view_history_kv as Record<
|
||||
string,
|
||||
{ id: string; name: string; url: string }
|
||||
>
|
||||
).reverse() as [journal_entry_id, journal_entry_obj] (journal_entry_id)}
|
||||
{#each Object.entries($journals_loc.entry_view_history_kv as Record<string, { id: string; name: string; url: string }>).reverse() as [journal_entry_id, journal_entry_obj] (journal_entry_id)}
|
||||
<option value={journal_entry_obj.id}>
|
||||
{(journal_entry_obj?.name ||
|
||||
journal_entry_obj?.id) ??
|
||||
@@ -237,7 +232,9 @@ Middle-click to open in new tab`}>
|
||||
</a>
|
||||
{:else}
|
||||
<!-- Edit Journal button. Creates a modal to edit the journal. -->
|
||||
<Journal_entry_obj_qry {log_lvl} lq__journal_obj={$lq__journal_obj} />
|
||||
<Journal_entry_obj_qry
|
||||
{log_lvl}
|
||||
lq__journal_obj={$lq__journal_obj} />
|
||||
{/if}
|
||||
|
||||
<!-- Add default journal entry -->
|
||||
|
||||
@@ -114,7 +114,10 @@ let lq__journal_entry_obj_li = $derived(
|
||||
const journal_id = $lq__journal_obj?.journal_id;
|
||||
|
||||
return liveQuery(async () => {
|
||||
if (params.remote_first && (!Array.isArray(ids) || ids.length === 0)) {
|
||||
if (
|
||||
params.remote_first &&
|
||||
(!Array.isArray(ids) || ids.length === 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -258,14 +261,11 @@ async function handle_search_refresh(params: JournalSearchParams) {
|
||||
const api_ids = api_results
|
||||
.map((entry) => entry.id || entry.journal_entry_id)
|
||||
.filter((entry): entry is string => Boolean(entry));
|
||||
const display_ids = !qry_str && local_ids.length > 0 ? local_ids : api_ids;
|
||||
const display_ids =
|
||||
!qry_str && local_ids.length > 0 ? local_ids : api_ids;
|
||||
|
||||
// Protect UI cache if API returns empty during revalidation
|
||||
if (
|
||||
!qry_str &&
|
||||
local_ids.length > 0 &&
|
||||
api_ids.length === 0
|
||||
) {
|
||||
if (!qry_str && local_ids.length > 0 && api_ids.length === 0) {
|
||||
untrack(() => {
|
||||
$journals_sess.entry.qry__status = 'done';
|
||||
});
|
||||
|
||||
@@ -278,8 +278,8 @@ $effect(() => {
|
||||
flex-col
|
||||
items-stretch
|
||||
gap-1
|
||||
px-2 md:px-4
|
||||
space-y-2
|
||||
space-y-2 px-2
|
||||
md:px-4
|
||||
">
|
||||
<!-- {#if $lq__journal_entry_obj} -->
|
||||
<Journal_entry_view
|
||||
|
||||
@@ -9,7 +9,7 @@ import { journals_loc } from '$lib/ae_journals/ae_journals_stores';
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
summary: string; // Bindable
|
||||
summary: string | null | undefined; // Bindable
|
||||
on_save: () => void;
|
||||
log_lvl?: number;
|
||||
}
|
||||
@@ -18,7 +18,8 @@ let { content, summary = $bindable(), on_save, log_lvl = 0 }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="journal-entry-floating-actions absolute top-14 right-2 z-10">
|
||||
<div class="journal-entry-ai-tools inline-flex flex-wrap items-center justify-end gap-1">
|
||||
<div
|
||||
class="journal-entry-ai-tools inline-flex flex-wrap items-center justify-end gap-1">
|
||||
<AE_AITools
|
||||
{content}
|
||||
model={$journals_loc.llm__api_model}
|
||||
|
||||
@@ -8,17 +8,16 @@ 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';
|
||||
import type { ae_JournalEntry, ae_Journal } from '$lib/types/ae_types';
|
||||
|
||||
type JournalEntryDraft = Partial<ae_JournalEntry> & {
|
||||
content?: string | false;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
import type {
|
||||
ae_JournalEntry,
|
||||
ae_Journal,
|
||||
ae_JournalEntryDraft
|
||||
} from '$lib/types/ae_types';
|
||||
|
||||
interface Props {
|
||||
entry: ae_JournalEntry;
|
||||
journal: ae_Journal;
|
||||
tmp_entry_obj: JournalEntryDraft; // Bindable
|
||||
tmp_entry_obj: ae_JournalEntryDraft; // Bindable
|
||||
editor_view?: unknown; // Bindable
|
||||
has_changed: boolean;
|
||||
updated_idb: boolean;
|
||||
@@ -40,7 +39,9 @@ let {
|
||||
const is_editing = $derived(
|
||||
$journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current'
|
||||
);
|
||||
const preferred_viewer = $derived((journal?.cfg_json?.pref_viewer ?? 'rendered').toLowerCase());
|
||||
const preferred_viewer = $derived(
|
||||
(journal?.cfg_json?.pref_viewer ?? 'rendered').toLowerCase()
|
||||
);
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -59,7 +60,7 @@ const preferred_viewer = $derived((journal?.cfg_json?.pref_viewer ?? 'rendered')
|
||||
class_li="w-full rounded-lg bg-surface-50 shadow-lg dark:bg-surface-800" />
|
||||
{:else if preferred_viewer === 'plain'}
|
||||
<div
|
||||
class="bg-surface-50 dark:bg-surface-800 text-surface-800 dark:text-surface-100 w-full rounded-lg border border-gray-200 p-4 font-mono text-sm whitespace-pre-wrap wrap-break-word shadow-lg dark:border-gray-700">
|
||||
class="bg-surface-50 dark:bg-surface-800 text-surface-800 dark:text-surface-100 w-full rounded-lg border border-gray-200 p-4 font-mono text-sm wrap-break-word whitespace-pre-wrap shadow-lg dark:border-gray-700">
|
||||
{tmp_entry_obj?.content || ''}
|
||||
</div>
|
||||
{:else}
|
||||
@@ -115,7 +116,7 @@ const preferred_viewer = $derived((journal?.cfg_json?.pref_viewer ?? 'rendered')
|
||||
{:else}
|
||||
<textarea
|
||||
bind:value={tmp_entry_obj.content}
|
||||
class="textarea h-125 w-full grow rounded-lg border-orange-500/30 p-4 font-mono whitespace-pre-wrap wrap-break-word shadow-lg"
|
||||
class="textarea h-125 w-full grow rounded-lg border-orange-500/30 p-4 font-mono wrap-break-word whitespace-pre-wrap shadow-lg"
|
||||
placeholder="Edit content..."></textarea>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -100,11 +100,12 @@ function toggle_edit_mode() {
|
||||
<input
|
||||
type="text"
|
||||
bind:value={tmp_entry_obj.name}
|
||||
class="input input-sm h-10 min-w-0 flex-1 border-none bg-transparent text-lg font-bold leading-none transition-colors duration-150 focus:ring-2 focus:ring-primary-500"
|
||||
class="input input-sm focus:ring-primary-500 h-10 min-w-0 flex-1 border-none bg-transparent text-lg leading-none font-bold transition-colors duration-150 focus:ring-2"
|
||||
placeholder="Entry Title..."
|
||||
onchange={on_save} />
|
||||
{:else}
|
||||
<h2 class="flex h-10 min-w-0 flex-1 items-center truncate text-base font-bold leading-none md:text-lg">
|
||||
<h2
|
||||
class="flex h-10 min-w-0 flex-1 items-center truncate text-base leading-none font-bold md:text-lg">
|
||||
{entry.name ||
|
||||
ae_util.iso_datetime_formatter(
|
||||
entry.created_on,
|
||||
@@ -115,14 +116,16 @@ function toggle_edit_mode() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex shrink-0 items-center gap-2 whitespace-nowrap transition-colors duration-150">
|
||||
<div
|
||||
class="ml-auto flex shrink-0 items-center gap-2 whitespace-nowrap transition-colors duration-150">
|
||||
<!-- Auto-Save indicator -->
|
||||
{#if $journals_loc.entry.edit_kv[entry.journal_entry_id] === 'current'}
|
||||
<div
|
||||
class="border-surface-500/20 mr-1 flex items-center gap-1 border-r px-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-icon btn-icon-sm transition-colors duration-150 {$journals_loc.entry.auto_save
|
||||
class="btn-icon btn-icon-sm transition-colors duration-150 {$journals_loc
|
||||
.entry.auto_save
|
||||
? 'text-primary-500'
|
||||
: 'opacity-30'}"
|
||||
onclick={() =>
|
||||
|
||||
@@ -231,7 +231,8 @@ async function handle_remove_file(file_id: string) {
|
||||
file.hosted_file_id || file.id || file.hosted_file_id}
|
||||
<div
|
||||
class="bg-surface-50-950 border-surface-500/10 group hover:border-primary-500 flex items-center justify-between rounded-xl border p-3 shadow-sm transition-all">
|
||||
<div class="flex grow items-center gap-3 overflow-hidden">
|
||||
<div
|
||||
class="flex grow items-center gap-3 overflow-hidden">
|
||||
<AE_Comp_Hosted_Files_Download_Button
|
||||
hosted_file_id={file_id}
|
||||
hosted_file_obj={file}
|
||||
@@ -281,7 +282,9 @@ async function handle_remove_file(file_id: string) {
|
||||
<FileUp size="1.8em" />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="font-bold">Upload new attachment</p>
|
||||
<p class="font-bold">
|
||||
Upload new attachment
|
||||
</p>
|
||||
<p class="text-xs opacity-60">
|
||||
Drag and drop or click to browse
|
||||
</p>
|
||||
|
||||
@@ -31,6 +31,7 @@ import AE_Comp_Journal_Entry_AiTools from './ae_comp__journal_entry_ai_tools.sve
|
||||
import AE_Comp_Journal_Entry_ObjFileLi from './ae_comp__journal_entry_obj_file_li.svelte';
|
||||
import AE_Comp_Modal_Journal_Entry_Append from './ae_comp__modal_journal_entry_append.svelte';
|
||||
import AE_Comp_Modal_Journal_Entry_Config from './ae_comp__modal_journal_entry_config.svelte';
|
||||
import type { ae_JournalEntryDraft } from '$lib/types/ae_types';
|
||||
|
||||
// Icons
|
||||
import { CircleAlert, CircleX, LoaderCircle } from '@lucide/svelte';
|
||||
@@ -53,8 +54,8 @@ let {
|
||||
|
||||
// *** State
|
||||
let editor_view: any = $state();
|
||||
let orig_entry_obj: key_val | null = $state(null);
|
||||
let tmp_entry_obj: key_val = $state({});
|
||||
let orig_entry_obj: ae_JournalEntryDraft | null = $state(null);
|
||||
let tmp_entry_obj: ae_JournalEntryDraft = $state({});
|
||||
let save_status: 'saved' | 'unsaved' | 'saving' = $state('saved');
|
||||
let decryption_error: string | null = $state(null);
|
||||
let auto_save_timer: ReturnType<typeof setTimeout>;
|
||||
@@ -65,7 +66,7 @@ let show_config_modal = $state(false);
|
||||
function deep_copy(obj: any) {
|
||||
if (!obj) return null;
|
||||
try {
|
||||
const copy: key_val = {};
|
||||
const copy: ae_JournalEntryDraft = {};
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
if (val instanceof Date) {
|
||||
@@ -240,9 +241,12 @@ async function run_decryption_workflow() {
|
||||
}
|
||||
|
||||
// SUCCESS
|
||||
const content = result.content || '';
|
||||
const content =
|
||||
typeof result.content === 'string'
|
||||
? result.content
|
||||
: await (result.content ?? '');
|
||||
tmp_entry_obj.content = content;
|
||||
tmp_entry_obj.content_md_html = handle_marked(content);
|
||||
tmp_entry_obj.content_md_html = await handle_marked(content);
|
||||
tmp_entry_obj.content_encrypted = null;
|
||||
tmp_entry_obj.history_encrypted = null;
|
||||
|
||||
@@ -303,8 +307,12 @@ async function update_journal_entry(fields_kv?: key_val) {
|
||||
if (!fields_kv) {
|
||||
if (tmp_entry_obj.private) {
|
||||
if (tmp_entry_obj.content) {
|
||||
const content_to_encrypt =
|
||||
typeof tmp_entry_obj.content === 'string'
|
||||
? tmp_entry_obj.content
|
||||
: '';
|
||||
data_kv.content_encrypted = await ae_util.encrypt_wrapper(
|
||||
tmp_entry_obj.content,
|
||||
content_to_encrypt,
|
||||
decrypt_key
|
||||
);
|
||||
data_kv.content = null;
|
||||
@@ -316,9 +324,15 @@ async function update_journal_entry(fields_kv?: key_val) {
|
||||
}
|
||||
|
||||
try {
|
||||
const journal_entry_id = $lq__journal_entry_obj?.journal_entry_id;
|
||||
if (!journal_entry_id) {
|
||||
console.error('Journal entry ID missing for update.');
|
||||
return;
|
||||
}
|
||||
|
||||
await journals_func.update_ae_obj__journal_entry({
|
||||
api_cfg: $ae_api,
|
||||
journal_entry_id: $lq__journal_entry_obj?.journal_entry_id,
|
||||
journal_entry_id,
|
||||
data_kv: data_kv,
|
||||
log_lvl: 1
|
||||
});
|
||||
@@ -424,9 +438,9 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
group-hover/entry-view:opacity-15 dark:opacity-10 dark:group-hover/entry-view:opacity-20">
|
||||
</div>
|
||||
<section
|
||||
class="ae_view relative flex w-full min-w-0 flex-col gap-4 rounded-xl
|
||||
border border-surface-200-800
|
||||
bg-surface-50-900 p-2 shadow-xl"
|
||||
class="ae_view border-surface-200-800 bg-surface-50-900 relative flex w-full min-w-0 flex-col
|
||||
gap-4 rounded-xl
|
||||
border p-2 shadow-xl"
|
||||
bind:clientHeight={$ae_loc.iframe_height_modal_body}>
|
||||
{#if $lq__journal_entry_obj && $lq__journal_obj}
|
||||
<AE_Comp_Journal_Entry_Header
|
||||
@@ -445,7 +459,8 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
{#if decryption_error}
|
||||
<div
|
||||
class="bg-error-100 border-error-300 flex w-full animate-bounce items-center justify-between rounded-lg border p-4 shadow-xl">
|
||||
<div class="text-error-700 flex items-center gap-4 font-bold">
|
||||
<div
|
||||
class="text-error-700 flex items-center gap-4 font-bold">
|
||||
<CircleAlert size="2.5em" />
|
||||
<span class="text-xl">{decryption_error}</span>
|
||||
</div>
|
||||
@@ -460,15 +475,17 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
|
||||
<!-- ring-2 inset indicates "edit mode" in both light and dark without a background swap -->
|
||||
<section
|
||||
class="relative grow overflow-hidden rounded-xl border border-surface-200-800
|
||||
bg-surface-100-900 p-2 shadow-md min-w-0
|
||||
class="border-surface-200-800 bg-surface-100-900 relative min-w-0 grow overflow-hidden
|
||||
rounded-xl border p-2 shadow-md
|
||||
{$journals_loc.entry.edit_kv[
|
||||
$lq__journal_entry_obj?.journal_entry_id
|
||||
] == 'current'
|
||||
? 'ring-primary-500/40 ring-2 ring-inset'
|
||||
: ''}">
|
||||
<AE_Comp_Journal_Entry_AiTools
|
||||
content={tmp_entry_obj.content}
|
||||
content={typeof tmp_entry_obj.content === 'string'
|
||||
? tmp_entry_obj.content
|
||||
: ''}
|
||||
bind:summary={tmp_entry_obj.summary}
|
||||
on_save={() => update_journal_entry()}
|
||||
{log_lvl} />
|
||||
@@ -491,8 +508,9 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
{lq__journal_entry_obj} />
|
||||
|
||||
<div
|
||||
class="rounded-xl border border-surface-200-800 bg-surface-100-900 p-3 shadow-sm">
|
||||
<AE_Comp_Journal_Entry_Metadata entry={$lq__journal_entry_obj} />
|
||||
class="border-surface-200-800 bg-surface-100-900 rounded-xl border p-3 shadow-sm">
|
||||
<AE_Comp_Journal_Entry_Metadata
|
||||
entry={$lq__journal_entry_obj} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -509,7 +527,6 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
|
||||
<AE_Comp_Modal_Journal_Entry_Config
|
||||
bind:show={show_config_modal}
|
||||
entry={$lq__journal_entry_obj}
|
||||
journal={$lq__journal_obj}
|
||||
bind:tmp_entry_obj
|
||||
on_save={() => update_journal_entry()}
|
||||
|
||||
@@ -52,8 +52,9 @@ import {
|
||||
} from '$lib/ae_journals/ae_journals_stores';
|
||||
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
|
||||
import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte';
|
||||
import type { ae_JournalEntryDraft } from '$lib/types/ae_types';
|
||||
|
||||
let tmp_entry_obj: key_val = $state({});
|
||||
let tmp_entry_obj: ae_JournalEntryDraft = $state({});
|
||||
|
||||
// Derived state for modal visibility
|
||||
// We cast to boolean for the prop, but we need to handle the close event to clear the store ID
|
||||
@@ -106,9 +107,7 @@ let visible_journal_entry_obj_li = $derived(
|
||||
</div>
|
||||
<section
|
||||
class="journal_list relative flex w-full flex-col items-center justify-center gap-1 md:gap-2">
|
||||
{#if visible_journal_entry_obj_li === null ||
|
||||
($journals_sess.entry.qry__status === 'loading' &&
|
||||
visible_journal_entry_obj_li.length === 0)}
|
||||
{#if visible_journal_entry_obj_li === null || ($journals_sess.entry.qry__status === 'loading' && visible_journal_entry_obj_li.length === 0)}
|
||||
<!-- Loading state -->
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-10 opacity-50">
|
||||
|
||||
@@ -16,14 +16,9 @@ interface Props {
|
||||
|
||||
let { log_lvl = $bindable(0), lq__journal_obj }: Props = $props();
|
||||
|
||||
import {
|
||||
Library,
|
||||
RemoveFormatting
|
||||
} from '@lucide/svelte';
|
||||
import { Library, RemoveFormatting } from '@lucide/svelte';
|
||||
|
||||
import {
|
||||
ae_loc,
|
||||
} from '$lib/stores/ae_stores';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import {
|
||||
journals_loc,
|
||||
journals_sess
|
||||
@@ -127,10 +122,13 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
|
||||
{#if $ae_loc.edit_mode && ($ae_loc.manager_access || $ae_loc.trusted_access)}
|
||||
<span class="flex flex-row flex-wrap items-center gap-2">
|
||||
<span class="hidden text-sm text-gray-500 lg:inline"> Filters: </span>
|
||||
<span class="hidden text-sm text-gray-500 lg:inline">
|
||||
Filters:
|
||||
</span>
|
||||
|
||||
{#if $ae_loc.manager_access}
|
||||
<label class="flex flex-row items-center gap-1 text-xs font-semibold text-gray-500">
|
||||
<label
|
||||
class="flex flex-row items-center gap-1 text-xs font-semibold text-gray-500">
|
||||
<span>Enabled</span>
|
||||
<select
|
||||
class="select select-sm"
|
||||
@@ -145,7 +143,8 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
{/if}
|
||||
|
||||
{#if $ae_loc.trusted_access}
|
||||
<label class="flex flex-row items-center gap-1 text-xs font-semibold text-gray-500">
|
||||
<label
|
||||
class="flex flex-row items-center gap-1 text-xs font-semibold text-gray-500">
|
||||
<span>Hidden</span>
|
||||
<select
|
||||
class="select select-sm"
|
||||
|
||||
@@ -312,7 +312,7 @@ async function delete_journal() {
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Journal Group (text)</span>
|
||||
<input
|
||||
|
||||
@@ -236,7 +236,6 @@ async function handle_new_entry() {
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,12 +8,10 @@ import { untrack } from 'svelte';
|
||||
import {
|
||||
CalendarClock,
|
||||
Check,
|
||||
CircleAlert,
|
||||
CodeXml,
|
||||
Database,
|
||||
Layout,
|
||||
MonitorPlay,
|
||||
MousePointerClick,
|
||||
Palette,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
Wrench,
|
||||
@@ -22,7 +20,8 @@ import {
|
||||
import { Modal } from 'flowbite-svelte';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import {
|
||||
journals_loc,
|
||||
journals_sess
|
||||
@@ -30,24 +29,29 @@ import {
|
||||
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
let { log_lvl = 0, show = $bindable(false) }: Props = $props();
|
||||
let { show = $bindable(false) }: Props = $props();
|
||||
|
||||
// Internal State
|
||||
let tab: 'form' | 'local_json' | 'session_json' = $state('form');
|
||||
let tmp_config: any = $state({
|
||||
let tab: 'config' | 'local_json' | 'session_json' = $state('config');
|
||||
let tmp_config: key_val = $state({
|
||||
journal: {},
|
||||
entry: {}
|
||||
});
|
||||
|
||||
const panel_class = 'space-y-4 rounded-xl border border-surface-500/20 bg-surface-500/5 p-4 shadow-sm';
|
||||
const panel_title_class = 'flex items-center gap-2 border-b border-surface-500/20 pb-2 text-lg font-bold';
|
||||
const panel_class =
|
||||
'space-y-4 rounded-xl border border-surface-500/20 bg-surface-500/5 p-4 shadow-sm';
|
||||
const panel_title_class =
|
||||
'flex items-center gap-2 border-b border-surface-500/20 pb-2 text-lg font-bold';
|
||||
const field_card_class =
|
||||
'bg-surface-500/5 border-surface-500/10 flex cursor-pointer items-center space-x-3 rounded-lg border p-3 transition-colors hover:bg-surface-500/10';
|
||||
const tab_button_base_class = 'btn btn-sm border transition-all duration-200';
|
||||
const tab_button_active_class = 'border-surface-200-800 bg-surface-200-800 text-surface-950-50 shadow-sm';
|
||||
const tab_button_inactive_class = 'border-transparent bg-surface-50-900/60 text-surface-600-400 hover:border-surface-200-800 hover:bg-surface-100-900 hover:text-surface-950-50';
|
||||
const tab_button_active_class =
|
||||
'border-surface-200-800 bg-surface-200-800 text-surface-950-50 shadow-sm';
|
||||
const tab_button_inactive_class =
|
||||
'border-transparent bg-surface-50-900/60 text-surface-600-400 hover:border-surface-200-800 hover:bg-surface-100-900 hover:text-surface-950-50';
|
||||
|
||||
function tab_button_class(is_active: boolean): string {
|
||||
return `${tab_button_base_class} ${is_active ? tab_button_active_class : tab_button_inactive_class}`;
|
||||
@@ -76,7 +80,7 @@ function handle_save() {
|
||||
dismissable={false}
|
||||
placement="top-center"
|
||||
size="xl"
|
||||
class="relative mx-auto flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] w-full flex-col rounded-xl border border-surface-200-800 bg-surface-50-900 text-surface-950-50 shadow-xl"
|
||||
class="border-surface-200-800 bg-surface-50-900 text-surface-950-50 relative mx-auto flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] w-full flex-col rounded-xl border shadow-xl"
|
||||
headerClass="flex w-full flex-row items-center justify-between gap-2 rounded-t-xl border-b border-surface-200-800 bg-surface-100-900 p-4"
|
||||
footerClass="flex w-full flex-row items-center justify-center gap-2 rounded-b-xl border-t border-surface-200-800 bg-surface-100-900 p-4">
|
||||
{#snippet header()}
|
||||
@@ -98,8 +102,8 @@ function handle_save() {
|
||||
class="bg-surface-500/10 sticky top-0 z-10 mx-auto mb-4 flex max-w-fit justify-center gap-1 rounded-lg p-1 backdrop-blur-sm">
|
||||
<button
|
||||
type="button"
|
||||
class={tab_button_class(tab === 'form')}
|
||||
onclick={() => (tab = 'form')}>
|
||||
class={tab_button_class(tab === 'config')}
|
||||
onclick={() => (tab = 'config')}>
|
||||
<Settings size="1.1em" class="mr-1" /> Config
|
||||
</button>
|
||||
<button
|
||||
@@ -116,16 +120,16 @@ function handle_save() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if tab === 'form'}
|
||||
<div class="animate-in fade-in space-y-8 duration-300">
|
||||
{#if tab === 'config'}
|
||||
<div class="animate-in fade-in space-y-4 duration-300">
|
||||
<!-- Date/Time Section -->
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<CalendarClock size="1.2em" class="text-primary-500" />
|
||||
Date and Time Display
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-6 p-2 md:grid-cols-2">
|
||||
<label class="label">
|
||||
<div class="grid grid-cols-1 gap-4 p-2 md:grid-cols-2">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>DateTime Format</span>
|
||||
<select
|
||||
@@ -145,7 +149,7 @@ function handle_save() {
|
||||
>ISO (YYYY-MM-DD HH:mm:ss)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Time-Only Format</span>
|
||||
<select
|
||||
@@ -172,9 +176,8 @@ function handle_save() {
|
||||
class="text-primary-500" />
|
||||
User Interface Preferences
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-6 p-2 md:grid-cols-2">
|
||||
<label
|
||||
class="bg-surface-500/5 border-surface-500/10 flex cursor-pointer items-center space-x-3 rounded-lg border p-2">
|
||||
<div class="grid grid-cols-1 gap-4 p-2 md:grid-cols-2">
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_config.entry.auto_save}
|
||||
@@ -186,8 +189,7 @@ function handle_save() {
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="bg-surface-500/5 border-surface-500/10 flex cursor-pointer items-center space-x-3 rounded-lg border p-2">
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_config.show_id_random}
|
||||
@@ -209,8 +211,8 @@ function handle_save() {
|
||||
<Database size="1.2em" class="text-primary-500" />
|
||||
Journal Query Filters
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-6 p-2 md:grid-cols-3">
|
||||
<label class="label">
|
||||
<div class="grid grid-cols-1 gap-4 p-2 md:grid-cols-3">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Enabled Status</span>
|
||||
<select
|
||||
@@ -223,7 +225,7 @@ function handle_save() {
|
||||
>All (Enabled & Disabled & NULL)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Hidden Status</span>
|
||||
<select
|
||||
@@ -235,7 +237,7 @@ function handle_save() {
|
||||
>All (Visible & Hidden & NULL)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Query Limit</span>
|
||||
<select
|
||||
@@ -259,8 +261,8 @@ function handle_save() {
|
||||
<Database size="1.2em" class="text-primary-500" />
|
||||
Entry Query Filters
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-6 p-2 md:grid-cols-3">
|
||||
<label class="label">
|
||||
<div class="grid grid-cols-1 gap-4 p-2 md:grid-cols-3">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Enabled Status</span>
|
||||
<select
|
||||
@@ -273,7 +275,7 @@ function handle_save() {
|
||||
>All (Enabled & Disabled & NULL)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Hidden Status</span>
|
||||
<select
|
||||
@@ -285,7 +287,7 @@ function handle_save() {
|
||||
>All (Visible & Hidden & NULL)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Query Limit</span>
|
||||
<select
|
||||
@@ -309,12 +311,12 @@ function handle_save() {
|
||||
<ShieldCheck size="1.2em" class="text-primary-500" />
|
||||
Security & Encryption
|
||||
</h2>
|
||||
<div class="space-y-4 rounded-lg border border-surface-500/10 bg-surface-500/5 p-4">
|
||||
<div
|
||||
class="border-surface-500/10 bg-surface-500/5 space-y-4 rounded-lg border p-4">
|
||||
<div class="text-sm italic opacity-80">
|
||||
Global security overrides for the journal module.
|
||||
</div>
|
||||
<label
|
||||
class="flex cursor-pointer items-center space-x-3">
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={
|
||||
@@ -326,6 +328,125 @@ function handle_save() {
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Alerts Section -->
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<CircleAlert size="1.2em" class="text-primary-500" />
|
||||
Alerts & Messaging
|
||||
</h2>
|
||||
<div
|
||||
class="border-surface-500/10 bg-surface-500/5 space-y-4 rounded-xl border p-4">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Alert Message</span>
|
||||
<textarea
|
||||
bind:value={tmp_config.alert_msg}
|
||||
class="textarea min-h-24 w-full"
|
||||
placeholder="Optional alert or notice shown with the journal"
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_config.alert}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Enable Alert</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Mark this journal for attention in lists and
|
||||
headers.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Admin Section -->
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<Settings size="1.2em" class="text-primary-500" />
|
||||
Admin
|
||||
</h2>
|
||||
<div
|
||||
class="border-surface-500/10 bg-surface-500/5 space-y-4 rounded-xl border p-4">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Notes</span>
|
||||
<textarea
|
||||
bind:value={tmp_config.notes}
|
||||
class="textarea min-h-24 w-full"
|
||||
placeholder="Internal notes for administrators and trusted access"
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_config.enable}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Enabled</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Allow access to this journal.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_config.hide}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Hidden</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Hide this journal from standard lists.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_config.priority}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Priority</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Pin this journal ahead of the standard
|
||||
order.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="bg-surface-500/5 border-surface-500/10 flex items-center justify-between rounded-lg border p-3">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-bold"
|
||||
>Sort Order</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Lower numbers appear earlier in lists.
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={tmp_config.sort}
|
||||
class="input input-sm w-24 text-right" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="border-surface-500/10 bg-surface-500/5 rounded-xl border p-4">
|
||||
<p class="text-xs italic opacity-70">
|
||||
Admin changes apply to the journal module state
|
||||
and are saved with the rest of this config.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{:else if tab === 'local_json'}
|
||||
<div class="h-full min-h-100">
|
||||
|
||||
@@ -5,10 +5,11 @@ import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
|
||||
import { ae_api } from '$lib/stores/ae_stores';
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import type { ae_JournalEntryDraft } from '$lib/types/ae_types';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
journal_entry: key_val;
|
||||
journal_entry: ae_JournalEntryDraft;
|
||||
journal_config: key_val; // The cfg_json from the journal object
|
||||
mode?: 'append' | 'prepend' | 'auto';
|
||||
on_close: () => void;
|
||||
@@ -26,7 +27,7 @@ let {
|
||||
log_lvl = 0
|
||||
}: Props = $props();
|
||||
// Local State
|
||||
let tmp_entry_obj: key_val = $state({});
|
||||
let tmp_entry_obj: ae_JournalEntryDraft = $state({});
|
||||
|
||||
// Header Options
|
||||
let add_timestamp_header: boolean = $state(true);
|
||||
@@ -50,7 +51,8 @@ $effect(() => {
|
||||
});
|
||||
|
||||
async function handle_save() {
|
||||
let current_entry_content = tmp_entry_obj?.content || '';
|
||||
let current_entry_content =
|
||||
typeof tmp_entry_obj?.content === 'string' ? tmp_entry_obj.content : '';
|
||||
let add_content = '';
|
||||
let new_content = current_entry_content;
|
||||
|
||||
@@ -109,11 +111,16 @@ async function handle_save() {
|
||||
new_content = new_content.trim() + '\n';
|
||||
|
||||
let data_kv = { content: new_content };
|
||||
const journal_entry_id = tmp_entry_obj.journal_entry_id;
|
||||
if (!journal_entry_id) {
|
||||
console.error('Journal entry ID missing for append save.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let update_result = await journals_func.update_ae_obj__journal_entry({
|
||||
api_cfg: $ae_api,
|
||||
journal_entry_id: tmp_entry_obj?.journal_entry_id,
|
||||
journal_entry_id,
|
||||
data_kv: data_kv,
|
||||
log_lvl: log_lvl
|
||||
});
|
||||
|
||||
@@ -7,13 +7,11 @@ import {
|
||||
ArrowDownToLine,
|
||||
ArrowUpToLine,
|
||||
Check,
|
||||
CircleAlert,
|
||||
CodeXml,
|
||||
Copy,
|
||||
FileDown,
|
||||
Fingerprint,
|
||||
FingerprintPattern,
|
||||
Minus,
|
||||
Plus,
|
||||
RefreshCcw,
|
||||
Settings,
|
||||
Shapes,
|
||||
@@ -29,13 +27,13 @@ import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
|
||||
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
||||
import AE_Object_Flags from '$lib/ae_elements/AE_Object_Flags.svelte';
|
||||
import type { ae_Journal, ae_JournalEntry } from '$lib/types/ae_types';
|
||||
import type { ae_Journal, ae_JournalEntryDraft } from '$lib/types/ae_types';
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
show?: boolean;
|
||||
journal: ae_Journal;
|
||||
tmp_entry_obj: ae_JournalEntry; // Bindable
|
||||
tmp_entry_obj: ae_JournalEntryDraft; // Bindable
|
||||
on_save: () => void;
|
||||
on_force_reset?: () => void;
|
||||
on_show_export?: () => void;
|
||||
@@ -63,12 +61,17 @@ const normalize_date = (val?: string | Date | null) => {
|
||||
return val.slice(0, 16);
|
||||
};
|
||||
|
||||
const panel_class = 'space-y-4 rounded-xl border border-surface-500/20 bg-surface-500/5 p-4 shadow-sm';
|
||||
const panel_title_class = 'flex items-center gap-2 border-b border-surface-500/20 pb-2 text-lg font-bold';
|
||||
const field_card_class = 'bg-surface-500/5 border-surface-500/10 flex cursor-pointer items-center space-x-3 rounded-lg border p-3 transition-colors hover:bg-surface-500/10';
|
||||
const panel_class =
|
||||
'space-y-4 rounded-xl border border-surface-500/20 bg-surface-500/5 p-4 shadow-sm';
|
||||
const panel_title_class =
|
||||
'flex items-center gap-2 border-b border-surface-500/20 pb-2 text-lg font-bold';
|
||||
const field_card_class =
|
||||
'bg-surface-500/5 border-surface-500/10 flex cursor-pointer items-center space-x-3 rounded-lg border p-3 transition-colors hover:bg-surface-500/10';
|
||||
const tab_button_base_class = 'btn btn-sm border transition-all duration-200';
|
||||
const tab_button_active_class = 'border-surface-200-800 bg-surface-200-800 text-surface-950-50 shadow-sm';
|
||||
const tab_button_inactive_class = 'border-transparent bg-surface-50-900/60 text-surface-600-400 hover:border-surface-200-800 hover:bg-surface-100-900 hover:text-surface-950-50';
|
||||
const tab_button_active_class =
|
||||
'border-surface-200-800 bg-surface-200-800 text-surface-950-50 shadow-sm';
|
||||
const tab_button_inactive_class =
|
||||
'border-transparent bg-surface-50-900/60 text-surface-600-400 hover:border-surface-200-800 hover:bg-surface-100-900 hover:text-surface-950-50';
|
||||
|
||||
function tab_button_class(is_active: boolean): string {
|
||||
return `${tab_button_base_class} ${is_active ? tab_button_active_class : tab_button_inactive_class}`;
|
||||
@@ -77,6 +80,12 @@ function tab_button_class(is_active: boolean): string {
|
||||
async function handle_update_entry() {
|
||||
try {
|
||||
// WHITELISTED BASE TABLE COLUMNS ONLY
|
||||
const journal_entry_id = tmp_entry_obj.journal_entry_id;
|
||||
if (!journal_entry_id) {
|
||||
console.error('Journal entry ID missing for update.');
|
||||
return;
|
||||
}
|
||||
|
||||
const data_kv = {
|
||||
name: tmp_entry_obj.name,
|
||||
short_name: tmp_entry_obj.short_name,
|
||||
@@ -86,6 +95,7 @@ async function handle_update_entry() {
|
||||
type_code: tmp_entry_obj.type_code,
|
||||
topic_code: tmp_entry_obj.topic_code,
|
||||
tags: tmp_entry_obj.tags,
|
||||
passcode_hash: tmp_entry_obj.passcode_hash,
|
||||
private: tmp_entry_obj.private,
|
||||
public: tmp_entry_obj.public,
|
||||
personal: tmp_entry_obj.personal,
|
||||
@@ -105,7 +115,7 @@ async function handle_update_entry() {
|
||||
|
||||
await journals_func.update_ae_obj__journal_entry({
|
||||
api_cfg: $ae_api,
|
||||
journal_entry_id: tmp_entry_obj.journal_entry_id,
|
||||
journal_entry_id,
|
||||
data_kv: data_kv,
|
||||
log_lvl: log_lvl
|
||||
});
|
||||
@@ -119,15 +129,25 @@ async function handle_admin_delete_action() {
|
||||
const delete_method = can_delete ? 'delete' : 'disable';
|
||||
const action_label = can_delete ? 'delete' : 'remove';
|
||||
const confirm_label = can_delete ? 'delete' : 'remove';
|
||||
const journal_entry_id = tmp_entry_obj.journal_entry_id;
|
||||
|
||||
if (!confirm(`Are you sure you want to ${confirm_label} this journal entry?`)) {
|
||||
if (!journal_entry_id) {
|
||||
console.error('Journal entry ID missing for delete action.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!confirm(
|
||||
`Are you sure you want to ${confirm_label} this journal entry?`
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await journals_func.delete_ae_obj_id__journal_entry({
|
||||
api_cfg: $ae_api,
|
||||
journal_entry_id: tmp_entry_obj.journal_entry_id,
|
||||
journal_entry_id,
|
||||
method: delete_method,
|
||||
log_lvl
|
||||
});
|
||||
@@ -135,7 +155,10 @@ async function handle_admin_delete_action() {
|
||||
show = false;
|
||||
await goto(`/journals/${tmp_entry_obj.journal_id}`);
|
||||
} catch (error) {
|
||||
console.error(`Error attempting to ${action_label} journal entry:`, error);
|
||||
console.error(
|
||||
`Error attempting to ${action_label} journal entry:`,
|
||||
error
|
||||
);
|
||||
alert(`Failed to ${action_label} journal entry.`);
|
||||
}
|
||||
}
|
||||
@@ -147,9 +170,9 @@ async function handle_admin_delete_action() {
|
||||
dismissable={false}
|
||||
placement="top-center"
|
||||
size="lg"
|
||||
class="relative mx-auto flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] w-full flex-col rounded-xl border border-surface-200-800 bg-surface-50-900 text-surface-950-50 shadow-xl"
|
||||
class="border-surface-200-800 bg-surface-50-900 text-surface-950-50 relative mx-auto flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] w-full flex-col rounded-xl border shadow-xl"
|
||||
headerClass="flex w-full flex-row items-center justify-between gap-2 rounded-t-xl border-b border-surface-200-800 bg-surface-100-900 p-4"
|
||||
footerClass="flex w-full flex-row items-center justify-center gap-2 rounded-b-xl border-t border-surface-200-800 bg-surface-100-900 p-4">
|
||||
footerClass="mt-auto flex w-full shrink-0 flex-row items-center justify-center gap-2 rounded-b-xl border-t border-surface-200-800 bg-surface-100-900 p-4">
|
||||
{#snippet header()}
|
||||
<h3 class="flex flex-1 items-center gap-2 text-lg font-bold">
|
||||
<Settings class="text-primary-500" />
|
||||
@@ -338,6 +361,25 @@ async function handle_admin_delete_action() {
|
||||
}}
|
||||
class="input" />
|
||||
</label>
|
||||
|
||||
{#if !journal?.cfg_json?.hide_btn_template}
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_entry_obj.template}
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Template</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Mark this entry as a reusable template
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -346,118 +388,28 @@ async function handle_admin_delete_action() {
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<ShieldCheck size="1.2em" class="text-primary-500" />
|
||||
Status & Security
|
||||
Status
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_entry_obj.enable}
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Enabled</span>
|
||||
<span class="text-xs opacity-60"
|
||||
>Allow access to this entry</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_entry_obj.hide}
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Hidden</span>
|
||||
<span class="text-xs opacity-60"
|
||||
>Hide from standard lists</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_entry_obj.priority}
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Priority Entry</span>
|
||||
<span class="text-xs opacity-60"
|
||||
>Star or pin to top</span>
|
||||
</div>
|
||||
</label>
|
||||
<div class="bg-surface-500/5 border-surface-500/10 flex items-center justify-between rounded-lg border p-3">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-bold"
|
||||
>Sort Order</span>
|
||||
<span class="text-xs opacity-60"
|
||||
>Manual list position</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-icon btn-icon-sm preset-tonal-surface"
|
||||
onclick={() => {
|
||||
tmp_entry_obj.sort =
|
||||
(tmp_entry_obj.sort ?? 0) - 1;
|
||||
{#if tmp_entry_obj.alert}
|
||||
<label
|
||||
class="label flex flex-col items-start gap-1 md:col-span-2">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Alert Message</span>
|
||||
<textarea
|
||||
bind:value={tmp_entry_obj.alert_msg}
|
||||
class="textarea min-h-24 w-full"
|
||||
placeholder="Optional alert or notice shown with the entry"
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}><Minus size="1em" /></button>
|
||||
<span
|
||||
class="w-8 text-center font-mono text-lg font-bold"
|
||||
>{tmp_entry_obj.sort ?? 0}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-icon btn-icon-sm preset-tonal-surface"
|
||||
onclick={() => {
|
||||
tmp_entry_obj.sort =
|
||||
(tmp_entry_obj.sort ?? 0) + 1;
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}><Plus size="1em" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}}></textarea>
|
||||
<span class="text-xs opacity-60">
|
||||
Shown when Alert is enabled.
|
||||
</span>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<Fingerprint size="1.2em" class="text-primary-500" />
|
||||
Privacy Flags
|
||||
</h2>
|
||||
<p class="text-xs opacity-60">
|
||||
Visibility and audience controls for the entry itself.
|
||||
</p>
|
||||
<AE_Object_Flags
|
||||
bind:obj={tmp_entry_obj}
|
||||
show_labels={false}
|
||||
on_toggle={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}
|
||||
hide_alert={journal?.cfg_json?.hide_btn_alert}
|
||||
hide_private={journal?.cfg_json?.hide_btn_private}
|
||||
hide_public={journal?.cfg_json?.hide_btn_public}
|
||||
hide_personal={journal?.cfg_json?.hide_btn_personal}
|
||||
hide_professional={journal?.cfg_json
|
||||
?.hide_btn_professional}
|
||||
hide_template={journal?.cfg_json?.hide_btn_template} />
|
||||
</section>
|
||||
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<CircleAlert size="1.2em" class="text-primary-500" />
|
||||
Alerts & Messaging
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -469,22 +421,111 @@ async function handle_admin_delete_action() {
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Alert</span>
|
||||
<span class="text-xs opacity-60"
|
||||
>Flag this entry for emphasis</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Action required or reminder.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label md:col-span-2">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Alert Message</span>
|
||||
<textarea
|
||||
bind:value={tmp_entry_obj.alert_msg}
|
||||
class="textarea min-h-24"
|
||||
placeholder="Optional note shown with the alert"
|
||||
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_entry_obj.priority}
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}></textarea>
|
||||
}}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Priority Entry</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Pin this entry ahead of the standard order.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="bg-surface-500/5 border-surface-500/10 flex items-center justify-between rounded-lg border p-3">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-bold"
|
||||
>Sort Order</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Lower numbers appear earlier in lists.
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={tmp_entry_obj.sort}
|
||||
class="input input-sm w-24 text-right" />
|
||||
</label>
|
||||
|
||||
<label class={field_card_class}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={tmp_entry_obj.hide}
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Hidden</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Hide from standard lists.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<FingerprintPattern
|
||||
size="1.2em"
|
||||
class="text-primary-500" />
|
||||
Security and Privacy
|
||||
</h2>
|
||||
<div
|
||||
class="border-surface-500/10 bg-surface-500/5 space-y-4 rounded-xl border p-4">
|
||||
<label class="label flex flex-col items-start gap-1">
|
||||
<span class="text-sm font-bold opacity-70"
|
||||
>Passcode</span>
|
||||
<input
|
||||
type="password"
|
||||
bind:value={tmp_entry_obj.passcode_hash}
|
||||
class="input w-full"
|
||||
placeholder="journal_entry.passcode_hash"
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}} />
|
||||
<span class="text-xs opacity-60">
|
||||
Stored on the entry as passcode_hash.
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p
|
||||
class="text-xs font-semibold tracking-wider uppercase opacity-60">
|
||||
Visibility and audience
|
||||
</p>
|
||||
<AE_Object_Flags
|
||||
bind:obj={tmp_entry_obj}
|
||||
show_labels={false}
|
||||
on_toggle={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
}}
|
||||
hide_alert={true}
|
||||
hide_private={journal?.cfg_json
|
||||
?.hide_btn_private}
|
||||
hide_public={journal?.cfg_json?.hide_btn_public}
|
||||
hide_personal={journal?.cfg_json
|
||||
?.hide_btn_personal}
|
||||
hide_professional={journal?.cfg_json
|
||||
?.hide_btn_professional}
|
||||
hide_template={true} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -513,12 +554,14 @@ async function handle_admin_delete_action() {
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if $ae_loc.trusted_access || $ae_loc.manager_access || $ae_loc.administrator_access}
|
||||
<section class={panel_class}>
|
||||
<h2 class={panel_title_class}>
|
||||
<Settings size="1.2em" class="text-primary-500" />
|
||||
Admin
|
||||
</h2>
|
||||
<details
|
||||
class="border-surface-500/20 bg-surface-500/5 rounded-xl border shadow-sm">
|
||||
<summary
|
||||
class="border-surface-500/20 flex cursor-pointer items-center gap-2 border-b px-4 py-3 text-lg font-bold">
|
||||
<Settings size="1.2em" class="text-primary-500" />
|
||||
Admin
|
||||
</summary>
|
||||
<div class="space-y-4 p-4">
|
||||
<p class="text-xs opacity-60">
|
||||
Trusted access and above only. Notes are for staff
|
||||
use; managers and admins see Delete while trusted
|
||||
@@ -532,7 +575,7 @@ async function handle_admin_delete_action() {
|
||||
<textarea
|
||||
bind:value={tmp_entry_obj.notes}
|
||||
class="textarea min-h-24"
|
||||
placeholder="Rarely used staff/admin note"
|
||||
placeholder="Rarely used special case admin/staff notes"
|
||||
onchange={() => {
|
||||
handle_update_entry();
|
||||
on_save();
|
||||
@@ -551,15 +594,22 @@ async function handle_admin_delete_action() {
|
||||
class="checkbox checkbox-primary" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Enabled</span>
|
||||
<span class="text-xs opacity-60"
|
||||
>Default access state for this entry</span>
|
||||
<span class="text-xs opacity-60">
|
||||
Allow default access for AE object
|
||||
type; essentially marked for
|
||||
deletion.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm mx-auto inline-flex w-fit min-w-36 justify-center gap-2 px-4 font-bold {($ae_loc.manager_access || $ae_loc.administrator_access) ? 'preset-tonal-error hover:preset-filled-error-500' : 'preset-tonal-warning hover:preset-filled-warning-500'}"
|
||||
title={($ae_loc.manager_access || $ae_loc.administrator_access)
|
||||
class="btn btn-sm mx-auto inline-flex w-fit min-w-36 justify-center gap-2 px-4 font-bold {$ae_loc.manager_access ||
|
||||
$ae_loc.administrator_access
|
||||
? 'preset-tonal-error hover:preset-filled-error-500'
|
||||
: 'preset-tonal-warning hover:preset-filled-warning-500'}"
|
||||
title={$ae_loc.manager_access ||
|
||||
$ae_loc.administrator_access
|
||||
? 'Permanently delete this journal entry'
|
||||
: 'Disable this journal entry instead of deleting it'}
|
||||
onclick={handle_admin_delete_action}>
|
||||
@@ -573,8 +623,8 @@ async function handle_admin_delete_action() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{:else if tab === 'json'}
|
||||
<div class="h-full min-h-100">
|
||||
@@ -605,4 +655,4 @@ async function handle_admin_delete_action() {
|
||||
Done
|
||||
</button>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
</Modal>
|
||||
|
||||
@@ -54,12 +54,7 @@ describe('Journal Entry Search Filtering', () => {
|
||||
});
|
||||
|
||||
expect(result?.length).toBe(4);
|
||||
expect(result?.map((entry) => entry.id)).toEqual([
|
||||
'4',
|
||||
'3',
|
||||
'2',
|
||||
'1'
|
||||
]);
|
||||
expect(result?.map((entry) => entry.id)).toEqual(['4', '3', '2', '1']);
|
||||
});
|
||||
|
||||
it('should filter by enabled and hidden status', () => {
|
||||
|
||||
Reference in New Issue
Block a user