diff --git a/bun.lockb b/bun.lockb index 85489cfc..3dd5d05b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/dist/index.js b/dist/index.js index 02856f05..0e671b1f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -118,7 +118,48 @@ } // stringification toString() { - const array = [...this.value]; + const array = [...this.value.values()]; + const json = JSON.stringify(array); + return json; + } + }; + var MapState = class extends State { + additionHandlers = /* @__PURE__ */ new Set(); + removalHandlers = /* @__PURE__ */ new Map(); + // init + constructor(initialItems) { + super(new Map(initialItems)); + } + // list + set(key, item) { + this.value.set(key, item); + this.additionHandlers.forEach((handler) => handler(item)); + this.callSubscriptions(); + } + remove(key) { + const item = this.value.get(key); + if (!item) return; + this.value.delete(key); + this.callSubscriptions(); + if (!this.removalHandlers.has(item)) return; + this.removalHandlers.get(item)(item); + this.removalHandlers.delete(item); + } + clear() { + this.value.clear(); + this.callSubscriptions(); + } + // handlers + handleAddition(handler) { + this.additionHandlers.add(handler); + [...this.value.values()].forEach(handler); + } + handleRemoval(item, handler) { + this.removalHandlers.set(item, handler); + } + // stringification + toString() { + const array = [...this.value.entries()]; const json = JSON.stringify(array); return json; } @@ -143,9 +184,8 @@ persistState(localStorageKey, state); return state; } - function restoreListState(localStorageKey) { + function restoreListState(localStorageKey, initialItems = []) { const storedString = localStorage.getItem(localStorageKey) ?? ""; - let initialItems = []; try { const array = JSON.parse(storedString); if (!Array.isArray(array)) throw ""; @@ -156,6 +196,18 @@ persistState(localStorageKey, state); return state; } + function restoreMapState(localStorageKey, initialItems = []) { + const storedString = localStorage.getItem(localStorageKey) ?? ""; + try { + const array = JSON.parse(storedString); + if (!Array.isArray(array)) throw ""; + initialItems = array; + } catch { + } + const state = new MapState(initialItems); + persistState(localStorageKey, state); + return state; + } function createElement(tagName, attributes = {}, ...children) { const element = document.createElement(tagName); if (attributes != null) @@ -346,6 +398,9 @@ messages(id) { return id + "messages"; }, + items(id) { + return id + "items"; + }, outbox(id) { return id + "outbox"; }, @@ -363,6 +418,7 @@ secondaryChannels; encryptionKey; messages; + items; outbox; isOutBoxEmpty; composingMessage; @@ -390,6 +446,7 @@ ); this.encryptionKey = restoreState(storageKeys.encyptionKey(id), ""); this.messages = restoreListState(storageKeys.messages(id)); + this.items = restoreMapState(storageKeys.items(id)); this.outbox = restoreListState(storageKeys.outbox(id)); this.isOutBoxEmpty = createProxyState( [this.outbox], @@ -441,7 +498,12 @@ if (channels.indexOf(this.primaryChannel.value) == -1) return; if (data.subscribed != void 0) this.handleSubscription(data.subscribed); if (!data.messageBody) return; - const { sender, body, channel, isoDate } = JSON.parse(data.messageBody); + const { sender, body, channel, isoDate, itemData } = JSON.parse( + data.messageBody + ); + if (itemData != void 0) { + return this.handleItem(itemData); + } this.handleMessage({ sender, body: await decryptString(body, this.encryptionKey.value), @@ -459,21 +521,11 @@ this.messages.add(chatMessage); if (selectedChat.value != this) this.hasUnreadMessages.value = true; }; + handleItem = (itemData) => { + console.log(itemData); + }; // messages - sendNewMessage = async () => { - if (this.cannotSendMessage.value == true) return; - await this.sendMessageText(this.composingMessage.value); - this.composingMessage.value = ""; - }; - sendMessageText = async (text) => { - const message = await this.createMessage(text); - this.sendExistingMessage(message); - }; - resendMessage = (message) => { - if (this.cannotResendMessage.value == true) return; - this.sendMessageText(message.body); - }; - createMessage = async (messageText) => { + createMessage = async (messageText, itemData) => { const secondaryChannelNames = [ ...this.secondaryChannels.value.values() ]; @@ -482,14 +534,29 @@ ...secondaryChannelNames ]; const joinedChannelName = allChannelNames.join("/"); - return { + const messageObject = { channel: joinedChannelName, sender: senderName.value, body: messageText, isoDate: (/* @__PURE__ */ new Date()).toISOString() }; + if (itemData) messageObject.itemData = itemData; + return messageObject; }; - sendExistingMessage = async (chatMessage) => { + sendMessageFromComposer = async () => { + if (this.cannotSendMessage.value == true) return; + await this.sendMessageFromText(this.composingMessage.value); + this.composingMessage.value = ""; + }; + sendMessageFromText = async (text) => { + const message = await this.createMessage(text); + this.sendMessage(message); + }; + resendMessage = (message) => { + if (this.cannotResendMessage.value == true) return; + this.sendMessageFromText(message.body); + }; + sendMessage = async (chatMessage) => { if (isConnected.value == true && this.isSubscribed.value == true) { const encrypted = this.encryptionKey.value == "" ? chatMessage.body : await encryptString(chatMessage.body, this.encryptionKey.value); chatMessage.body = encrypted; @@ -501,7 +568,7 @@ }; sendMessagesInOutbox = () => { this.outbox.value.forEach((message) => { - this.sendExistingMessage(message); + this.sendMessage(message); this.outbox.remove(message); }); }; @@ -518,6 +585,12 @@ message.body = await decryptString(message.body, this.encryptionKey.value); this.messages.callSubscriptions(); }; + // items + async createItem(item) { + this.items.set(item.id, item); + const message = await this.createMessage("", item); + this.sendMessage(message); + } // channel setChannel = () => { if (this.cannotSetChannel.value == true) return; @@ -1031,7 +1104,13 @@ // src/Views/chatToolView.tsx function ChatToolView(chat) { - return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("span", null, "Tools")); + function createItem() { + chat.createItem({ + id: UUID(), + title: "new item" + }); + } + return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("button", { "on:click": createItem }, "+"), /* @__PURE__ */ createElement("hr", null)); } // src/Views/messageComposer.tsx @@ -1043,13 +1122,13 @@ style: "max-width: unset", placeholder: translation.composerPlaceholder, "bind:value": chat.composingMessage, - "on:enter": chat.sendNewMessage + "on:enter": chat.sendMessageFromComposer } ), /* @__PURE__ */ createElement( "button", { class: "primary", - "on:click": chat.sendNewMessage, + "on:click": chat.sendMessageFromComposer, "toggle:disabled": chat.cannotSendMessage }, /* @__PURE__ */ createElement("span", { class: "icon" }, "send") diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index ecc5d2d1..1d45b477 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -21,6 +21,18 @@ export interface ChatMessage { sender: string; body: string; isoDate: string; + + itemData?: Item; +} + +// item +export interface Item { + id: string; + title: string; +} + +export interface NoteItem extends Item { + body: string; } // chat @@ -34,6 +46,7 @@ export class Chat { encryptionKey: React.State; messages: React.ListState; + items: React.MapState; outbox: React.ListState; isOutBoxEmpty: React.State; @@ -68,6 +81,7 @@ export class Chat { ); this.encryptionKey = React.restoreState(storageKeys.encyptionKey(id), ""); this.messages = React.restoreListState(storageKeys.messages(id)); + this.items = React.restoreMapState(storageKeys.items(id)); this.outbox = React.restoreListState(storageKeys.outbox(id)); this.isOutBoxEmpty = React.createProxyState( [this.outbox], @@ -133,7 +147,14 @@ export class Chat { if (data.subscribed != undefined) this.handleSubscription(data.subscribed); if (!data.messageBody) return; - const { sender, body, channel, isoDate } = JSON.parse(data.messageBody); + const { sender, body, channel, isoDate, itemData } = JSON.parse( + data.messageBody + ); + + if (itemData != undefined) { + return this.handleItem(itemData); + } + this.handleMessage({ sender, body: await decryptString(body, this.encryptionKey.value), @@ -155,25 +176,15 @@ export class Chat { if (selectedChat.value != this) this.hasUnreadMessages.value = true; }; + handleItem = (itemData: Item): void => { + console.log(itemData); + }; + // messages - sendNewMessage = async (): Promise => { - if (this.cannotSendMessage.value == true) return; - - await this.sendMessageText(this.composingMessage.value); - this.composingMessage.value = ""; - }; - - sendMessageText = async (text: string): Promise => { - const message = await this.createMessage(text); - this.sendExistingMessage(message); - }; - - resendMessage = (message: ChatMessage) => { - if (this.cannotResendMessage.value == true) return; - this.sendMessageText(message.body); - }; - - createMessage = async (messageText: string): Promise => { + createMessage = async ( + messageText: string, + itemData?: Item + ): Promise => { // get channels const secondaryChannelNames: string[] = [ ...this.secondaryChannels.value.values(), @@ -185,15 +196,35 @@ export class Chat { // create object const joinedChannelName = allChannelNames.join("/"); - return { + const messageObject: ChatMessage = { channel: joinedChannelName, sender: senderName.value, body: messageText, isoDate: new Date().toISOString(), }; + + if (itemData) messageObject.itemData = itemData; + return messageObject; }; - sendExistingMessage = async (chatMessage: ChatMessage): Promise => { + sendMessageFromComposer = async (): Promise => { + if (this.cannotSendMessage.value == true) return; + + await this.sendMessageFromText(this.composingMessage.value); + this.composingMessage.value = ""; + }; + + sendMessageFromText = async (text: string): Promise => { + const message = await this.createMessage(text); + this.sendMessage(message); + }; + + resendMessage = (message: ChatMessage) => { + if (this.cannotResendMessage.value == true) return; + this.sendMessageFromText(message.body); + }; + + sendMessage = async (chatMessage: ChatMessage): Promise => { if (isConnected.value == true && this.isSubscribed.value == true) { const encrypted = this.encryptionKey.value == "" @@ -211,7 +242,7 @@ export class Chat { sendMessagesInOutbox = () => { this.outbox.value.forEach((message) => { - this.sendExistingMessage(message); + this.sendMessage(message); this.outbox.remove(message); }); }; @@ -233,6 +264,14 @@ export class Chat { this.messages.callSubscriptions(); }; + // items + async createItem(item: Item): Promise { + this.items.set(item.id, item); + + const message = await this.createMessage("", item); + this.sendMessage(message); + } + // channel setChannel = (): void => { if (this.cannotSetChannel.value == true) return; diff --git a/src/Views/chatToolView.tsx b/src/Views/chatToolView.tsx index ca32cfaf..1576c065 100644 --- a/src/Views/chatToolView.tsx +++ b/src/Views/chatToolView.tsx @@ -4,9 +4,18 @@ import { Chat } from "../Model/chatModel"; import { translation } from "../translations"; export function ChatToolView(chat: Chat) { + function createItem() { + chat.createItem({ + id: React.UUID(), + title: "new item", + }); + } + return (
- Tools + +
+
); } diff --git a/src/Views/messageComposer.tsx b/src/Views/messageComposer.tsx index 66a87fed..8d2e281c 100644 --- a/src/Views/messageComposer.tsx +++ b/src/Views/messageComposer.tsx @@ -12,11 +12,11 @@ export function MessageComposer(chat: Chat) { style="max-width: unset" placeholder={translation.composerPlaceholder} bind:value={chat.composingMessage} - on:enter={chat.sendNewMessage} + on:enter={chat.sendMessageFromComposer} >