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:
parent
f70f4c4518
commit
1b2177933c
7 changed files with 95 additions and 129 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
1
src/lib/customEventKinds.ts
Normal file
1
src/lib/customEventKinds.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const EVENT_KIND_FOLDERS_LIST = 30008;
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue