speed up folder handling by using a list for folders and letters assinged to them rather than multiple different events

This commit is contained in:
Danny Morabito 2024-11-29 17:32:11 +01:00
parent f70f4c4518
commit 1b2177933c
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
7 changed files with 95 additions and 129 deletions

View file

@ -16,7 +16,6 @@
async function addNewFolderPressed() {
const newFolder = await createFolder(newFolderName, '');
if (!newFolder) return;
await newFolder.publish();
folders = [...folders, { id: newFolder.id, name: newFolderName, icon: 'eos-icons:plus' }];
newFolderName = '';
isAddingFolder = false;

View file

@ -1,12 +1,13 @@
<script lang="ts">
import { groupByStamps, sortBy } from '$lib/stores.svelte';
import { groupByStamps, ndk, sortBy } from '$lib/stores.svelte';
import Icon from '@iconify/svelte';
import NostrIdentifier from './NostrIdentifier.svelte';
import { getReadableDate, getReadableTime, moveMessageToFolder } from '$lib/utils.svelte.js';
import { getReadableDate, getReadableTime } from '$lib/utils.svelte.js';
import Dialog from './Dialog.svelte';
import Select from './Select.svelte';
import type { Letter } from '$lib/letter';
import { slide } from 'svelte/transition';
import { FolderLabel } from '$lib/folderLabel';
let {
letters,
@ -95,14 +96,10 @@
async function doMove() {
if (!moveFolderName) return alert('Please select a folder');
if (selectedLetters.length === 0) return alert('Please select at least one letter');
const events = [];
for (let letter of selectedLetters)
events.push(await moveMessageToFolder(
letter,
moveFolderName
));
for (let event of events)
await event.publish();
const folderIndexToMoveTo = foldersList.findIndex(f => f.id === moveFolderName);
if (folderIndexToMoveTo === -1) return alert('Please select a valid folder');
foldersList[folderIndexToMoveTo].letters = [...foldersList[folderIndexToMoveTo].letters, ...selectedLetters];
await FolderLabel.save($ndk, foldersList);
selectedLetters = [];
moveFolderName = '';
moveFolderDialogOpen = false;

View file

@ -0,0 +1 @@
export const EVENT_KIND_FOLDERS_LIST = 30008;

View file

@ -1,28 +1,68 @@
import { NDKEvent, type NDKEventId, NDKKind } from '@nostr-dev-kit/ndk';
import NDK, { NDKEvent, type NDKEventId } from '@nostr-dev-kit/ndk';
import { EVENT_KIND_FOLDERS_LIST } from '$lib/customEventKinds';
export class FolderLabel {
id: NDKEventId;
name: string;
icon: string;
letters: NDKEventId[];
constructor() {
this.id = '';
this.name = '';
this.icon = '';
this.letters = [];
}
static async fromDecryptedMessage(
decryptedMessage: NDKEvent,
encryptedMessage: NDKEvent
): Promise<FolderLabel> {
if (decryptedMessage.kind !== NDKKind.Label) throw new Error('Not a label');
let labelType = decryptedMessage.tags.find((t) => t[0] === 'label-type')?.[1];
if (!labelType) throw new Error('No label type');
if (labelType !== 'folder') throw new Error('Not a folder');
let label = new FolderLabel();
label.id = encryptedMessage.id;
label.name = decryptedMessage.tags.find((t) => t[0] === 'name')?.[1] ?? 'No name';
label.icon = decryptedMessage.tags.find((t) => t[0] === 'icon')?.[1] ?? '';
return label;
static fromJSON(json: {
id: NDKEventId;
name: string;
icon: string;
letters?: NDKEventId[];
}): FolderLabel {
let folder = new FolderLabel();
folder.id = json.id;
folder.name = json.name;
folder.icon = json.icon;
if (json.letters) folder.letters = json.letters;
else folder.letters = [];
return folder;
}
static async getAll(ndk: NDK): Promise<FolderLabel[]> {
let newFolders = await ndk.fetchEvent({
kinds: [EVENT_KIND_FOLDERS_LIST],
'#p': [ndk.activeUser!.pubkey],
'#d': ['letter-folders']
});
if (!newFolders) {
await FolderLabel.save(ndk, []);
return [];
}
let decrypted = await ndk.signer!.nip44Decrypt(ndk.activeUser!, newFolders.content);
return JSON.parse(decrypted).map((x) => FolderLabel.fromJSON(x));
}
static async save(ndk: NDK, folders: FolderLabel[]) {
let newFolders = JSON.stringify(folders.map((x) => x.toJson()));
let newFoldersEncrypted = await ndk.signer!.nip44Encrypt(ndk.activeUser!, newFolders);
let event = new NDKEvent(ndk);
event.kind = EVENT_KIND_FOLDERS_LIST;
event.content = newFoldersEncrypted;
event.created_at = Math.floor(Date.now() / 1000);
event.tags = [['d', 'letter-folders'], ['p', ndk.activeUser!.pubkey], ['encrypted']];
await event.sign(ndk.signer!);
await event.publish();
}
toJson() {
return {
id: this.id,
name: this.name,
icon: this.icon,
letters: this.letters
};
}
}

View file

@ -1,25 +0,0 @@
import { NDKEvent, type NDKEventId, NDKKind } from '@nostr-dev-kit/ndk';
export class LetterToFolderMapping {
message: NDKEventId;
folder: NDKEventId;
constructor() {
this.message = '';
this.folder = '';
}
static async fromDecryptedMessage(
decryptedMessage: NDKEvent,
encryptedMessage: NDKEvent
): Promise<LetterToFolderMapping> {
if (decryptedMessage.kind !== NDKKind.Label) throw new Error('Not a label');
let labelType = decryptedMessage.tags.find((t) => t[0] === 'label-type')?.[1];
if (!labelType) throw new Error('No label type');
if (labelType !== 'letter-to-folder-mapping') throw new Error('Not a letter-to-folder-mapping');
let mapping = new LetterToFolderMapping();
mapping.message = decryptedMessage.tags.find((t) => t[0] === 'message')?.[1] ?? '';
mapping.folder = decryptedMessage.tags.find((t) => t[0] === 'folder')?.[1] ?? '';
return mapping;
}
}

View file

@ -13,7 +13,6 @@ import {
import { generateSecretKey } from 'nostr-tools';
import { Letter } from '$lib/letter';
import { FolderLabel } from '$lib/folderLabel';
import { LetterToFolderMapping } from '$lib/letterToFolderMapping';
let ndk: NDK;
let dateFormat: string;
@ -47,45 +46,15 @@ export async function decryptSealedMessage(message: NDKEvent): Promise<NDKEvent>
export async function decryptSealedMessageIntoReadableType(
encryptedMessage: NDKEvent
): Promise<Letter | FolderLabel | LetterToFolderMapping | undefined> {
): Promise<Letter | undefined> {
await waitForNDK();
let rawDecrypted = await decryptSealedMessage(encryptedMessage);
switch (rawDecrypted.kind) {
case NDKKind.Article:
return getLetterFromDecryptedMessage(rawDecrypted, encryptedMessage);
case NDKKind.Label:
let labelType = rawDecrypted.tags.find((t) => t[0] === 'label-type')?.[1];
if (labelType === 'folder')
return getLabelFromDecryptedMessage(rawDecrypted, encryptedMessage);
if (labelType === 'letter-to-folder-mapping')
return getLetterToFolderMappingFromDecryptedMessage(rawDecrypted, encryptedMessage);
}
}
async function getLabelFromDecryptedMessage(
msg: NDKEvent,
encryptedMessage: NDKEvent
): Promise<FolderLabel | undefined> {
await waitForNDK();
if (msg.kind != NDKKind.Label) return;
let labelType = msg.tags.find((t) => t[0] === 'label-type')?.[1];
if (!labelType) return;
if (labelType !== 'folder') return;
return FolderLabel.fromDecryptedMessage(msg, encryptedMessage);
}
async function getLetterToFolderMappingFromDecryptedMessage(
msg: NDKEvent,
encryptedMessage: NDKEvent
): Promise<LetterToFolderMapping | undefined> {
await waitForNDK();
if (msg.kind != NDKKind.Label) return;
let labelType = msg.tags.find((t) => t[0] === 'label-type')?.[1];
if (!labelType) return;
if (labelType !== 'letter-to-folder-mapping') return;
return LetterToFolderMapping.fromDecryptedMessage(msg, encryptedMessage);
}
export async function moveMessageToFolder(id: NDKEventId, folder: NDKEventId | string) {
if (folder === 'sent') throw new Error('Cannot move message to sent folder');
await waitForNDK();
@ -103,16 +72,15 @@ export async function moveMessageToFolder(id: NDKEventId, folder: NDKEventId | s
export async function createFolder(name: string, icon: string) {
await waitForNDK();
const user = await ndk.signer!.user();
const rawMessage = new NDKEvent();
rawMessage.author = user;
rawMessage.created_at = Math.ceil(Date.now() / 1000);
rawMessage.kind = NDKKind.Label;
rawMessage.content = '';
rawMessage.tags.push(['label-type', 'folder']);
rawMessage.tags.push(['name', name]);
rawMessage.tags.push(['icon', icon]);
return encryptEventForRecipient(rawMessage, user);
const allFolders = await FolderLabel.getAll(ndk);
const newFolder = {
id: crypto.randomUUID(),
name: name,
icon: icon
};
allFolders.push(newFolder);
await FolderLabel.save(ndk, allFolders);
return newFolder;
}
export async function createSealedLetter(

View file

@ -4,12 +4,11 @@
import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
import { decryptSealedMessageIntoReadableType } from '$lib/utils.svelte';
import { Letter } from '$lib/letter';
import { FolderLabel } from '$lib/folderLabel';
import type { NDKEvent, NDKEventId } from '@nostr-dev-kit/ndk';
import { LetterToFolderMapping } from '$lib/letterToFolderMapping';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import IconButton from '../components/IconButton.svelte';
import FoldersListSidebar from '../components/FoldersListSidebar.svelte';
import { onMount } from 'svelte';
import { FolderLabel } from '$lib/folderLabel';
let sidebarOpen = $state(false);
@ -25,53 +24,39 @@
let letters = $state<Letter[]>([]);
let activeFolder = $state('inbox');
let folders = $state([
{ id: 'inbox', name: 'Inbox', icon: 'solar:inbox-bold-duotone' },
{ id: 'sent', name: 'Sent', icon: 'fa:send' },
{ id: 'trash', name: 'Trash', icon: 'eos-icons:trash' }
let folders = $state<FolderLabel[]>([
FolderLabel.fromJSON({ id: 'inbox', name: 'Inbox', icon: 'solar:inbox-bold-duotone', letters: [] }),
FolderLabel.fromJSON({ id: 'sent', name: 'Sent', icon: 'fa:send', letters: [] }),
FolderLabel.fromJSON({ id: 'trash', name: 'Trash', icon: 'eos-icons:trash', letters: [] })
]);
async function loadFolders() {
let newFolders = await FolderLabel.getAll($ndk);
for (let folder of newFolders)
if (!folders.find(f => f.id === folder.id)) {
folders = [...folders, folder];
} else if (folder.id == 'trash')
folders.find(f => f.id === folder.id)!.letters = folder.letters;
}
async function decryptMessages(messages: NDKEvent[]) {
const currentMessages = messages;
const decryptedFolders = [
{ id: 'inbox', name: 'Inbox', icon: 'solar:inbox-bold-duotone' },
{ id: 'sent', name: 'Sent', icon: 'fa:send' },
{ id: 'trash', name: 'Trash', icon: 'formkit:trash' }
];
const allLetters = [];
let allLettersInFolders: NDKEventId[] = [];
const letterLabels = new Map<string, NDKEventId[]>();
for (const message of currentMessages) {
const msg = await decryptSealedMessageIntoReadableType(message);
if ((msg instanceof Letter)) {
allLetters.push(msg);
} else if (msg instanceof FolderLabel) {
decryptedFolders.push(msg);
} else if (msg instanceof LetterToFolderMapping) {
if (!letterLabels.has(msg.folder))
letterLabels.set(msg.folder, []);
for (const folderId of letterLabels.keys()) {
// check if letter is in a folder, if so remove it and move it to the new folder
if (letterLabels.get(folderId)!.includes(msg.message)) {
letterLabels.get(folderId)!.splice(letterLabels.get(folderId)!.indexOf(msg.message), 1);
break;
}
}
allLettersInFolders.push(msg.message);
letterLabels.set(msg.folder, letterLabels.get(msg.folder)!.concat(msg.message));
}
if (!(msg instanceof Letter)) continue;
allLetters.push(msg);
}
letters = allLetters.filter(letter => {
if (activeFolder === 'sent')
return letter.from.pubkey === $ndk.activeUser.pubkey;
if (activeFolder === 'inbox' && letter.from.pubkey !== $ndk.activeUser.pubkey)
return !allLettersInFolders.includes(letter.id);
if (!letterLabels.has(activeFolder))
return false;
return letterLabels.get(activeFolder)!.includes(letter.id);
return !folders.some(x => x.letters.includes(letter.id));
const folder = folders.find(f => f.letters.includes(letter.id));
if (!folder) return false;
return folder.id === activeFolder;
});
folders = decryptedFolders;
}
messages.subscribe((m: NDKEvent) => decryptMessages(m));
@ -86,9 +71,10 @@
let moveFolderDialogOpen = $state(false);
onMount(() => {
onMount(async () => {
$pageTitle = 'Inbox';
$pageIcon = 'solar:inbox-bold-duotone';
await loadFolders();
});
</script>