Lots of work with linking files to Journal Entries.

This commit is contained in:
Scott Idem
2025-05-20 15:37:27 -04:00
parent 83a8377155
commit 15d417ba52
6 changed files with 517 additions and 196 deletions

View File

@@ -0,0 +1,152 @@
<script lang="ts">
// *** Import Svelte specific
// Eventually this should use Lucide icons instead of FontAwesome
// import {
// ArrowDown01, ArrowDown10, ArrowDownUp,
// BookHeart, BriefcaseBusiness,
// CalendarClock, CalendarOff, Clock, CodeXml, Copy,
// Eye, EyeOff,
// Flag, FlagOff, FileX, Fingerprint,
// Globe, Group,
// Hash, History,
// LockKeyhole, LockKeyholeOpen,
// MessageSquareWarning, Menu, Minus,
// NotebookPen, NotebookText, NotepadTextDashed,
// Pencil, PenLine, Plus,
// RemoveFormatting,
// Search, Settings,
// Shapes, Share2, ShieldCheck, ShieldMinus, Siren, Skull,
// SquareLibrary,
// Tags, Trash2, TypeOutline,
// X
// } from '@lucide/svelte';
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { api } from '$lib/api';
import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
interface Props {
log_lvl?: number;
hosted_file_id: null|string;
hosted_file_obj: null|key_val;
filename?: null|string;
max_length?: number;
auto_download?: boolean;
linked_to_type?: null|string;
linked_to_id?: null|string;
download_complete?: null|boolean;
download_percent?: number;
download_status_msg?: string;
classes?: string;
}
let {
log_lvl = 0,
hosted_file_id,
hosted_file_obj,
filename = $bindable(null),
max_length = $bindable(30),
auto_download = true,
linked_to_type = $bindable(null),
linked_to_id = $bindable(null),
download_complete = $bindable(),
download_percent = $bindable(),
download_status_msg = $bindable('Not started'),
classes = 'btn btn-sm lg:btn-md variant-ghost-tertiary hover:variant-filled-tertiary min-w-48'
}: Props = $props();
if (log_lvl) {
console.log(`ae_comp__hosted_files_download_button.svelte hosted_file_id=${hosted_file_id}`, hosted_file_obj);
}
let ae_promises: key_val = $state({});
$effect(() => {
if ($ae_sess?.api_download_kv[hosted_file_obj?.hosted_file_id_random]?.percent_completed) {
download_percent = $ae_sess.api_download_kv[hosted_file_obj?.hosted_file_id_random].percent_completed;
}
});
</script>
{#if hosted_file_id && hosted_file_obj}
<button
type="button"
disabled={!$ae_loc.trusted_access}
class="{classes ?? 'btn'}"
onclick={() => {
download_complete = false;
download_status_msg = 'Downloading...';
ae_promises[hosted_file_obj.hosted_file_id_random] = api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: hosted_file_obj.hosted_file_id_random,
return_file: true,
filename: filename ?? hosted_file_obj.filename,
auto_download: auto_download,
log_lvl: log_lvl
})
.then((result) => {
if (result === null) {
console.log('File not found (404)');
download_complete = null;
download_status_msg = 'File not found';
} else if (result === false) {
console.log('Possible error with API server (check network and server status)');
download_complete = false;
download_status_msg = 'Failed to download';
} else {
// console.log('File found and downloaded');
download_complete = true;
download_status_msg = 'File downloaded';
}
return result;
});
}}
title={`Download this file:\n${filename ?? hosted_file_obj.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256.slice(0, 10)}...\nHosted ID: ${hosted_file_obj.hosted_file_id_random}\n Linked to: ${linked_to_type} ID: ${linked_to_id}`}
>
{#await ae_promises[hosted_file_obj.hosted_file_id_random]}
<span class="fas fa-spinner fa-spin mx-1"></span>
<span class="">
Downloading
{#if $ae_sess.api_download_kv[hosted_file_obj.hosted_file_id_random]}
{$ae_sess.api_download_kv[hosted_file_obj.hosted_file_id_random].percent_completed}%
{/if}
:
</span>
{:then}
<span class="fas fa-{ae_util.file_extension_icon(hosted_file_obj?.extension)}"></span>
{/await}
{#if download_complete === null}
<span class="text-red-800 dark:text-red-200">File not found</span>
{:else if download_complete === false}
<span class="text-red-800 dark:text-red-200">Failed to download!</span>
{/if}
<span class="grow">
{ae_util.shorten_filename({filename: filename ?? hosted_file_obj?.filename, max_length: max_length})}
</span>
</button>
{:else}
<button
type="button"
disabled
class="{classes ?? 'btn'}"
title="No file selected"
>
<span class="fas fa-{ae_util.file_extension_icon(hosted_file_obj?.extension)}"></span>
<span class="grow">
No file info
</span>
</button>
{/if}

View File

@@ -58,7 +58,7 @@ export interface Journal {
cfg_json?: null|key_val; // This is the configuration JSON for the journal
data_json?: null|string; // We always need to store something extra...
data_json?: null|key_val; // We always need to store something extra...
ux_mode?: null|string; // 'mobile' or 'desktop'
@@ -229,7 +229,7 @@ export interface Journal_Entry {
related_entry_id_li?: null|key_val; // List of related journal entry IDs
// cfg_json?: null|key_val; // This is the configuration JSON for the journal entry
data_json?: null|string; // We always need to store something extra...
data_json?: null|key_val; // We always need to store something extra...
// This only allows for basic access to the content.
passcode_read?: null|string; // For LLM (AI) generated summary...???

View File

@@ -99,7 +99,8 @@ ae_tmp.show__direct_download = $ae_loc.core?.show__direct_download ?? false;
hidden: 'not_hidden',
limit: 250,
// params: params,
try_cache: true
try_cache: true,
log_lvl: 2
});
// ae_tmp.show__file_li = false;

View File

@@ -0,0 +1,307 @@
<script lang="ts">
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/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 Comp_hosted_files_upload from '$lib/ae_core/ae_comp__hosted_files_upload.svelte';
import Element_manage_hosted_file_li_wrap from '$lib/element_manage_hosted_file_li_all.svelte';
interface Props {
log_lvl?: number;
link_to_type: string;
link_to_id: string;
lq__journal_entry_obj: any;
}
let {
log_lvl = 0,
link_to_type,
link_to_id,
lq__journal_entry_obj,
}: Props = $props();
let ae_promises: key_val = $state({});
let hosted_file_kv: key_val = $state($lq__journal_entry_obj?.data_json?.hosted_file_kv ?? {}); // WARNING: This does no seem to set soon enough. Added an effect to set it as a backup.
// let hosted_file_li: [string, any][] = $state([]);
let hosted_file_obj_li: any[] = $state($lq__journal_entry_obj?.data_json?.hosted_file_obj_li ?? []);
let hosted_file_id_li: string[] = $state([]);
let upload_complete: boolean = $state(false);
let slct_hosted_file_kv: key_val = $state({});
let slct_hosted_file_id: string|null = $state(null);
let slct_hosted_file_obj: any = $state(null);
let rem_hosted_file_id: string|null = $state(null);
async function update_journal_entry() {
// hosted_file_kv = lq__journal_entry_obj.data_json.hosted_file_kv;
// hosted_file_obj_li = [... $lq__journal_entry_obj?.data_json?.hosted_file_obj_li];
// hosted_file_kv = {
// ...$lq__journal_entry_obj?.data_json?.hosted_file_kv,
// };
let data_kv: key_val = {'data_json' : $lq__journal_entry_obj?.data_json ?? {}};
data_kv.data_json = {
hosted_file_kv: hosted_file_kv,
// ...$lq__journal_entry_obj?.data_json,
// hosted_file_kv: {
// ...slct_hosted_file_kv,
// },
// hosted_file_obj_li: hosted_file_obj_li
};
console.log('data_kv', data_kv);
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,
});
console.log('Journal entry updated successfully:', response);
} catch (error) {
console.error('Error updating journal entry:', error);
alert('Failed to update journal entry.');
}
}
$effect(() => {
// if ($lq__journal_entry_obj && Object.keys(slct_hosted_file_kv ?? {}).length && slct_hosted_file_kv != hosted_file_kv) {
// hosted_file_kv = {
// hosted_file_kv,
// ...slct_hosted_file_kv
// };
// console.log('hosted_file_kv', hosted_file_kv);
// slct_hosted_file_kv = {};
// update_journal_entry();
// }
if (!Object.keys(hosted_file_kv ?? {}).length && $lq__journal_entry_obj?.data_json?.hosted_file_kv) {
hosted_file_kv = $lq__journal_entry_obj?.data_json?.hosted_file_kv;
}
if ($lq__journal_entry_obj && slct_hosted_file_id && slct_hosted_file_obj) {
hosted_file_kv = {
...hosted_file_kv,
[slct_hosted_file_id]: slct_hosted_file_obj
};
slct_hosted_file_id = null;
slct_hosted_file_obj = null;
update_journal_entry();
// slct_hosted_file_obj = $lq__journal_entry_obj?.data_json?.hosted_file_obj_li?.find((obj: any) => obj.hosted_file_id == slct_hosted_file_id);
// console.log('slct_hosted_file_obj', slct_hosted_file_obj);
}
if ($lq__journal_entry_obj && rem_hosted_file_id) {
console.log('rem_hosted_file_id', rem_hosted_file_id);
delete hosted_file_kv[rem_hosted_file_id];
rem_hosted_file_id = null;
update_journal_entry();
}
});
</script>
<section
class:hidden={!$ae_loc.edit_mode}
class="ae_section journal_entry__hosted_file border border-gray-200 rounded p-2 space-y-2"
>
<h3 class="h3">Upload/Manage Hosted File</h3>
<!-- {Object.keys($lq__journal_entry_obj?.data_json?.hosted_file_kv ?? {}).length} selected -->
<!-- {Object.keys(hosted_file_kv ?? {}).length} selected -->
<!-- {Object.keys(slct_hosted_file_kv ?? {}).length} to add -->
{#if $ae_loc.trusted_access}
<button
type="button"
class="btn btn-sm variant-ghost-warning hover:variant-filled-warning float-right"
title="Toggle between Upload and Select from Hosted Files"
onclick={() => {
if ($ae_sess.files.add_to_use_files_method == 'upload') {
$ae_sess.files.add_to_use_files_method = 'select';
} else {
$ae_sess.files.add_to_use_files_method = 'upload';
}
}}
>
<span class="fas fa-exchange-alt m-1"></span>
Upload/Select
</button>
<div
class:hidden={$ae_sess.files.add_to_use_files_method != 'upload'}
class="upload"
>
<Comp_hosted_files_upload
class_li="border border-gray-300 rounded-md p-2 bg-gray-100 hover:bg-gray-200"
link_to_type={link_to_type}
link_to_id={link_to_id}
bind:hosted_file_id_li={hosted_file_id_li}
bind:hosted_file_obj_li={hosted_file_obj_li}
bind:upload_complete={upload_complete}
log_lvl={log_lvl}
>
{#snippet label()}
<span >
<div>
<span class="fas fa-upload"></span>
<strong class="bg-green-100 p-1">Upload Journal Entry files</strong>
</div>
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<strong>Aether hosted files only</strong>
</span>
</span>
{/snippet}
</Comp_hosted_files_upload>
</div>
<div
class:hidden={$ae_sess.files.add_to_use_files_method != 'select'}
class=""
>
<!-- link_to_type={'journal_entry'} -->
<!-- link_to_id={$lq__journal_entry_obj?.journal_entry_id} -->
<Element_manage_hosted_file_li_wrap
link_to_type={'account'}
link_to_id={$ae_loc?.account_id}
allow_basic={true}
allow_moderator={true}
class_li={''}
bind:slct_hosted_file_kv={slct_hosted_file_kv}
bind:slct_hosted_file_id={slct_hosted_file_id}
bind:slct_hosted_file_obj={slct_hosted_file_obj}
/>
</div>
{/if}
{#if !Object.keys(hosted_file_kv ?? {}).length}
No file(s) uploaded yet.
{:else}
<!-- {Object.keys(hosted_file_kv ?? {}).length} -->
<!-- <pre>
{JSON.stringify(hosted_file_kv)}
</pre> -->
{#each Object.entries(hosted_file_kv) as [hosted_file_id, hosted_file_obj]}
<div
class="flex flex-row flex-wrap gap-1 items-center justify-center w-full"
>
<label for="filename" class="flex-grow">
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<span class="fas fa-file-alt"></span>
<strong class="bg-green-100 p-1">Filename</strong>
</span>
<input type="text" id="filename" name="filename" value={(hosted_file_obj?.filename ? hosted_file_obj?.filename : 'unknown')} class="input w-full">
</label>
<label for="extension max-w-20">
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<span class="fas fa-file-code"></span>
<strong class="bg-green-100 p-1">File Extension</strong>
{#if !$ae_loc.administrator_access}
<span class="fas fa-lock" title="Field is locked"></span>
{:else}
<span class="fas fa-unlock" title="Field is unlocked"></span>
{/if}
</span>
<input
type="text"
id="extension"
name="extension"
value={(hosted_file_obj?.extension ? hosted_file_obj?.extension : 'ext')}
readonly={!$ae_loc.administrator_access}
class="input w-full max-w-20"
>
</label>
<button
disabled={!$ae_loc.administrator_access}
type="button"
onclick={() => {
if (confirm('Are you sure you want to remove the file?')) {
// First - Attempt to delete the hosted file
ae_promises.journal_entry_obj__hosted_file = core_func.delete_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: hosted_file_id,
link_to_type: link_to_type,
link_to_id: link_to_id,
rm_orphan: true,
fake_delete: true,
log_lvl: log_lvl
})
.then(function (delete_result) {
// Second - If deleted, then update the journal_entry_obj
console.log(`File removed. Now update the journal_entry_obj`);
rem_hosted_file_id = hosted_file_id;
// update_journal_entry();
})
.catch(function (error) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(() => {
// We need to do all of this since the DB object has changed and the SLCT object does automatically update (yet...??? Svelte 5?).
slct_hosted_file_obj.hosted_file_id = null;
slct_hosted_file_obj.file_path = null;
slct_hosted_file_obj.filename = null;
slct_hosted_file_obj.file_extension = null;
});
}
}}
class="btn btn-sm variant-soft-error"
>
{#await ae_promises.journal_entry_obj__hosted_file}
<span class="fas fa-spinner fa-spin m-1"></span>
{:then}
<span class="fas fa-trash-alt m-1"></span>
Remove File
{/await}
<!-- <span class="fas fa-trash-alt m-1"></span>
Remove File -->
</button>
</div>
{/each}
<!-- <label for="file_path">File Path
{#if !$ae_loc.administrator_access}
<span class="fas fa-lock" title="Field is locked"></span>
{:else}
<span class="fas fa-unlock" title="Field is unlocked"></span>
{/if}
<input
type="text"
id="file_path"
name="file_path"
value={(slct_hosted_file_obj.file_path ? slct_hosted_file_obj.file_path : '')}
readonly={!$ae_loc.administrator_access}
class="input w-full"
>
</label> -->
{/if}
</section>

View File

@@ -9,7 +9,7 @@ import {
BookHeart, BriefcaseBusiness,
CalendarClock, CalendarOff, Clock, CodeXml, Copy,
Eye, EyeOff,
Flag, FlagOff, FileX, Fingerprint,
Flag, FlagOff, FileDown, FileX, Fingerprint,
Globe, Group,
Hash, History,
LockKeyhole, LockKeyholeOpen,
@@ -36,11 +36,14 @@ import E_app_codemirror_v5 from '$lib/e_app_codemirror_v5.svelte';
import type { key_val } from '$lib/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { api } from '$lib/api';
import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/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 Comp_journal_entry_file_li from './ae_comp__journal_entry_obj_file_li.svelte';
import Comp_hosted_files_upload from '$lib/ae_core/ae_comp__hosted_files_upload.svelte';
import Element_manage_hosted_file_li_wrap from '$lib/element_manage_hosted_file_li_all.svelte';
import Comp_hosted_files_download_button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
interface Props {
log_lvl?: number;
@@ -305,10 +308,10 @@ async function update_journal_entry() {
// log_lvl = 1;
// append slct_hosted_file_kv to data_json.hosted_file_kv
if (!tmp_entry_obj.data_json.hosted_file_kv) {
tmp_entry_obj.data_json.hosted_file_kv = {};
}
tmp_entry_obj.data_json.hosted_file_kv = $journals_loc.entry.hosted_file_kv;
// if (!tmp_entry_obj.data_json.hosted_file_kv) {
// tmp_entry_obj.data_json.hosted_file_kv = {};
// }
// tmp_entry_obj.data_json.hosted_file_kv = $journals_loc.entry.hosted_file_kv;
let data_kv: key_val = {
alert: tmp_entry_obj?.alert,
@@ -2266,198 +2269,40 @@ tabindex={$ae_loc.edit_mode ? 0 : -1} -->
{/if}
<hr class="divider my-2" />
{#if $lq__journal_entry_obj?.journal_entry_id}
<section
class:hidden={!$ae_loc.edit_mode}
class="ae_section journal_entry__hosted_file border border-gray-200 rounded p-2 space-y-2"
>
<Comp_journal_entry_file_li
log_lvl={log_lvl}
link_to_type="journal_entry"
link_to_id={$lq__journal_entry_obj?.journal_entry_id}
lq__journal_entry_obj={lq__journal_entry_obj}
/>
<h3 class="h3">Upload/Manage Hosted File</h3>
<!-- Object.keys(hosted_file_kv ?? {}).length -->
{#if $lq__journal_entry_obj?.data_json?.hosted_file_kv}
<div class="flex flex-row flex-wrap gap-1 items-center justify-center w-full">
<span class="">
<!-- <SquareDownload size="1em" class="mx-1 inline-block"/> -->
<FileDown size="1em" class="mx-1 inline-block" />
<span class="text-sm text-gray-500 hidden sm:inline">
Download Files:
</span>
</span>
{#if !tmp_entry_obj?.data_json?.hosted_file_kv}
No file(s) uploaded yet.
{#each Object.entries($lq__journal_entry_obj?.data_json?.hosted_file_kv) as [key, hosted_file_obj]}
{#if $ae_loc.trusted_access}
<button
type="button"
class="btn btn-sm variant-ghost-warning hover:variant-filled-warning float-right"
title="Toggle between Upload and Select from Hosted Files"
onclick={() => {
if ($ae_sess.files.add_to_use_files_method == 'upload') {
$ae_sess.files.add_to_use_files_method = 'select';
} else {
$ae_sess.files.add_to_use_files_method = 'upload';
}
}}
>
<span class="fas fa-exchange-alt m-1"></span>
Upload/Select
</button>
<div
class:hidden={$ae_sess.files.add_to_use_files_method != 'upload'}
class="upload"
>
<Comp_hosted_files_upload
class_li="border border-gray-300 rounded-md p-2 bg-gray-100 hover:bg-gray-200"
link_to_type="journal_entry"
link_to_id={tmp_entry_obj.journal_entry_id}
bind:hosted_file_id_li={tmp_entry_obj.hosted_file_id_li}
bind:hosted_file_obj_li={tmp_entry_obj.hosted_file_obj_li}
bind:upload_complete={tmp_entry_obj.upload_complete}
log_lvl={log_lvl}
>
{#snippet label()}
<span >
<div>
<span class="fas fa-upload"></span>
<strong class="bg-green-100 p-1">Upload Journal Entry files</strong>
</div>
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<strong>Aether hosted files only</strong>
</span>
</span>
{/snippet}
</Comp_hosted_files_upload>
</div>
<div
class:hidden={$ae_sess.files.add_to_use_files_method != 'select'}
class=""
>
<!-- link_to_type={'journal_entry'} -->
<!-- link_to_id={$lq__journal_entry_obj?.journal_entry_id} -->
<Element_manage_hosted_file_li_wrap
link_to_type={'account'}
link_to_id={$ae_loc?.account_id}
allow_basic={true}
allow_moderator={true}
class_li={''}
bind:slct_hosted_file_kv={slct_hosted_file_kv}
bind:slct_hosted_file_id={slct_hosted_file_id}
bind:slct_hosted_file_obj={slct_hosted_file_obj}
/>
</div>
{/if}
{:else}
<button
disabled={!$ae_loc.administrator_access}
type="button"
onclick={() => {
if (confirm('Are you sure you want to remove the file?')) {
// First - Attempt to delete the hosted file
ae_promises.journal_entry_obj__hosted_file = core_func.delete_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: tmp_entry_obj.hosted_file_id,
link_to_type: 'journal_entry',
link_to_id: $lq__journal_entry_obj?.journal_entry_id,
rm_orphan: true,
fake_delete: false,
log_lvl: log_lvl
})
.then(function (delete_result) {
// Second - If deleted, then update the journal_entry_obj
console.log(`File removed. Now update the journal_entry_obj`);
update_journal_entry();
// ae_promises.journal_entry_obj = archives_func.update_ae_obj__journal_entry({
// api_cfg: $ae_api,
// journal_entry_id: $lq__journal_entry_obj?.journal_entry_id,
// data_kv: {
// hosted_file_id_random: null,
// file_path: null,
// filename: null,
// file_extension: null,
// journal_entry_type: null,
// },
// log_lvl: log_lvl
// })
// .then(function (journal_entry_obj_update_result) {
// // We need to do all of this since the DB object has changed and the SLCT object does automatically update (yet...??? Svelte 5?).
// // tmp_entry_obj = $lq__journal_entry_obj;
// })
// .catch(function (error) {
// console.log('Something went wrong.');
// console.log(error);
// return false;
// });
})
.catch(function (error) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(() => {
// We need to do all of this since the DB object has changed and the SLCT object does automatically update (yet...??? Svelte 5?).
tmp_entry_obj.hosted_file_id = null;
tmp_entry_obj.file_path = null;
tmp_entry_obj.filename = null;
tmp_entry_obj.file_extension = null;
});
}
}}
class="novi_btn btn btn-sm variant-soft-error float-right"
>
{#await ae_promises.journal_entry_obj__hosted_file}
<span class="fas fa-spinner fa-spin m-1"></span>
{:then}
<span class="fas fa-trash-alt m-1"></span>
Remove File
{/await}
<!-- <span class="fas fa-trash-alt m-1"></span>
Remove File -->
</button>
<!-- <label for="file_path">File Path
{#if !$ae_loc.administrator_access}
<span class="fas fa-lock" title="Field is locked"></span>
{:else}
<span class="fas fa-unlock" title="Field is unlocked"></span>
{/if}
<input
type="text"
id="file_path"
name="file_path"
value={(tmp_entry_obj.file_path ? tmp_entry_obj.file_path : '')}
readonly={!$ae_loc.administrator_access}
class="input w-full"
>
</label> -->
<label for="filename">Filename
<input type="text" id="filename" name="filename" value={(tmp_entry_obj.filename ? tmp_entry_obj.filename : 'unknown')} class="input w-full">
</label>
<label for="file_extension">File Extension
{#if !$ae_loc.administrator_access}
<span class="fas fa-lock" title="Field is locked"></span>
{:else}
<span class="fas fa-unlock" title="Field is unlocked"></span>
{/if}
<input
type="text"
id="file_extension"
name="file_extension"
value={(tmp_entry_obj.file_extension ? tmp_entry_obj.file_extension : 'ext')}
readonly={!$ae_loc.administrator_access}
class="input w-24"
>
</label>
<Comp_hosted_files_download_button
hosted_file_id={hosted_file_obj?.hosted_file_id_random ?? ''}
hosted_file_obj={hosted_file_obj}
linked_to_type="journal_entry"
linked_to_id={$lq__journal_entry_obj?.journal_entry_id}
/>
{/each}
</div>
{/if}
</section>
{:else}
<section
class="ae_section journal_entry__hosted_file border border-gray-200 rounded p-2 space-y-2"

View File

@@ -7,7 +7,7 @@ import { Modal } from 'flowbite-svelte';
import {
CalendarClock, Check, CodeXml, Copy,
Eye, EyeOff,
FileLock, Fingerprint, Flag, FlagOff,
FileLock, Files, Fingerprint, Flag, FlagOff,
Group,
ListPlus, Lock,
NotebookPen, NotebookText, NotepadTextDashed,
@@ -343,12 +343,28 @@ $effect(() => {
</span>
<div class="flex flex-row flex-wrap gap-2 items-center justify-end">
<!-- Linked file count -->
<div
class="ae_linked_file_count flex flex-row flex-wrap gap-0.5 items-center justify-start"
class:hidden={!journals_journal_entry_obj?.data_json?.hosted_file_kv}
>
<Files class="mx-1 inline-block" />
<span class="text-xs text-gray-500 hidden md:inline">Linked files:</span>
<span class="font-semibold text-sm text-gray-500">
{journals_journal_entry_obj?.data_json?.hosted_file_kv
? Object.keys(journals_journal_entry_obj?.data_json?.hosted_file_kv).length
: 0}&times;
</span>
</div>
<!-- Tags for journal entry. Comma delimited list. -->
{#if journals_journal_entry_obj.tags && journals_journal_entry_obj.tags.length}
<div class="tags flex flex-wrap gap-1 items-center justify-start p-1">
<div class="tags flex flex-row flex-wrap gap-0.5 items-center justify-start p-1">
<Tags class="mx-1 inline-block"/>
<span class="text-sm text-gray-500 hidden md:inline">Tags:</span>
<span class="text-xs text-gray-500 hidden md:inline">Tags:</span>
{#each journals_journal_entry_obj.tags.split(',') as tag}
<span