diff --git a/README.md b/README.md new file mode 100644 index 0000000..2023059 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# WhichNot + +WhichNot is an experimental offline-first note-taking application, aimed at recreating the look-and-feel and ease of use of a standard messaging app. + +**Try and use it now at !** (Includes a demo notebook with more info about the app.) + +![App screenshot](https://octospacc.altervista.org/wp-content/uploads/2025/04/image-22.png) \ No newline at end of file diff --git a/app.js b/app.js index 7d72f92..490d8da 100644 --- a/app.js +++ b/app.js @@ -113,7 +113,7 @@ const uuidv7 = () => { } const generateUUID = () => uuidv7(); // crypto.randomUUID(); const genAESKey = async () => crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']); -// const genEd25519 = async () => crypto.subtle.generateKey({ name: 'Ed25519', namedCurve: 'Ed25519' }, true, ['sign', 'verify']); +const genEcdsaP256 = async () => crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']); const exportJWK = async (key) => btoa(JSON.stringify(await crypto.subtle.exportKey('jwk', key))); const importJWK = async (b64, alg, usages) => crypto.subtle.importKey('jwk', JSON.parse(atob(b64)), alg, true, usages); const randBytes = (n=12) => { @@ -175,6 +175,14 @@ const randomEmoji = () => EMOJIS[Math.floor(Math.random() * EMOJIS.length)]; const randomColor = () => ('#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0')); const closedContextMenu = s => ({ contextMenu: { ...s.contextMenu, visible: false } }); +const makeTextareaHeight = text => { + let lines = text.split('\n').length; + if (lines > 10) { + lines = 10; + } + return `${lines + 2}em`; +}; +const textareaInputHandler = el => (el.style.minHeight = makeTextareaHeight(el.value)); function App() { const [state, setState] = useState({ @@ -186,6 +194,7 @@ function App() { contextMenu:{ visible: false, messageId: null, x: 0, y: 0 }, searchModal: { visible: false, global: false, query: '' }, editingMessage: null, replyingTo: null, reactionInputFor: null, + debugMode: false, }); const isFirstHashPush = useRef(true); const messageInputRef = useRef(); @@ -283,13 +292,14 @@ function App() { let id = /* (type === 'local' ? */ generateUUID(); /* : prompt('Remote ID:')); */ // if (!id) return; const now = Date.now(); - // const ed = await genEd25519(); + // const ecdsa = await genEcdsaP256(); const notebook = { id, name: `${STRINGS.get('Notebook')} ${now}`, description: '', emoji: randomEmoji(), color: randomColor(), parseMode: "markdown", // sourceType: type, nextMessageId: 1, created: now, - aesKeyB64: await exportJWK(await genAESKey()), // edPrivB64: await exportJWK(ed.privateKey), edPubB64: await exportJWK(ed.publicKey), + aesKeyB64: await exportJWK(await genAESKey()), + // ecdsaPrivB64: await exportJWK(ecdsa.privateKey), ecdsaPubB64: await exportJWK(ecdsa.publicKey), }; setState(s => ({ ...s, notebooks: [ ...s.notebooks, notebook ], @@ -380,6 +390,7 @@ function App() { const message = state.messages[state.selectedNotebookId]?.[state.editingMessage]; if (message) { messageInputRef.current.value = message.text; + textareaInputHandler(messageInputRef.current); } } }, [state.editingMessage, state.selectedNotebookId, state.messages]); @@ -404,6 +415,7 @@ function App() { } message = { ...message, text, edited: (state.editingMessage!=null ? (text !== message.text ? Date.now() : message.edited) : false), }; messageInputRef.current.value = ''; + messageInputRef.current.style.minHeight = null; // update nextMessageId if new setState(s => ({ ...s, notebooks: s.notebooks.map(notebook => notebook.id===notebookId ? { ...notebook, nextMessageId: (state.editingMessage==null ? notebook.nextMessageId+1 : notebook.nextMessageId) } @@ -501,7 +513,7 @@ function ChatScreen({messageInputRef}) { ${!notebook.readonly && html`
${state.replyingTo && html`
- ${STRINGS.get('Reply to')}: "${ + ${STRINGS.get('Reply to')}: "${ getMessage(state.replyingTo.notebookId, state.replyingTo.messageId)?.text || '' }" @@ -514,7 +526,7 @@ function ChatScreen({messageInputRef}) { ev.preventDefault(); sendMessage(); } - }}/> + }} onInput=${ev => textareaInputHandler(ev.target)} />
`}
@@ -557,7 +569,12 @@ function Message({message, notebook}) { `)} ${!notebook.readonly && (state.reactionInputFor===message.id - ? html` e.key==='Enter' && (confirmReaction(message.id, e.target.value), e.target.value='')} />` + ? html` { + if (ev.key==='Enter') { + confirmReaction(message.id, ev.target.value); + ev.target.value = ''; + } + }} />` : html`` )} @@ -682,7 +699,7 @@ function NotebookSettingsModal() {

-

+