More work on encryption of content and history.

This commit is contained in:
Scott Idem
2025-05-05 17:10:19 -04:00
parent 0b61596833
commit 66b122dca5
9 changed files with 685 additions and 301 deletions

View File

@@ -606,8 +606,9 @@ export function db_save_ae_obj_li__journal(
updated_on: obj.updated_on, updated_on: obj.updated_on,
// Generated fields for sorting locally only // Generated fields for sorting locally only
tmp_sort_1: `${obj.group}_${obj.priority}_${obj.sort}_${obj.updated_on}_${obj.created_on}`, tmp_sort_1: `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${obj.sort ?? '0'}_${obj.updated_on}_${obj.created_on}`,
tmp_sort_2: `${obj.group}_${obj.priority}_${obj.sort}_${obj.updated_on ?? obj.created_on}`, tmp_sort_2: `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${obj.sort ?? '0'}_${obj.updated_on ?? obj.created_on}`,
tmp_sort_3: `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${obj.sort ?? '0'}_${obj.name}_${obj.updated_on ?? obj.created_on}`,
// tmp_sort_1: `${obj.original_datetime}_${obj.group}_${obj.priority}_${obj.sort}`, // tmp_sort_1: `${obj.original_datetime}_${obj.group}_${obj.priority}_${obj.sort}`,
// tmp_sort_2: `${obj.group}_${obj.original_datetime}_${obj.priority}_${obj.sort}`, // tmp_sort_2: `${obj.group}_${obj.original_datetime}_${obj.priority}_${obj.sort}`,

View File

@@ -356,6 +356,20 @@ export async function db_save_ae_obj_li__journal_entry(
content_md_html = await marked.parse(content_cleaned ?? '') ?? null; content_md_html = await marked.parse(content_cleaned ?? '') ?? null;
} }
let history = obj.history ?? '';
let history_cleaned: null|string = null;
let history_md_html: null|string = null; // await marked.parse(history_cleaned ?? '') ?? null;
if (obj.history_encrypted) {
// In theory "history" should be null if "history_encrypted" has a value.
history = null; // obj.history_encrypted;
history_cleaned = null;
history_md_html = null;
} else {
history_cleaned = history.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/,"");
history_md_html = await marked.parse(history_cleaned ?? '') ?? null;
}
let obj_record = { let obj_record = {
id: obj.journal_entry_id_random, id: obj.journal_entry_id_random,
journal_entry_id: obj.journal_entry_id_random, journal_entry_id: obj.journal_entry_id_random,
@@ -397,6 +411,12 @@ export async function db_save_ae_obj_li__journal_entry(
content_json: obj.content_json, content_json: obj.content_json,
content_encrypted: obj.content_encrypted, content_encrypted: obj.content_encrypted,
history: obj.history,
history_md_html: history_md_html,
history_encrypted: obj.history_encrypted,
passcode_hash: obj.passcode_hash,
// url: obj.url, // url: obj.url,
// url_text: obj.url_text, // url_text: obj.url_text,

View File

@@ -76,6 +76,8 @@ let journals_session_data_struct: key_val = {
show__modal_view__journal_entry_id: null, show__modal_view__journal_entry_id: null,
show__modal_edit__journal_entry_id: null, show__modal_edit__journal_entry_id: null,
show__content__journal_entry_history: false,
journal: { journal: {
edit: false, edit: false,
edit_kv: {}, edit_kv: {},

View File

@@ -87,6 +87,7 @@ export interface Journal {
// Generated fields for sorting locally only // Generated fields for sorting locally only
tmp_sort_1?: null|string; tmp_sort_1?: null|string;
tmp_sort_2?: null|string; tmp_sort_2?: null|string;
tmp_sort_3?: null|string;
// Additional fields for convenience (database views) // Additional fields for convenience (database views)
file_count?: null|number; // Only files directly under a journal file_count?: null|number; // Only files directly under a journal
@@ -198,6 +199,11 @@ export interface Journal_Entry {
content_json?: null|string; content_json?: null|string;
content_encrypted?: null|string; // This is the encrypted content of the journal entry content_encrypted?: null|string; // This is the encrypted content of the journal entry
history?: null|string; // This is the history of the journal entry; a log
history_encrypted?: null|string; // This is the encrypted history of the journal entry
passcode_hash?: null|string; // This is the passcode hash for the journal entry to look up the passcode
start_datetime?: null|Date; start_datetime?: null|Date;
end_datetime?: null|Date; end_datetime?: null|Date;
timezone?: null|string; timezone?: null|string;
@@ -239,6 +245,7 @@ export interface Journal_Entry {
// Generated fields for sorting locally only // Generated fields for sorting locally only
tmp_sort_1?: null|string; tmp_sort_1?: null|string;
tmp_sort_2?: null|string; tmp_sort_2?: null|string;
tmp_sort_3?: null|string;
// Additional fields for convenience (database views) // Additional fields for convenience (database views)
file_count?: null|number; // Only files directly under a journal file_count?: null|number; // Only files directly under a journal
@@ -299,7 +306,7 @@ export class MySubClassedDexie extends Dexie {
constructor() { constructor() {
super('ae_journals_db'); super('ae_journals_db');
this.version(1).stores({ this.version(3).stores({
journal: ` journal: `
id, journal_id, id, journal_id,
code, code,
@@ -309,7 +316,7 @@ export class MySubClassedDexie extends Dexie {
name, name,
start_datetime, end_datetime, start_datetime, end_datetime,
timezone, timezone,
tmp_sort_1, tmp_sort_2, tmp_sort_1, tmp_sort_2, tmp_sort_3,
enable, hide, priority, sort, group, notes, created_on, updated_on`, enable, hide, priority, sort, group, notes, created_on, updated_on`,
journal_entry: ` journal_entry: `
id, journal_entry_id, id, journal_entry_id,
@@ -320,7 +327,7 @@ export class MySubClassedDexie extends Dexie {
name, name,
start_datetime, end_datetime, start_datetime, end_datetime,
timezone, timezone,
tmp_sort_1, tmp_sort_2, tmp_sort_1, tmp_sort_2, tmp_sort_3,
enable, hide, priority, sort, group, notes, created_on, updated_on`, enable, hide, priority, sort, group, notes, created_on, updated_on`,
}); });
} }

View File

@@ -45,10 +45,13 @@ let lq__journal_obj_li = $derived(liveQuery(async () => {
let results = await db_journals.journal let results = await db_journals.journal
.where('person_id') .where('person_id')
.equals($ae_loc.person_id) .equals($ae_loc.person_id)
// .sortBy('group')
// .sortBy('priority')
// .sortBy('sort')
.reverse() .reverse()
.sortBy('tmp_sort_2') .sortBy('tmp_sort_3')
// .orderBy('tmp_sort_2') // .orderBy('tmp_sort_3')
// .reverse() // .reverse()
// .toArray() // .toArray()

View File

@@ -661,7 +661,7 @@ async function handle_update_journal() {
data_kv: data_kv, data_kv: data_kv,
log_lvl: log_lvl log_lvl: log_lvl
}).then(() => { }).then(() => {
alert('Journal sort order incremented!'); // alert('Journal sort order incremented!');
}).catch((error) => { }).catch((error) => {
console.error('Error updating journal sort order:', error); console.error('Error updating journal sort order:', error);
alert('Failed to update journal sort order.'); alert('Failed to update journal sort order.');
@@ -691,7 +691,7 @@ async function handle_update_journal() {
data_kv: data_kv, data_kv: data_kv,
log_lvl: log_lvl log_lvl: log_lvl
}).then(() => { }).then(() => {
alert('Journal sort order decremented!'); // alert('Journal sort order decremented!');
}).catch((error) => { }).catch((error) => {
console.error('Error updating journal sort order:', error); console.error('Error updating journal sort order:', error);
alert('Failed to update journal sort order.'); alert('Failed to update journal sort order.');

View File

@@ -10,6 +10,7 @@ import {
Eye, EyeOff, Eye, EyeOff,
Flag, FlagOff, FileX, Fingerprint, Flag, FlagOff, FileX, Fingerprint,
Globe, Group, Globe, Group,
History,
LockKeyhole, LockKeyholeOpen, LockKeyhole, LockKeyholeOpen,
MessageSquareWarning, Minus, MessageSquareWarning, Minus,
NotebookPen, NotebookText, NotepadTextDashed, NotebookPen, NotebookText, NotepadTextDashed,
@@ -145,6 +146,8 @@ async function update_journal_entry() {
return; return;
} }
log_lvl = 1;
let data_kv: key_val = { let data_kv: key_val = {
alert: tmp_entry_obj?.alert, alert: tmp_entry_obj?.alert,
personal: tmp_entry_obj?.personal, personal: tmp_entry_obj?.personal,
@@ -162,25 +165,107 @@ async function update_journal_entry() {
category_code: tmp_entry_obj?.category_code, category_code: tmp_entry_obj?.category_code,
content: tmp_entry_obj?.content, content: tmp_entry_obj?.content,
content_encrypted: null, // This should only be generated below. content_encrypted: null, // This should only be generated below.
history: tmp_entry_obj?.history,
history_encrypted: null, // This should only be generated below.
group: tmp_entry_obj?.group, group: tmp_entry_obj?.group,
archive_on: tmp_entry_obj?.archive_on, archive_on: tmp_entry_obj?.archive_on,
name: tmp_entry_obj?.name, name: tmp_entry_obj?.name,
tags: tmp_entry_obj?.tags, tags: tmp_entry_obj?.tags,
}; };
if (tmp_entry_obj?.content) {
let { left_over_string, cut_out_string } = handle_cut_string(tmp_entry_obj?.content);
if (!tmp_entry_obj?.private) {
// let new_content_string = '';
// let cut_content_string = '';
// {new_content_string, cut_content_string}
data_kv.content = left_over_string;
data_kv.history = data_kv.history + '\n\n' + cut_out_string;
} else if (tmp_entry_obj?.private) {
// Need to decrypt the current content and current history. Assume the history has not been decrypted yet.
if (log_lvl) {
console.log('TEST: Decrypting the history before saving it...');
}
decrypted_history = await handle_decrypt_string(tmp_entry_obj?.history_encrypted, journal_key);
data_kv.history = decrypted_history + '\n\n' + cut_out_string;
if (log_lvl) {
console.log('TEST: Encrypting the history before saving it...');
}
let encrypted_combined_data = await handle_encrypt_string(data_kv.history, journal_key);
data_kv.history_encrypted = encrypted_combined_data;
data_kv.history = null;
}
}
// // Check if the content contains a set of special "cut" XML tags. Anything inside the tags will be moved (appended) to the history field. Anything outside the tags should stay. The content may need to be merged back together if something was cut out of the middle. If no closing tag is found, then cut everything to the end of the content. Be sure to prefix the new history content with a timestamp header in Markdown.
// // Example: Hello <cut>Old</cut> World!
// // Example header: # Cut on 2024-11-06T12:34:56Z
// if (tmp_entry_obj?.content) {
// const cutTag = '<cut>';
// const cutEndTag = '</cut>';
// const cutIndex = tmp_entry_obj.content.indexOf(cutTag);
// const cutEndIndex = tmp_entry_obj.content.indexOf(cutEndTag);
// if (cutIndex !== -1) {
// let current_timestamp = new Date().toISOString();
// if (cutEndIndex !== -1) {
// // Cut everything between the cut tags
// const cutContent = tmp_entry_obj.content.substring(cutIndex + cutTag.length, cutEndIndex);
// data_kv.history = data_kv.history + '\n\n' + `# Cut on ${current_timestamp}\n` + cutContent;
// data_kv.content = tmp_entry_obj.content.replace(cutTag + cutContent + cutEndTag, '');
// } else {
// // Cut everything after the cut tag
// const cutContent = tmp_entry_obj.content.substring(cutIndex + cutTag.length);
// data_kv.history = data_kv.history + '\n\n' + `# Cut on ${current_timestamp}\n` + cutContent;
// data_kv.content = tmp_entry_obj.content.substring(0, cutIndex);
// }
// }
// }
// // We also need to support a Markdown variant of the cut tag: ~~cut cut~~.
// // Example: Hello ~~CUT: Old :CUT~~ World!
// // Example: Hello ~~:: Old ::~~ World!
// if (tmp_entry_obj?.content) {
// const cutTag = '~~::';
// const cutEndTag = '::~~';
// const cutIndex = tmp_entry_obj.content.indexOf(cutTag);
// const cutEndIndex = tmp_entry_obj.content.indexOf(cutEndTag);
// if (cutIndex !== -1) {
// let current_timestamp = new Date().toISOString();
// if (cutEndIndex !== -1) {
// // Cut everything between the cut tags
// const cutContent = tmp_entry_obj.content.substring(cutIndex + cutTag.length, cutEndIndex);
// data_kv.history = data_kv.history + '\n\n' + `# Cut on ${current_timestamp}\n` + cutContent;
// data_kv.content = tmp_entry_obj.content.replace(cutTag + cutContent + cutEndTag, '');
// } else {
// // Cut everything after the cut tag
// const cutContent = tmp_entry_obj.content.substring(cutIndex + cutTag.length);
// data_kv.history = data_kv.history + '\n\n' + `# Cut on ${current_timestamp}\n` + cutContent;
// data_kv.content = tmp_entry_obj.content.substring(0, cutIndex);
// }
// }
// }
if (tmp_entry_obj?.content && tmp_entry_obj?.private) { if (tmp_entry_obj?.content && tmp_entry_obj?.private) {
// console.log('TEST: Saving encrypted content', tmp_entry_obj?.content); // console.log('TEST: Saving encrypted content', tmp_entry_obj?.content);
content = tmp_entry_obj?.content; content = tmp_entry_obj?.content;
// console.log('TEST: journal_key', journal_key); // console.log('TEST: journal_key', journal_key);
// Encrypt the content // // Encrypt the content
let encrypted_base64 = await ae_util.encrypt_content(content, journal_key); // let encrypted_base64 = await ae_util.encrypt_content(content, journal_key);
encrypted_base64_content = encrypted_base64.base64; // encrypted_base64_content = encrypted_base64.base64;
encryption_iv = encrypted_base64.iv; // encryption_iv = encrypted_base64.iv;
console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`); // console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`);
// Combine the IV and encrypted content // // Combine the IV and encrypted content
const combined_data = Array.from(encryption_iv).map(byte => byte.toString(16).padStart(2, '0')).join('') + ':' + encrypted_base64_content; // const combined_data = Array.from(encryption_iv).map(byte => byte.toString(16).padStart(2, '0')).join('') + ':' + encrypted_base64_content;
let combined_data = await handle_encrypt_string(content, journal_key);
data_kv.content_encrypted = combined_data; data_kv.content_encrypted = combined_data;
data_kv.content = null; data_kv.content = null;
@@ -193,7 +278,7 @@ async function update_journal_entry() {
decrypted_content = ''; decrypted_content = '';
} else if (tmp_entry_obj?.content_encrypted && !tmp_entry_obj?.private) { } else if (tmp_entry_obj?.content_encrypted && !tmp_entry_obj?.private) {
console.log('TEST: Decrypting the content before saving it...'); console.log('TEST: Decrypting the content before saving it...');
await handle_decrypt_content(); decrypted_content = await handle_decrypt_string(tmp_entry_obj?.content_encrypted, journal_key);
data_kv.content = decrypted_content; // tmp_entry_obj.content data_kv.content = decrypted_content; // tmp_entry_obj.content
decrypted_content = ''; decrypted_content = '';
// return false; // return false;
@@ -298,6 +383,7 @@ let encrypted_base64_content: string = $state('');
let encryption_iv: null|Uint8Array<ArrayBuffer> = $state(null); let encryption_iv: null|Uint8Array<ArrayBuffer> = $state(null);
let decrypted_content: string = $state(''); let decrypted_content: string = $state('');
let trigger_decrypt: boolean = $state(false); let trigger_decrypt: boolean = $state(false);
let decrypted_history: string = $state('');
$effect(() => { $effect(() => {
if ($lq__journal_obj?.passcode) { if ($lq__journal_obj?.passcode) {
@@ -306,76 +392,194 @@ $effect(() => {
} }
}); });
$effect(async () => { // $effect(async () => {
if (tmp_entry_obj?.content_encrypted && trigger_decrypt) { // if (tmp_entry_obj?.content_encrypted && trigger_decrypt) {
trigger_decrypt = false; // trigger_decrypt = false;
handle_decrypt_content(); // handle_decrypt_content();
} // }
// if (tmp_entry_obj?.content) { // // if (tmp_entry_obj?.content) {
// content = tmp_entry_obj?.content; // // content = tmp_entry_obj?.content;
// let encrypted_base64 = await ae_util.encrypt_content(content, journal_key); // // let encrypted_base64 = await ae_util.encrypt_content(content, journal_key);
// encrypted_base64_content = encrypted_base64.base64; // // encrypted_base64_content = encrypted_base64.base64;
// encryption_iv = encrypted_base64.iv; // // encryption_iv = encrypted_base64.iv;
// let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key); // // let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key);
// // decrypted_content = decrypted;
// // if (log_lvl) {
// // console.log('Decrypted content:', decrypted_content);
// // }
// // }
// // console.log('Encrypted content:', base64);
// // console.log('IV:', iv);
// });
// async function handle_decrypt_content() {
// log_lvl = 1;
// if (log_lvl) {
// console.log('TEST: handle_decrypt_content');
// }
// let combined_data = tmp_entry_obj?.content_encrypted;
// let [encryption_iv_hex, encrypted_base64_content] = combined_data.split(':');
// encryption_iv = new Uint8Array(encryption_iv_hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
// if (log_lvl) {
// console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`);
// }
// let decrypted: string|null = null;
// try {
// decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key);
// } catch (error) {
// console.error('Error decrypting content:', error);
// alert('Failed to decrypt content. Please check the passcode.');
// return;
// }
// // let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key);
// // decrypted_content = 'XXX '+decrypted+' XXX';
// if (!decrypted) {
// alert('Failed to decrypt content. Please check the passcode.');
// return;
// }
// decrypted_content = decrypted; // decrypted_content = decrypted;
// if (log_lvl) { // if (log_lvl) {
// console.log('Decrypted content:', decrypted_content); // console.log('Decrypted content:', decrypted_content);
// } // }
// } // tmp_entry_obj.content = decrypted_content;
// console.log('Encrypted content:', base64); // // orig_entry_obj.content = decrypted_content;
// console.log('IV:', iv); // // tmp_entry_obj_changed = false;
});
async function handle_decrypt_content() { // // tmp_entry_obj.content_encrypted = null;
// }
async function handle_decrypt_string(encrypted_string: string, passcode: string) {
log_lvl = 1; log_lvl = 1;
if (log_lvl) { if (log_lvl) {
console.log('TEST: handle_decrypt_content'); console.log(`TEST: handle_decrypt_string: ${passcode}`, encrypted_string);
} }
let combined_data = tmp_entry_obj?.content_encrypted; if (!encrypted_string) {
console.log('TEST: No encrypted string provided');
return '';
}
if (!passcode) {
console.log('TEST: No journal key provided');
return false;
}
let combined_data = encrypted_string;
let [encryption_iv_hex, encrypted_base64_content] = combined_data.split(':'); let [encryption_iv_hex, encrypted_base64_content] = combined_data.split(':');
encryption_iv = new Uint8Array(encryption_iv_hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); encryption_iv = new Uint8Array(encryption_iv_hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
if (log_lvl) { if (log_lvl) {
console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`); console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`);
} }
let decrypted: string|null = null; // Decrypt the string using the journal key
let decrypted_string = '';
try { try {
decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key); decrypted_string = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, passcode);
} catch (error) { } catch (error) {
console.error('Error decrypting content:', error); console.error('Error decrypting content:', error);
alert('Failed to decrypt content. Please check the passcode.'); alert('Failed to decrypt content. Please check the passcode.');
return; return;
} }
// let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key); return decrypted_string;
// decrypted_content = 'XXX '+decrypted+' XXX';
if (!decrypted) {
alert('Failed to decrypt content. Please check the passcode.');
return;
} }
decrypted_content = decrypted;
async function handle_encrypt_string(text_string: string, passcode: string) {
log_lvl = 1;
if (log_lvl) { if (log_lvl) {
console.log('Decrypted content:', decrypted_content); console.log('TEST: handle_encrypt_string');
}
if (!text_string) {
console.log('TEST: No text string provided');
return '';
}
if (!passcode) {
console.log('TEST: No journal key provided');
return false;
} }
tmp_entry_obj.content = decrypted_content;
// orig_entry_obj.content = decrypted_content;
// tmp_entry_obj_changed = false;
// tmp_entry_obj.content_encrypted = null; // Encrypt the string using the journal key
let encrypted_base64 = await ae_util.encrypt_content(text_string, passcode);
let encrypted_base64_content = encrypted_base64.base64;
let encryption_iv = encrypted_base64.iv;
console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`);
// Combine the IV and encrypted content
const combined_data = Array.from(encryption_iv).map(byte => byte.toString(16).padStart(2, '0')).join('') + ':' + encrypted_base64_content;
return combined_data;
} }
// return new_string and cut_string
function handle_cut_string(old_string: string) {
// Check if the string contains a set of special "cut" XML tags or Markdown. Anything inside the tags or Markdown will be moved (appended) to the history field. Anything outside the tags should stay. The string may need to be merged back together if something was cut out of the middle. If no closing tag is found, then cut everything to the end of the string. Be sure to prefix the new history sting with a timestamp header in Markdown.
// Example: Hello <cut>Old</cut> World!
// Example header: # Cut on 2024-11-06T12:34:56Z
// We also need to support a Markdown variant of the cut tag: ~~cut cut~~.
// Example: Hello ~~CUT: Old :CUT~~ World!
// Example: Hello ~~:: Old ::~~ World!
let left_over_string = old_string; // Will be for the updated content field
let cut_out_string = ''; // Will be for the history field
if (old_string) {
let cut_tag = '<cut>';
let cut_end_tag = '</cut>';
let cut_index = old_string.indexOf(cut_tag);
let cut_end_index = old_string.indexOf(cut_end_tag);
let cut_prefix = `# Cut on ${new Date().toISOString()}\n`;
if (cut_index !== -1) {
if (cut_end_index !== -1) {
// Cut everything between the cut tags
const cut_content = old_string.substring(cut_index + cut_tag.length, cut_end_index);
cut_out_string = cut_prefix + cut_content;
left_over_string = old_string.replace(cut_tag + cut_content + cut_end_tag, '');
} else {
// Cut everything after the cut tag
const cut_content = old_string.substring(cut_index + cut_tag.length);
cut_out_string = cut_prefix + cut_content;
left_over_string = old_string.substring(0, cut_index);
}
}
cut_tag = '~~::';
cut_end_tag = '::~~';
cut_index = old_string.indexOf(cut_tag);
cut_end_index = old_string.indexOf(cut_end_tag);
cut_prefix = `# Cut on ${new Date().toISOString()}\n`;
if (cut_index !== -1) {
if (cut_end_index !== -1) {
// Cut everything between the cut tags
const cut_content = old_string.substring(cut_index + cut_tag.length, cut_end_index);
cut_out_string = cut_prefix + cut_content;
left_over_string = old_string.replace(cut_tag + cut_content + cut_end_tag, '');
} else {
// Cut everything after the cut tag
const cut_content = old_string.substring(cut_index + cut_tag.length);
cut_out_string = cut_prefix + cut_content;
left_over_string = old_string.substring(0, cut_index);
}
}
}
return { left_over_string, cut_out_string };
}
</script> </script>
<section class="svelte_component ae_section ae_view journal_entry_obj view__journal_entry_obj bg-white flex flex-col flex-grow items-center justify-center rounded-lg w-full h-full p-2 m-2 space-y-2" bind:clientHeight={$ae_loc.iframe_height_modal_body}> <section class="svelte_component ae_section ae_view journal_entry_obj view__journal_entry_obj bg-white flex flex-col flex-grow items-center justify-center rounded-lg w-full h-full p-2 m-2 space-y-2" bind:clientHeight={$ae_loc.iframe_height_modal_body}>
{#if $lq__journal_entry_obj} {#if $lq__journal_entry_obj}
<header class="ae_header journal_entry__header flex flex-row flex-wrap gap-2 items-center justify-between w-full"> <header class="ae_header journal_entry__header flex flex-row flex-wrap gap-2 items-center justify-between w-full">
<div class="flex flex-row flex-wrap gap-2 items-center justify-start"> <div class="flex flex-row flex-wrap gap-2 items-center justify-start">
@@ -421,7 +625,7 @@ async function handle_decrypt_content() {
<input <input
type="text" type="text"
bind:value={tmp_entry_obj.name} bind:value={tmp_entry_obj.name}
class="input input-bordered" class="input input-bordered w-96"
placeholder="Journal Entry Name" placeholder="Journal Entry Name"
title="Edit the name of this journal entry" title="Edit the name of this journal entry"
/> />
@@ -670,7 +874,8 @@ async function handle_decrypt_content() {
</header> </header>
<div
<section
class="flex flex-row flex-wrap gap-1 items-center justify-evenly w-full max-w-sm " class="flex flex-row flex-wrap gap-1 items-center justify-evenly w-full max-w-sm "
> >
<!-- Entry alert status --> <!-- Entry alert status -->
@@ -762,7 +967,8 @@ async function handle_decrypt_content() {
// tmp_entry_obj.content = null; // tmp_entry_obj.content = null;
} else if (tmp_entry_obj?.private && tmp_entry_obj?.content_encrypted) { } else if (tmp_entry_obj?.private && tmp_entry_obj?.content_encrypted) {
if (confirm('Are you sure you want to decrypt the content to view/edit?')) { if (confirm('Are you sure you want to decrypt the content to view/edit?')) {
trigger_decrypt = true; // trigger_decrypt = true;
handle_decrypt_content();
} else { } else {
return false; return false;
} }
@@ -885,13 +1091,11 @@ async function handle_decrypt_content() {
{/if} {/if}
</button> --> </button> -->
</div> </section>
{#if (!$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id])}
<!-- svelte-ignore a11y_no_noninteractive_tabindex --> <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<div <!-- ondblclick={() => {
ondblclick={() => {
if ($ae_loc.trusted_access && $ae_loc.edit_mode) { if ($ae_loc.trusted_access && $ae_loc.edit_mode) {
// Toggle edit mode // Toggle edit mode
$journals_loc.entry.edit = !$journals_loc.entry.edit; $journals_loc.entry.edit = !$journals_loc.entry.edit;
@@ -899,24 +1103,29 @@ async function handle_decrypt_content() {
} }
}} }}
role={$ae_loc.edit_mode ? 'button' : 'article'} role={$ae_loc.edit_mode ? 'button' : 'article'}
tabindex={$ae_loc.edit_mode ? 0 : -1} tabindex={$ae_loc.edit_mode ? 0 : -1} -->
<section
class=" class="
flex-grow flex-grow
flex flex-col items-center justify-center basis-full
flex flex-col flex-wrap items-center justify-center
h-full min-h-max max-h-full
w-full max-w-6xl w-full max-w-6xl
" "
> >
{#if (!$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id])}
<article <article
class:border-orange-200={$ae_loc.edit_mode} class:border-orange-200={$ae_loc.edit_mode}
class:hover:border-orange-500={$ae_loc.edit_mode} class:hover:border-orange-500={$ae_loc.edit_mode}
class=" class="
space-y-1
prose
flex-grow flex-grow
h-full min-h-48 w-full min-w-full max-w-6xl basis-full
h-full min-h-max max-h-full
w-full min-w-full max-w-6xl
p-2 p-2
space-y-1
font-mono font-mono
!bg-slate-100 !text-gray-900 !bg-slate-100 !text-gray-900
dark:!bg-slate-900 dark:!text-gray-100 dark:!bg-slate-900 dark:!text-gray-100
@@ -924,6 +1133,7 @@ async function handle_decrypt_content() {
shadow-md rounded-lg shadow-md rounded-lg
border border-gray-200 dark:border-gray-700 border border-gray-200 dark:border-gray-700
hover:border-gray-500 dark:hover:border-gray-500 hover:border-gray-500 dark:hover:border-gray-500
prose
prose-h1:underline prose-h1:decoration-double prose-h1:underline prose-h1:decoration-double
prose-h2:underline prose-h2:underline
prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg
@@ -954,15 +1164,19 @@ async function handle_decrypt_content() {
<!-- {@html encrypt_content($lq__journal_entry_obj?.content, journal_key)} --> <!-- {@html encrypt_content($lq__journal_entry_obj?.content, journal_key)} -->
<!-- --{@html encrypted_base64_content}-- --> <!-- --{@html encrypted_base64_content}-- -->
<!-- {@html marked.parse($lq__journal_entry_obj?.content)} --> <!-- {@html marked.parse($lq__journal_entry_obj?.content)} -->
</div>
{:else if ($journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id])} {:else if ($journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id])}
<!-- && !($lq__journal_entry_obj?.content_encrypted && decrypted_content)) --> <!-- && !($lq__journal_entry_obj?.content_encrypted && decrypted_content)) -->
<!-- class="flex flex-row flex-wrap gap-1 items-center justify-center w-full max-w-sm" --> <!-- class="flex flex-row flex-wrap gap-1 items-center justify-center w-full max-w-sm" -->
{#if ($lq__journal_entry_obj?.content_encrypted && !decrypted_content)} {#if ($lq__journal_entry_obj?.content_encrypted && !decrypted_content)}
<div <div
class=" class="
flex-grow min-h-48 h-full w-full flex-grow
basis-full
h-full min-h-max max-h-full
w-full min-w-full max-w-6xl
p-2 p-2
space-y-1
bg-red-100 text-gray-900 bg-red-100 text-gray-900
dark:bg-red-900 dark:text-gray-100 dark:bg-red-900 dark:text-gray-100
shadow-lg rounded-lg shadow-lg rounded-lg
@@ -991,31 +1205,33 @@ async function handle_decrypt_content() {
</button> --> </button> -->
</div> </div>
{:else} {:else}
<div
<!-- <div
class=" class="
flex-grow flex-grow
flex flex-col items-center justify-center h-full max-h-full
w-full max-w-6xl w-full min-w-full max-w-6xl"
" > -->
>
<textarea <textarea
bind:value={tmp_entry_obj.content} bind:value={tmp_entry_obj.content}
ondblclick={() => { ondblclick={() => {
if ($ae_loc.trusted_access && $ae_loc.edit_mode) { // if ($ae_loc.trusted_access && $ae_loc.edit_mode) {
// Toggle edit mode // // Toggle edit mode
$journals_loc.entry.edit = !$journals_loc.entry.edit; // $journals_loc.entry.edit = !$journals_loc.entry.edit;
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = $journals_loc.entry.edit; // $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = $journals_loc.entry.edit;
} // }
}} }}
disabled={tmp_entry_obj.content_encrypted && !decrypted_content} disabled={tmp_entry_obj.content_encrypted && !decrypted_content}
class:border-orange-200={$ae_loc.edit_mode} class:border-orange-200={$ae_loc.edit_mode}
class:hover:border-orange-500={$ae_loc.edit_mode} class:hover:border-orange-500={$ae_loc.edit_mode}
class=" class="
space-y-1
flex-grow flex-grow
h-full min-h-max max-h-full w-full min-w-full max-w-6xl flex-shrink-0
basis-full
h-max min-h-max max-h-full
w-full min-w-full max-w-6xl
p-2 p-2
space-y-1
font-mono font-mono
bg-slate-100 text-gray-900 bg-slate-100 text-gray-900
dark:bg-slate-900 dark:text-gray-100 dark:bg-slate-900 dark:text-gray-100
@@ -1055,16 +1271,149 @@ async function handle_decrypt_content() {
Updated obj? {updated_obj} --> Updated obj? {updated_obj} -->
<!-- && $lq__journal_entry_obj?.updated_on !== orig_entry_obj?.updated_on --> <!-- && $lq__journal_entry_obj?.updated_on !== orig_entry_obj?.updated_on -->
<!-- </div> -->
{/if}
{/if}
<!-- If there is history, then we want a toggle button to show and hide the history. -->
{#if ($lq__journal_entry_obj?.history || $lq__journal_entry_obj?.history_encrypted)}
<div
class="
flex flex-col flex-wrap gap-1 items-center justify-center h-full w-full
"
>
<div>
<button
type="button"
onclick={async () => {
if ($journals_sess.show__content__journal_entry_history == 'view') {
$journals_sess.show__content__journal_entry_history = 'hide';
} else if ($journals_sess.show__content__journal_entry_history == 'edit') {
$journals_sess.show__content__journal_entry_history = 'hide';
} else if ($journals_sess.show__content__journal_entry_history == 'hide') {
$journals_sess.show__content__journal_entry_history = 'view';
} else if (!$journals_sess.show__content__journal_entry_history) {
$journals_sess.show__content__journal_entry_history = 'view';
}
if (!tmp_entry_obj?.history && tmp_entry_obj?.history_encrypted) {
decrypted_history = await handle_decrypt_string(tmp_entry_obj.history_encrypted, journal_key);
console.log('Decrypted history:', decrypted_history);
tmp_entry_obj.history = decrypted_history;
}
}}
class="btn btn-sm variant-soft-secondary hover:variant-filled-secondary *:hover:inline lg:text-xs"
title="Toggle history of this journal entry"
>
<History strokeWidth="2.5" color="blue" />
<span class="hidden sm:inline">History</span>
</button>
<button
type="button"
class:hidden={!$journals_sess.show__content__journal_entry_history}
onclick={() => {
if ($journals_sess.show__content__journal_entry_history == 'view') {
$journals_sess.show__content__journal_entry_history = 'edit';
} else if ($journals_sess.show__content__journal_entry_history == 'edit') {
$journals_sess.show__content__journal_entry_history = 'view';
}
}}
class="btn btn-sm variant-soft-secondary hover:variant-filled-secondary *:hover:inline lg:text-xs"
title="Toggle edit mode for history of this journal entry"
>
{#if $journals_sess.show__content__journal_entry_history == 'view'}
<PenLine strokeWidth="2.5" color="blue" class="inline-block" />
{:else if $journals_sess.show__content__journal_entry_history == 'edit'}
<Pencil strokeWidth="2.5" color="blue" class="inline-block" />
{/if}
<span class="hidden sm:inline">
{#if $journals_sess.show__content__journal_entry_history == 'view'}
Edit
{:else if $journals_sess.show__content__journal_entry_history == 'edit'}
View
{/if}
</span>
</button>
</div> </div>
<div
class="
flex-grow
basis-full
flex flex-col items-center justify-center
h-full
w-full max-w-6xl
"
>
{#if $journals_sess?.show__content__journal_entry_history == 'view'}
<article
class:border-orange-200={$ae_loc.edit_mode}
class:hover:border-orange-500={$ae_loc.edit_mode}
class="
space-y-1
h-full min-h-max max-h-full
w-full min-w-full max-w-6xl
p-2
font-mono
!bg-slate-100 !text-gray-900
dark:!bg-slate-900 dark:!text-gray-100
dark:prose-invert
shadow-md rounded-lg
border border-gray-200 dark:border-gray-700
hover:border-gray-500 dark:hover:border-gray-500
prose
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_entry_obj?.history_md_html}
</article>
{:else if $journals_sess?.show__content__journal_entry_history == 'edit'}
<textarea
bind:value={tmp_entry_obj.history}
class:border-orange-200={$ae_loc.edit_mode}
class:hover:border-orange-500={$ae_loc.edit_mode}
class="
space-y-1
flex-grow
basis-full
h-full min-h-max max-h-full
w-full min-w-full max-w-6xl
p-2
font-mono
bg-slate-100 text-gray-900
dark:bg-slate-900 dark:text-gray-100
shadow-lg rounded-lg
border border-gray-200 dark:border-gray-700
hover:border-gray-500 dark:hover:border-gray-500
"
placeholder="Edit journal entry content here..."
></textarea>
{/if} {/if}
</div>
</div>
{/if} {/if}
</section>
<!-- <div> <!-- <div>
{@html test_html} {@html test_html}
</div> --> </div> -->
<div class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-between w-full"> <section class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-between w-full">
<!-- {$lq__journal_entry_obj?.priority} <!-- {$lq__journal_entry_obj?.priority}
{$lq__journal_entry_obj?.sort} {$lq__journal_entry_obj?.sort}
{$lq__journal_entry_obj?.group} {$lq__journal_entry_obj?.group}
@@ -1333,7 +1682,7 @@ async function handle_decrypt_content() {
{/if} {/if}
</div> </section>
{/if} {/if}

View File

@@ -140,11 +140,11 @@ $effect(() => {
{/if} {/if}
{#if (journals_journal_entry_obj.priority)} {#if (journals_journal_entry_obj.priority)}
<Flag size="1.25emem" class="mx-1 inline-block text-yellow-500"/> <Flag size="1.25em" class="mx-1 inline-block text-yellow-500"/>
{/if} {/if}
{#if (journals_journal_entry_obj.group)} {#if (journals_journal_entry_obj.group)}
<Group size="1.25emem" class="mx-1 inline-block text-green-500"/> <Group size="1.25em" class="mx-1 inline-block text-green-500"/>
<span class="text-xs text-gray-500 hidden">Group:</span> <span class="text-xs text-gray-500 hidden">Group:</span>
<span class="font-semibold text-sm text-gray-500 hidden md:inline"> <span class="font-semibold text-sm text-gray-500 hidden md:inline">
{journals_journal_entry_obj.group} {journals_journal_entry_obj.group}

View File

@@ -48,6 +48,8 @@ let { lq__journal_obj_li }: Props = $props();
{/if} {/if}
</header> </header>
<!-- {journals_journal_obj?.tmp_sort_3} -->
{#if journals_journal_obj.description} {#if journals_journal_obj.description}
<div <div
class=" class="