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() {
|
async function addNewFolderPressed() {
|
||||||
const newFolder = await createFolder(newFolderName, '');
|
const newFolder = await createFolder(newFolderName, '');
|
||||||
if (!newFolder) return;
|
if (!newFolder) return;
|
||||||
await newFolder.publish();
|
|
||||||
folders = [...folders, { id: newFolder.id, name: newFolderName, icon: 'eos-icons:plus' }];
|
folders = [...folders, { id: newFolder.id, name: newFolderName, icon: 'eos-icons:plus' }];
|
||||||
newFolderName = '';
|
newFolderName = '';
|
||||||
isAddingFolder = false;
|
isAddingFolder = false;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { groupByStamps, sortBy } from '$lib/stores.svelte';
|
import { groupByStamps, ndk, sortBy } from '$lib/stores.svelte';
|
||||||
import Icon from '@iconify/svelte';
|
import Icon from '@iconify/svelte';
|
||||||
import NostrIdentifier from './NostrIdentifier.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 Dialog from './Dialog.svelte';
|
||||||
import Select from './Select.svelte';
|
import Select from './Select.svelte';
|
||||||
import type { Letter } from '$lib/letter';
|
import type { Letter } from '$lib/letter';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
import { FolderLabel } from '$lib/folderLabel';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
letters,
|
letters,
|
||||||
|
@ -95,14 +96,10 @@
|
||||||
async function doMove() {
|
async function doMove() {
|
||||||
if (!moveFolderName) return alert('Please select a folder');
|
if (!moveFolderName) return alert('Please select a folder');
|
||||||
if (selectedLetters.length === 0) return alert('Please select at least one letter');
|
if (selectedLetters.length === 0) return alert('Please select at least one letter');
|
||||||
const events = [];
|
const folderIndexToMoveTo = foldersList.findIndex(f => f.id === moveFolderName);
|
||||||
for (let letter of selectedLetters)
|
if (folderIndexToMoveTo === -1) return alert('Please select a valid folder');
|
||||||
events.push(await moveMessageToFolder(
|
foldersList[folderIndexToMoveTo].letters = [...foldersList[folderIndexToMoveTo].letters, ...selectedLetters];
|
||||||
letter,
|
await FolderLabel.save($ndk, foldersList);
|
||||||
moveFolderName
|
|
||||||
));
|
|
||||||
for (let event of events)
|
|
||||||
await event.publish();
|
|
||||||
selectedLetters = [];
|
selectedLetters = [];
|
||||||
moveFolderName = '';
|
moveFolderName = '';
|
||||||
moveFolderDialogOpen = false;
|
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 {
|
export class FolderLabel {
|
||||||
id: NDKEventId;
|
id: NDKEventId;
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
letters: NDKEventId[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.id = '';
|
this.id = '';
|
||||||
this.name = '';
|
this.name = '';
|
||||||
this.icon = '';
|
this.icon = '';
|
||||||
|
this.letters = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromDecryptedMessage(
|
static fromJSON(json: {
|
||||||
decryptedMessage: NDKEvent,
|
id: NDKEventId;
|
||||||
encryptedMessage: NDKEvent
|
name: string;
|
||||||
): Promise<FolderLabel> {
|
icon: string;
|
||||||
if (decryptedMessage.kind !== NDKKind.Label) throw new Error('Not a label');
|
letters?: NDKEventId[];
|
||||||
let labelType = decryptedMessage.tags.find((t) => t[0] === 'label-type')?.[1];
|
}): FolderLabel {
|
||||||
if (!labelType) throw new Error('No label type');
|
let folder = new FolderLabel();
|
||||||
if (labelType !== 'folder') throw new Error('Not a folder');
|
folder.id = json.id;
|
||||||
let label = new FolderLabel();
|
folder.name = json.name;
|
||||||
label.id = encryptedMessage.id;
|
folder.icon = json.icon;
|
||||||
label.name = decryptedMessage.tags.find((t) => t[0] === 'name')?.[1] ?? 'No name';
|
if (json.letters) folder.letters = json.letters;
|
||||||
label.icon = decryptedMessage.tags.find((t) => t[0] === 'icon')?.[1] ?? '';
|
else folder.letters = [];
|
||||||
return label;
|
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 { generateSecretKey } from 'nostr-tools';
|
||||||
import { Letter } from '$lib/letter';
|
import { Letter } from '$lib/letter';
|
||||||
import { FolderLabel } from '$lib/folderLabel';
|
import { FolderLabel } from '$lib/folderLabel';
|
||||||
import { LetterToFolderMapping } from '$lib/letterToFolderMapping';
|
|
||||||
|
|
||||||
let ndk: NDK;
|
let ndk: NDK;
|
||||||
let dateFormat: string;
|
let dateFormat: string;
|
||||||
|
@ -47,45 +46,15 @@ export async function decryptSealedMessage(message: NDKEvent): Promise<NDKEvent>
|
||||||
|
|
||||||
export async function decryptSealedMessageIntoReadableType(
|
export async function decryptSealedMessageIntoReadableType(
|
||||||
encryptedMessage: NDKEvent
|
encryptedMessage: NDKEvent
|
||||||
): Promise<Letter | FolderLabel | LetterToFolderMapping | undefined> {
|
): Promise<Letter | undefined> {
|
||||||
await waitForNDK();
|
await waitForNDK();
|
||||||
let rawDecrypted = await decryptSealedMessage(encryptedMessage);
|
let rawDecrypted = await decryptSealedMessage(encryptedMessage);
|
||||||
switch (rawDecrypted.kind) {
|
switch (rawDecrypted.kind) {
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return getLetterFromDecryptedMessage(rawDecrypted, encryptedMessage);
|
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) {
|
export async function moveMessageToFolder(id: NDKEventId, folder: NDKEventId | string) {
|
||||||
if (folder === 'sent') throw new Error('Cannot move message to sent folder');
|
if (folder === 'sent') throw new Error('Cannot move message to sent folder');
|
||||||
await waitForNDK();
|
await waitForNDK();
|
||||||
|
@ -103,16 +72,15 @@ export async function moveMessageToFolder(id: NDKEventId, folder: NDKEventId | s
|
||||||
|
|
||||||
export async function createFolder(name: string, icon: string) {
|
export async function createFolder(name: string, icon: string) {
|
||||||
await waitForNDK();
|
await waitForNDK();
|
||||||
const user = await ndk.signer!.user();
|
const allFolders = await FolderLabel.getAll(ndk);
|
||||||
const rawMessage = new NDKEvent();
|
const newFolder = {
|
||||||
rawMessage.author = user;
|
id: crypto.randomUUID(),
|
||||||
rawMessage.created_at = Math.ceil(Date.now() / 1000);
|
name: name,
|
||||||
rawMessage.kind = NDKKind.Label;
|
icon: icon
|
||||||
rawMessage.content = '';
|
};
|
||||||
rawMessage.tags.push(['label-type', 'folder']);
|
allFolders.push(newFolder);
|
||||||
rawMessage.tags.push(['name', name]);
|
await FolderLabel.save(ndk, allFolders);
|
||||||
rawMessage.tags.push(['icon', icon]);
|
return newFolder;
|
||||||
return encryptEventForRecipient(rawMessage, user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSealedLetter(
|
export async function createSealedLetter(
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
|
import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
|
||||||
import { decryptSealedMessageIntoReadableType } from '$lib/utils.svelte';
|
import { decryptSealedMessageIntoReadableType } from '$lib/utils.svelte';
|
||||||
import { Letter } from '$lib/letter';
|
import { Letter } from '$lib/letter';
|
||||||
import { FolderLabel } from '$lib/folderLabel';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import type { NDKEvent, NDKEventId } from '@nostr-dev-kit/ndk';
|
|
||||||
import { LetterToFolderMapping } from '$lib/letterToFolderMapping';
|
|
||||||
import IconButton from '../components/IconButton.svelte';
|
import IconButton from '../components/IconButton.svelte';
|
||||||
import FoldersListSidebar from '../components/FoldersListSidebar.svelte';
|
import FoldersListSidebar from '../components/FoldersListSidebar.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { FolderLabel } from '$lib/folderLabel';
|
||||||
|
|
||||||
let sidebarOpen = $state(false);
|
let sidebarOpen = $state(false);
|
||||||
|
|
||||||
|
@ -25,53 +24,39 @@
|
||||||
let letters = $state<Letter[]>([]);
|
let letters = $state<Letter[]>([]);
|
||||||
let activeFolder = $state('inbox');
|
let activeFolder = $state('inbox');
|
||||||
|
|
||||||
let folders = $state([
|
let folders = $state<FolderLabel[]>([
|
||||||
{ id: 'inbox', name: 'Inbox', icon: 'solar:inbox-bold-duotone' },
|
FolderLabel.fromJSON({ id: 'inbox', name: 'Inbox', icon: 'solar:inbox-bold-duotone', letters: [] }),
|
||||||
{ id: 'sent', name: 'Sent', icon: 'fa:send' },
|
FolderLabel.fromJSON({ id: 'sent', name: 'Sent', icon: 'fa:send', letters: [] }),
|
||||||
{ id: 'trash', name: 'Trash', icon: 'eos-icons:trash' }
|
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[]) {
|
async function decryptMessages(messages: NDKEvent[]) {
|
||||||
const currentMessages = messages;
|
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 = [];
|
const allLetters = [];
|
||||||
let allLettersInFolders: NDKEventId[] = [];
|
|
||||||
const letterLabels = new Map<string, NDKEventId[]>();
|
|
||||||
for (const message of currentMessages) {
|
for (const message of currentMessages) {
|
||||||
const msg = await decryptSealedMessageIntoReadableType(message);
|
const msg = await decryptSealedMessageIntoReadableType(message);
|
||||||
if ((msg instanceof Letter)) {
|
if (!(msg instanceof Letter)) continue;
|
||||||
allLetters.push(msg);
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
letters = allLetters.filter(letter => {
|
letters = allLetters.filter(letter => {
|
||||||
if (activeFolder === 'sent')
|
if (activeFolder === 'sent')
|
||||||
return letter.from.pubkey === $ndk.activeUser.pubkey;
|
return letter.from.pubkey === $ndk.activeUser.pubkey;
|
||||||
if (activeFolder === 'inbox' && letter.from.pubkey !== $ndk.activeUser.pubkey)
|
if (activeFolder === 'inbox' && letter.from.pubkey !== $ndk.activeUser.pubkey)
|
||||||
return !allLettersInFolders.includes(letter.id);
|
return !folders.some(x => x.letters.includes(letter.id));
|
||||||
if (!letterLabels.has(activeFolder))
|
const folder = folders.find(f => f.letters.includes(letter.id));
|
||||||
return false;
|
if (!folder) return false;
|
||||||
return letterLabels.get(activeFolder)!.includes(letter.id);
|
return folder.id === activeFolder;
|
||||||
});
|
});
|
||||||
folders = decryptedFolders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.subscribe((m: NDKEvent) => decryptMessages(m));
|
messages.subscribe((m: NDKEvent) => decryptMessages(m));
|
||||||
|
@ -86,9 +71,10 @@
|
||||||
|
|
||||||
let moveFolderDialogOpen = $state(false);
|
let moveFolderDialogOpen = $state(false);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
$pageTitle = 'Inbox';
|
$pageTitle = 'Inbox';
|
||||||
$pageIcon = 'solar:inbox-bold-duotone';
|
$pageIcon = 'solar:inbox-bold-duotone';
|
||||||
|
await loadFolders();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue