This commit is contained in:
2025-04-21 22:49:01 +02:00
parent da96f61d28
commit c1cc1d55eb

View File

@ -1391,6 +1391,7 @@ render(html`<${App}/>`, document.body);
.App.show-chat .ChatScreen { display: flex; }
}
</style>
<script src="../Downloads/localforage.min.js"></script>
</head>
<body>
<script type="module">
@ -1398,6 +1399,7 @@ import { h, render, createContext } from 'https://esm.sh/preact';
import { useState, useEffect, useCallback, useRef, useContext } from 'https://esm.sh/preact/hooks';
import htm from 'https://esm.sh/htm';
const html = htm.bind(h), AppContext = createContext();
localforage.config({ name: "WhichNot" });
const uuidv7 = () => {
const bytes = new Uint8Array(16);
@ -1482,11 +1484,11 @@ function App() {
// Load & decrypt
useEffect(() => {
const raw=JSON.parse(localStorage.getItem('notebooks')) || [],
enc={}, msgs={};
(async () => {
const raw = await localforage.getItem('notebooks') || [],
enc = {}, msgs = {};
for (const notebook of raw) {
const arr=JSON.parse(localStorage.getItem(`notebook-${notebook.id}`)) || [];
const arr = await localforage.getItem(`notebook-${notebook.id}`) || [];
enc[notebook.id] = arr;
const rawKey = await getAesRawKey(notebook.aesKeyB64);
const plain = {}; // [];
@ -1502,11 +1504,11 @@ function App() {
}, []);
// Persist notebooks meta
useEffect(() => localStorage.setItem('notebooks', JSON.stringify(state.notebooks)), [state.notebooks]);
useEffect(() => localforage.setItem('notebooks', state.notebooks), [state.notebooks]);
// Persist encrypted store
useEffect(() => {
for (const id in state.encrypted) {
localStorage.setItem(`notebook-${id}`, JSON.stringify(state.encrypted[id]));
localforage.setItem(`notebook-${id}`, state.encrypted[id]);
}
}, [state.encrypted]);
@ -1559,6 +1561,13 @@ function App() {
return (messageId ? messages[messageId] : messages);
// return (messageId ? messages.find(message => (message.id === messageId)) : messages);
}, [state.messages]);
const saveMessage = (notebookId, message) => persistMessages(notebookId, Object.values({ ...getMessages(notebookId), [message.id]: message }));
const deleteMessage = (notebookId, messageId) => {
const messages = getMessages(notebookId);
delete messages[messageId];
persistMessages(notebookId, Object.values(messages));
// setState(s => ({ ...s, messages: { ...s.messages, [nbId]: messages } }));
};
const persistMessages = useCallback(async(nbId, plainArr) => {
const notebook = getNotebook(nbId);
@ -1584,12 +1593,22 @@ function App() {
// }
}, [state.notebooks]);
const addReaction = useCallback(idx => setState(s => ({ ...s, reactionInputFor: idx })), []);
const addReaction = useCallback(messageId => setState(s => ({ ...s, reactionInputFor: messageId })), []);
const confirmReaction = useCallback(async (idx, emoji) => {
if(!emoji) return setState(s=>({...s,reactionInputFor:null}));
const nbId=state.selectedNotebook, arr=state.messages[nbId]||[];
const newArr=arr.map((m,i)=>i===idx?{...m,reactions:m.reactions.includes(emoji)?m.reactions:[...m.reactions,emoji]}:m);
await persistMessages(nbId,newArr);
if (!emoji) return;
setState(s => ({ ...s, reactionInputFor: null }));
const nbId = state.selectedNotebook;
//const arr = state.messages[nbId] || [];
//const newArr = arr.map((m, i) => i===idx ? { ...m, reactions: (m.reactions.includes(emoji) ? m.reactions : [...m.reactions, emoji]) } : m);
//const newArr = getMessages(nbId);
//const m = newArr[idx];
//newArr[idx] = { ...m, reactions: (m.reactions.includes(emoji) ? m.reactions : [...m.reactions, emoji]) }
//await persistMessages(nbId, newArr);
const message = getMessages(nbId, idx);
if (!message.reactions.includes(emoji)) {
message.reactions = [...message.reactions, emoji];
saveMessage(nbId, message);
}
setState(s => ({ ...s, reactionInputFor: null }));
},[state.selectedNotebook, state.messages, persistMessages]);
const removeReaction = useCallback(async (idx, emoji) => {
@ -1606,7 +1625,6 @@ function App() {
if (message) {
inputRef.current.value = message.text;
}
console.log(state, message, state.messages[state.selectedNotebook]);
}
}, [state.editingMessage, state.selectedNotebook, state.messages]);
@ -1645,13 +1663,6 @@ function App() {
await persistMessages(nbId, newArr);
}, [state.selectedNotebook, state.editingMessage, state.replyingTo, state.messages, state.notebooks]);
const deleteMessage = (notebookId, messageId) => {
const messages = getMessages(notebookId);
delete messages[messageId];
persistMessages(notebookId, Object.values(messages));
// setState(s => ({ ...s, messages: { ...s.messages, [nbId]: messages } }));
};
return html`
<${AppContext.Provider} value=${{
state, setState, createNotebook,
@ -1681,7 +1692,7 @@ function ChatList() {
<div class="ChatList">
<div class="ChatList-header">
<button onClick=${() => setState(s => ({ ...s, createModal: true }))}></button>
<button onClick=${() => setState(s => ({ ...s, searchModal: { visible: true, global: true, query: '' } }))}>🔍</button>
<!-- <button onClick=${() => setState(s => ({ ...s, searchModal: { visible: true, global: true, query: '' } }))}>🔍</button> -->
<button onClick=${() => setState(s => ({ ...s, showAppSettings: true }))}>⚙️</button>
</div>
${state.notebooks.sort((a, b) => (sortNotebook(b) - sortNotebook(a))).map(notebook => html`
@ -1733,13 +1744,13 @@ function ChatScreen({inputRef}) {
</button>
<div class="NotebookEmoji" style=${{ background: notebook.color }}>${notebook.emoji}</div>
<h3>${notebook.name}</h3>
<button class="SearchButton"
<!-- <button class="SearchButton"
onClick=${ev => {
ev.stopPropagation();
setState(s => ({ ...s, searchModal: { visible: true, global: false, query: '' }}));
}}>
🔍
</button>
</button> -->
</div>
<div class="Messages">
${messages.map(message => html`
@ -1918,7 +1929,7 @@ function SettingsModal() {
const del = () => {
if (confirm('Delete?')) {
if (notebook.sourceType==='local') {
localStorage.removeItem(`notebook-${notebook.id}`);
localforage.removeItem(`notebook-${notebook.id}`);
}
setState(s => ({ ...s,
notebooks: s.notebooks.filter(n => n.id!==notebook.id),
@ -1973,14 +1984,14 @@ function SearchModal() {
function AppSettingsModal() {
const {state,setState} = useContext(AppContext);
const exportData = () => JSON.stringify({ notebooks: state.notebooks, encrypted: state.encrypted }, null, 2);
const exportData = () => JSON.stringify({ notebooks: state.notebooks, encrypted: /* Object.fromEntries(Object.entries( */ state.encrypted /* ).map(([key, values]) => ([key, Object.values(values)]))) */ }, null, 2);
const [txt,setTxt] = useState('');
const doImport = () => {
try {
const obj = JSON.parse(txt);
if (obj.notebooks && obj.encrypted) {
localStorage.setItem('notebooks', JSON.stringify(obj.notebooks));
Object.entries(obj.encrypted).forEach(([id,arr]) => localStorage.setItem(`notebook-${id}`, JSON.stringify(arr)));
localforage.setItem('notebooks', obj.notebooks);
Object.entries(obj.encrypted).forEach(([id,arr]) => localforage.setItem(`notebook-${id}`, arr));
window.location.reload();
} else {
alert('Invalid format');