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