Vendored
+63
@@ -2,3 +2,66 @@
|
|||||||
[unread] {
|
[unread] {
|
||||||
border-left: 10px solid var(--text-warn);
|
border-left: 10px solid var(--text-warn);
|
||||||
}
|
}
|
||||||
|
:root {
|
||||||
|
--thread-view-height-with-tools: 8rem;
|
||||||
|
--thread-view-width-with-tools: 20rem;
|
||||||
|
--footer-top-line: calc(var(--control-height) + var(--safe-area-bottom));
|
||||||
|
}
|
||||||
|
#message-tab {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.message-tab-content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.chat-object-view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
[showingchattools] :is(.thread-view, .chat-object-view) {
|
||||||
|
overflow: scroll;
|
||||||
|
position: absolute;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
[showingchattools] header {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
[showingchattools] .chat-object-view {
|
||||||
|
display: flex;
|
||||||
|
top: var(--header-height);
|
||||||
|
bottom: calc(var(--thread-view-height-with-tools) + var(--footer-top-line));
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
[showingchattools] .thread-view {
|
||||||
|
display: flex;
|
||||||
|
height: var(--thread-view-height-with-tools);
|
||||||
|
top: unset;
|
||||||
|
bottom: var(--footer-top-line);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) and (max-width: 1400px) {
|
||||||
|
main.split:has([showingchattools]) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
main.split:has([showingchattools]) #settings-tab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
[showingchattools] :is(.thread-view, .chat-object-view) {
|
||||||
|
top: var(--header-height);
|
||||||
|
bottom: var(--footer-top-line);
|
||||||
|
}
|
||||||
|
[showingchattools] .chat-object-view {
|
||||||
|
left: 0px;
|
||||||
|
right: var(--thread-view-width-with-tools);
|
||||||
|
border-bottom: none;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
[showingchattools] .thread-view {
|
||||||
|
display: flex;
|
||||||
|
left: unset;
|
||||||
|
right: 0px;
|
||||||
|
height: unset;
|
||||||
|
width: var(--thread-view-width-with-tools);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+528
-77
@@ -76,6 +76,9 @@
|
|||||||
this._bindings.add(fn);
|
this._bindings.add(fn);
|
||||||
fn(this._value);
|
fn(this._value);
|
||||||
}
|
}
|
||||||
|
subscribeSilent(fn) {
|
||||||
|
this._bindings.add(fn);
|
||||||
|
}
|
||||||
// stringify
|
// stringify
|
||||||
toString() {
|
toString() {
|
||||||
return JSON.stringify(this._value);
|
return JSON.stringify(this._value);
|
||||||
@@ -118,7 +121,48 @@
|
|||||||
}
|
}
|
||||||
// stringification
|
// stringification
|
||||||
toString() {
|
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.remove(key);
|
||||||
|
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.keys()].forEach((key) => this.remove(key));
|
||||||
|
}
|
||||||
|
// 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);
|
const json = JSON.stringify(array);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -130,6 +174,9 @@
|
|||||||
);
|
);
|
||||||
return proxyState;
|
return proxyState;
|
||||||
}
|
}
|
||||||
|
function bulkSubscribe(statesToSubscibe, fn) {
|
||||||
|
statesToSubscibe.forEach((state) => state.subscribeSilent(fn));
|
||||||
|
}
|
||||||
function persistState(localStorageKey, state) {
|
function persistState(localStorageKey, state) {
|
||||||
state.subscribe(() => {
|
state.subscribe(() => {
|
||||||
const stringifiedValue = state.toString();
|
const stringifiedValue = state.toString();
|
||||||
@@ -143,9 +190,8 @@
|
|||||||
persistState(localStorageKey, state);
|
persistState(localStorageKey, state);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
function restoreListState(localStorageKey) {
|
function restoreListState(localStorageKey, initialItems = []) {
|
||||||
const storedString = localStorage.getItem(localStorageKey) ?? "";
|
const storedString = localStorage.getItem(localStorageKey) ?? "";
|
||||||
let initialItems = [];
|
|
||||||
try {
|
try {
|
||||||
const array = JSON.parse(storedString);
|
const array = JSON.parse(storedString);
|
||||||
if (!Array.isArray(array)) throw "";
|
if (!Array.isArray(array)) throw "";
|
||||||
@@ -156,6 +202,18 @@
|
|||||||
persistState(localStorageKey, state);
|
persistState(localStorageKey, state);
|
||||||
return 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) {
|
function createElement(tagName, attributes = {}, ...children) {
|
||||||
const element = document.createElement(tagName);
|
const element = document.createElement(tagName);
|
||||||
if (attributes != null)
|
if (attributes != null)
|
||||||
@@ -243,7 +301,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`;
|
throw `error: cannot process subscribe:children directive because StateItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,9 +404,15 @@
|
|||||||
messages(id) {
|
messages(id) {
|
||||||
return id + "messages";
|
return id + "messages";
|
||||||
},
|
},
|
||||||
|
objects(id) {
|
||||||
|
return id + "items";
|
||||||
|
},
|
||||||
outbox(id) {
|
outbox(id) {
|
||||||
return id + "outbox";
|
return id + "outbox";
|
||||||
},
|
},
|
||||||
|
itemOutbox(id) {
|
||||||
|
return id + "item-outbox";
|
||||||
|
},
|
||||||
composingMessage(id) {
|
composingMessage(id) {
|
||||||
return id + "composing-message";
|
return id + "composing-message";
|
||||||
}
|
}
|
||||||
@@ -363,7 +427,9 @@
|
|||||||
secondaryChannels;
|
secondaryChannels;
|
||||||
encryptionKey;
|
encryptionKey;
|
||||||
messages;
|
messages;
|
||||||
|
objects;
|
||||||
outbox;
|
outbox;
|
||||||
|
objectOutbox;
|
||||||
isOutBoxEmpty;
|
isOutBoxEmpty;
|
||||||
composingMessage;
|
composingMessage;
|
||||||
primaryChannelInput;
|
primaryChannelInput;
|
||||||
@@ -374,6 +440,7 @@
|
|||||||
cannotSetChannel;
|
cannotSetChannel;
|
||||||
cannotUndoChannel;
|
cannotUndoChannel;
|
||||||
cannotClearMessages;
|
cannotClearMessages;
|
||||||
|
cannotClearObjects;
|
||||||
// init
|
// init
|
||||||
constructor(id = UUID()) {
|
constructor(id = UUID()) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -390,7 +457,9 @@
|
|||||||
);
|
);
|
||||||
this.encryptionKey = restoreState(storageKeys.encyptionKey(id), "");
|
this.encryptionKey = restoreState(storageKeys.encyptionKey(id), "");
|
||||||
this.messages = restoreListState(storageKeys.messages(id));
|
this.messages = restoreListState(storageKeys.messages(id));
|
||||||
|
this.objects = restoreMapState(storageKeys.objects(id));
|
||||||
this.outbox = restoreListState(storageKeys.outbox(id));
|
this.outbox = restoreListState(storageKeys.outbox(id));
|
||||||
|
this.objectOutbox = restoreMapState(storageKeys.itemOutbox(id));
|
||||||
this.isOutBoxEmpty = createProxyState(
|
this.isOutBoxEmpty = createProxyState(
|
||||||
[this.outbox],
|
[this.outbox],
|
||||||
() => this.outbox.value.size == 0
|
() => this.outbox.value.size == 0
|
||||||
@@ -425,6 +494,10 @@
|
|||||||
[this.messages],
|
[this.messages],
|
||||||
() => this.messages.value.size == 0
|
() => this.messages.value.size == 0
|
||||||
);
|
);
|
||||||
|
this.cannotClearObjects = createProxyState(
|
||||||
|
[this.objects],
|
||||||
|
() => this.objects.value.size == 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// general
|
// general
|
||||||
deleteSelf = () => {
|
deleteSelf = () => {
|
||||||
@@ -441,7 +514,17 @@
|
|||||||
if (channels.indexOf(this.primaryChannel.value) == -1) return;
|
if (channels.indexOf(this.primaryChannel.value) == -1) return;
|
||||||
if (data.subscribed != void 0) this.handleSubscription(data.subscribed);
|
if (data.subscribed != void 0) this.handleSubscription(data.subscribed);
|
||||||
if (!data.messageBody) return;
|
if (!data.messageBody) return;
|
||||||
const { sender, body, channel, isoDate } = JSON.parse(data.messageBody);
|
const message = JSON.parse(data.messageBody);
|
||||||
|
const { sender, body, channel, isoDate, messageObjectString } = message;
|
||||||
|
if (messageObjectString) {
|
||||||
|
const decryptedMessageObjectString = await decryptString(
|
||||||
|
messageObjectString,
|
||||||
|
this.encryptionKey.value
|
||||||
|
);
|
||||||
|
const messageObject = JSON.parse(decryptedMessageObjectString);
|
||||||
|
if (messageObject.id && messageObject.title)
|
||||||
|
return this.handleMessageObject(messageObject);
|
||||||
|
}
|
||||||
this.handleMessage({
|
this.handleMessage({
|
||||||
sender,
|
sender,
|
||||||
body: await decryptString(body, this.encryptionKey.value),
|
body: await decryptString(body, this.encryptionKey.value),
|
||||||
@@ -459,21 +542,23 @@
|
|||||||
this.messages.add(chatMessage);
|
this.messages.add(chatMessage);
|
||||||
if (selectedChat.value != this) this.hasUnreadMessages.value = true;
|
if (selectedChat.value != this) this.hasUnreadMessages.value = true;
|
||||||
};
|
};
|
||||||
|
handleMessageObject = (messageObject) => {
|
||||||
|
this.addObject(messageObject);
|
||||||
|
};
|
||||||
|
// sending
|
||||||
|
sendMessagesInOutbox = () => {
|
||||||
|
this.outbox.value.forEach(async (message) => {
|
||||||
|
const isSent = await this.sendMessage(message);
|
||||||
|
if (isSent == true) this.outbox.remove(message);
|
||||||
|
});
|
||||||
|
this.objectOutbox.value.forEach(async (messageObject) => {
|
||||||
|
const chatMessage = await this.createChatMessage("", messageObject);
|
||||||
|
const isSent = await this.sendMessage(chatMessage);
|
||||||
|
if (isSent == true) this.objectOutbox.remove(messageObject.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
// messages
|
// messages
|
||||||
sendNewMessage = async () => {
|
createChatMessage = async (messageText, messageObject) => {
|
||||||
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) => {
|
|
||||||
const secondaryChannelNames = [
|
const secondaryChannelNames = [
|
||||||
...this.secondaryChannels.value.values()
|
...this.secondaryChannels.value.values()
|
||||||
];
|
];
|
||||||
@@ -482,42 +567,128 @@
|
|||||||
...secondaryChannelNames
|
...secondaryChannelNames
|
||||||
];
|
];
|
||||||
const joinedChannelName = allChannelNames.join("/");
|
const joinedChannelName = allChannelNames.join("/");
|
||||||
return {
|
const chatMessage = {
|
||||||
channel: joinedChannelName,
|
channel: joinedChannelName,
|
||||||
sender: senderName.value,
|
sender: senderName.value,
|
||||||
body: messageText,
|
body: messageText,
|
||||||
isoDate: (/* @__PURE__ */ new Date()).toISOString()
|
isoDate: (/* @__PURE__ */ new Date()).toISOString()
|
||||||
};
|
};
|
||||||
|
if (messageObject)
|
||||||
|
chatMessage.messageObjectString = await encryptString(
|
||||||
|
JSON.stringify(messageObject),
|
||||||
|
this.encryptionKey.value
|
||||||
|
);
|
||||||
|
return chatMessage;
|
||||||
};
|
};
|
||||||
sendExistingMessage = async (chatMessage) => {
|
sendMessageFromComposer = async () => {
|
||||||
if (isConnected.value == true && this.isSubscribed.value == true) {
|
if (this.cannotSendMessage.value == true) return;
|
||||||
const encrypted = this.encryptionKey.value == "" ? chatMessage.body : await encryptString(chatMessage.body, this.encryptionKey.value);
|
await this.sendMessageFromText(this.composingMessage.value);
|
||||||
chatMessage.body = encrypted;
|
this.composingMessage.value = "";
|
||||||
const messageString = JSON.stringify(chatMessage);
|
|
||||||
UDN.sendMessage(chatMessage.channel, messageString);
|
|
||||||
} else {
|
|
||||||
this.outbox.add(chatMessage);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
sendMessagesInOutbox = () => {
|
sendMessageFromText = async (text) => {
|
||||||
this.outbox.value.forEach((message) => {
|
const chatMessage = await this.createChatMessage(text);
|
||||||
this.sendExistingMessage(message);
|
this.outbox.add(chatMessage);
|
||||||
this.outbox.remove(message);
|
this.sendMessagesInOutbox();
|
||||||
});
|
};
|
||||||
|
resendMessage = (chatMessage) => {
|
||||||
|
if (this.cannotResendMessage.value == true) return;
|
||||||
|
this.sendMessageFromText(chatMessage.body);
|
||||||
};
|
};
|
||||||
clearMessages = () => {
|
clearMessages = () => {
|
||||||
this.messages.clear();
|
this.messages.clear();
|
||||||
};
|
};
|
||||||
deleteMessage = (message) => {
|
deleteMessage = (chatMessage) => {
|
||||||
this.messages.remove(message);
|
this.messages.remove(chatMessage);
|
||||||
};
|
};
|
||||||
deleteOutboxMessage = (message) => {
|
deleteOutboxMessage = (chatMessage) => {
|
||||||
this.outbox.remove(message);
|
this.outbox.remove(chatMessage);
|
||||||
};
|
};
|
||||||
decryptReceivedMessage = async (message) => {
|
decryptReceivedMessage = async (chatMessage) => {
|
||||||
message.body = await decryptString(message.body, this.encryptionKey.value);
|
chatMessage.body = await decryptString(
|
||||||
|
chatMessage.body,
|
||||||
|
this.encryptionKey.value
|
||||||
|
);
|
||||||
this.messages.callSubscriptions();
|
this.messages.callSubscriptions();
|
||||||
};
|
};
|
||||||
|
sendMessage = async (chatMessage) => {
|
||||||
|
if (isConnected.value == false || this.isSubscribed.value == false)
|
||||||
|
return false;
|
||||||
|
const encryptedBody = this.encryptionKey.value == "" ? chatMessage.body : await encryptString(chatMessage.body, this.encryptionKey.value);
|
||||||
|
chatMessage.body = encryptedBody;
|
||||||
|
const messageString = JSON.stringify(chatMessage);
|
||||||
|
UDN.sendMessage(chatMessage.channel, messageString);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
// objects
|
||||||
|
createObjectFromTitle = (title) => {
|
||||||
|
const firstObjectContent = this.createObjectContent();
|
||||||
|
const messageObject = {
|
||||||
|
id: UUID(),
|
||||||
|
title,
|
||||||
|
contentVersions: {}
|
||||||
|
};
|
||||||
|
this.addObjectContent(messageObject, firstObjectContent);
|
||||||
|
return messageObject;
|
||||||
|
};
|
||||||
|
createObjectContent = () => {
|
||||||
|
return {
|
||||||
|
id: UUID(),
|
||||||
|
isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
addObjectContent = (messageObject, content) => {
|
||||||
|
messageObject.contentVersions[content.id] = content;
|
||||||
|
};
|
||||||
|
addObject = (messageObject) => {
|
||||||
|
const existingObject = this.objects.value.get(messageObject.id);
|
||||||
|
if (existingObject) {
|
||||||
|
existingObject.title = messageObject.title;
|
||||||
|
Object.values(messageObject.contentVersions).forEach((content) => {
|
||||||
|
this.addObjectContent(existingObject, content);
|
||||||
|
});
|
||||||
|
this.objects.set(existingObject.id, existingObject);
|
||||||
|
} else {
|
||||||
|
this.objects.set(messageObject.id, messageObject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addObjectAndSend = (messageObject) => {
|
||||||
|
this.addObject(messageObject);
|
||||||
|
this.sendObject(messageObject);
|
||||||
|
};
|
||||||
|
sendObject = (messageObject) => {
|
||||||
|
this.objectOutbox.set(messageObject.id, messageObject);
|
||||||
|
this.sendMessagesInOutbox();
|
||||||
|
};
|
||||||
|
deleteObject = (messageObject) => {
|
||||||
|
this.objects.remove(messageObject.id);
|
||||||
|
this.objectOutbox.remove(messageObject.id);
|
||||||
|
};
|
||||||
|
resendObjects = () => {
|
||||||
|
this.objects.value.forEach((messageObject) => {
|
||||||
|
this.sendObject(messageObject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
clearObjects = () => {
|
||||||
|
this.objects.clear();
|
||||||
|
};
|
||||||
|
getSortedContents = (messageObject) => {
|
||||||
|
const contents = Object.values(messageObject.contentVersions);
|
||||||
|
contents.sort(
|
||||||
|
(a, b) => a.isoDateVersionCreated > b.isoDateVersionCreated ? 0 : 1
|
||||||
|
);
|
||||||
|
return contents;
|
||||||
|
};
|
||||||
|
getMostRecentContentId = (messageObject) => {
|
||||||
|
const contents = Object.values(messageObject.contentVersions);
|
||||||
|
return contents[contents.length - 1].id;
|
||||||
|
};
|
||||||
|
getMostRecentContent = (messageObject) => {
|
||||||
|
const id = this.getMostRecentContentId(messageObject);
|
||||||
|
return this.getObjectContentFromId(messageObject, id);
|
||||||
|
};
|
||||||
|
getObjectContentFromId = (messageObject, contentId) => {
|
||||||
|
return messageObject.contentVersions[contentId];
|
||||||
|
};
|
||||||
// channel
|
// channel
|
||||||
setChannel = () => {
|
setChannel = () => {
|
||||||
if (this.cannotSetChannel.value == true) return;
|
if (this.cannotSetChannel.value == true) return;
|
||||||
@@ -755,6 +926,7 @@
|
|||||||
var chats = new ListState();
|
var chats = new ListState();
|
||||||
var chatIds = restoreListState("chat-ids");
|
var chatIds = restoreListState("chat-ids");
|
||||||
var selectedChat = new State(void 0);
|
var selectedChat = new State(void 0);
|
||||||
|
var isShowingChatTools = new State(false);
|
||||||
var newChatName = new State("");
|
var newChatName = new State("");
|
||||||
var cannotCreateChat = createProxyState(
|
var cannotCreateChat = createProxyState(
|
||||||
[newChatName],
|
[newChatName],
|
||||||
@@ -766,6 +938,7 @@
|
|||||||
newChatName.value = "";
|
newChatName.value = "";
|
||||||
}
|
}
|
||||||
function closeChatView() {
|
function closeChatView() {
|
||||||
|
isShowingChatTools.value = false;
|
||||||
selectedChat.value = void 0;
|
selectedChat.value = void 0;
|
||||||
document.getElementById("settings-tab")?.scrollIntoView();
|
document.getElementById("settings-tab")?.scrollIntoView();
|
||||||
}
|
}
|
||||||
@@ -774,6 +947,9 @@
|
|||||||
chat.hasUnreadMessages.value = false;
|
chat.hasUnreadMessages.value = false;
|
||||||
document.getElementById("message-tab")?.scrollIntoView();
|
document.getElementById("message-tab")?.scrollIntoView();
|
||||||
}
|
}
|
||||||
|
function toggleChatTools() {
|
||||||
|
isShowingChatTools.value = !isShowingChatTools.value;
|
||||||
|
}
|
||||||
chatIds.value.forEach((id) => chats.add(new Chat(id)));
|
chatIds.value.forEach((id) => chats.add(new Chat(id)));
|
||||||
if (serverAddressInput.value != "" && didRequestConnection.value == true) {
|
if (serverAddressInput.value != "" && didRequestConnection.value == true) {
|
||||||
connect();
|
connect();
|
||||||
@@ -783,9 +959,11 @@
|
|||||||
var englishTranslations = {
|
var englishTranslations = {
|
||||||
// general
|
// general
|
||||||
set: "Set",
|
set: "Set",
|
||||||
|
save: "Save",
|
||||||
back: "Back",
|
back: "Back",
|
||||||
undoChanges: "Undo changes",
|
undoChanges: "Undo changes",
|
||||||
close: "Close",
|
close: "Close",
|
||||||
|
discard: "Discard",
|
||||||
// overview
|
// overview
|
||||||
overview: "Overview",
|
overview: "Overview",
|
||||||
connection: "Connection",
|
connection: "Connection",
|
||||||
@@ -797,7 +975,7 @@
|
|||||||
serverAddress: "Server Address",
|
serverAddress: "Server Address",
|
||||||
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
||||||
connectToServer: "Connect",
|
connectToServer: "Connect",
|
||||||
disconnect: "Disonnect",
|
disconnect: "Disconnect",
|
||||||
mailbox: "Mailbox",
|
mailbox: "Mailbox",
|
||||||
requestMailbox: "Enable",
|
requestMailbox: "Enable",
|
||||||
deleteMailbox: "Disable",
|
deleteMailbox: "Disable",
|
||||||
@@ -805,7 +983,7 @@
|
|||||||
primaryChannel: "Primary channel",
|
primaryChannel: "Primary channel",
|
||||||
primaryChannelPlaceholder: "my-channel",
|
primaryChannelPlaceholder: "my-channel",
|
||||||
addChat: "Add",
|
addChat: "Add",
|
||||||
// messages
|
// chat
|
||||||
showChatOptions: "show chat options",
|
showChatOptions: "show chat options",
|
||||||
configureChatTitle: "Configure Chat",
|
configureChatTitle: "Configure Chat",
|
||||||
secondaryChannel: "Secondary channel",
|
secondaryChannel: "Secondary channel",
|
||||||
@@ -815,34 +993,53 @@
|
|||||||
encryptionKey: "Encryption key",
|
encryptionKey: "Encryption key",
|
||||||
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
||||||
showKey: "Show key",
|
showKey: "Show key",
|
||||||
removeChat: "Remove chat",
|
clearObjects: "Delete all objects",
|
||||||
clearChatMessages: "Clear chat messages",
|
clearChatMessages: "Clear chat messages",
|
||||||
|
removeChat: "Remove chat",
|
||||||
messageInOutbox: "Pending",
|
messageInOutbox: "Pending",
|
||||||
noChatSelected: "No chat selected",
|
noChatSelected: "No chat selected",
|
||||||
composerPlaceholder: "Type a message...",
|
composerPlaceholder: "Type a message...",
|
||||||
sendMessage: "Send",
|
sendMessage: "Send",
|
||||||
|
// messages
|
||||||
resendMessage: "Resend message",
|
resendMessage: "Resend message",
|
||||||
decryptMessage: "Decrypt message",
|
decryptMessage: "Decrypt message",
|
||||||
copyMessage: "Copy message",
|
copyMessage: "Copy message",
|
||||||
deleteMessage: "Delete message"
|
deleteMessage: "Delete message",
|
||||||
|
// objects
|
||||||
|
showObjects: "show objects",
|
||||||
|
createObject: "create object",
|
||||||
|
untitledObject: "Untitled Object",
|
||||||
|
viewAll: "All",
|
||||||
|
viewNotes: "Notes",
|
||||||
|
noObjects: "No objects",
|
||||||
|
noNotes: "No notes",
|
||||||
|
objectTitle: "Object title",
|
||||||
|
objectTitlePlaceholder: "My object",
|
||||||
|
objectVersion: "Object version",
|
||||||
|
noteContent: "Note",
|
||||||
|
noteContentPlaceholder: "Take a note...",
|
||||||
|
resendObjects: "Resend all objects",
|
||||||
|
deleteObject: "Delete object"
|
||||||
};
|
};
|
||||||
var allTranslations = {
|
var allTranslations = {
|
||||||
en: englishTranslations,
|
en: englishTranslations,
|
||||||
es: {
|
es: {
|
||||||
// general
|
// general
|
||||||
set: "Guardar",
|
set: "Guardar",
|
||||||
|
save: "Guardar",
|
||||||
back: "Atr\xE1s",
|
back: "Atr\xE1s",
|
||||||
undoChanges: "Deshacer",
|
undoChanges: "Deshacer",
|
||||||
close: "Cerrar",
|
close: "Cerrar",
|
||||||
|
discard: "Descartar",
|
||||||
// overview
|
// overview
|
||||||
overview: "Resumen",
|
overview: "Resumen",
|
||||||
connection: "Conexion",
|
connection: "Conexi\xF3n",
|
||||||
chats: "Chats",
|
chats: "Chats",
|
||||||
yourName: "Tu nombre",
|
yourName: "Tu nombre",
|
||||||
namePlaceholder: "Juan P\xE9rez",
|
namePlaceholder: "Juan P\xE9rez",
|
||||||
encryptionUnavailableTitle: "Cifrado no disponible",
|
encryptionUnavailableTitle: "Cifrado no disponible",
|
||||||
encryptionUnavailableMessage: "Obt\xE9n esta aplicaci\xF3n a traves de HTTPS o contin\xFAa sin cifrado",
|
encryptionUnavailableMessage: "Obt\xE9n esta aplicaci\xF3n a traves de HTTPS o contin\xFAa sin cifrado",
|
||||||
serverAddress: "Direccion del servidor",
|
serverAddress: "Direcci\xF3n del servidor",
|
||||||
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
||||||
connectToServer: "Conectar",
|
connectToServer: "Conectar",
|
||||||
disconnect: "Desconectar",
|
disconnect: "Desconectar",
|
||||||
@@ -853,33 +1050,48 @@
|
|||||||
primaryChannel: "Canal principal",
|
primaryChannel: "Canal principal",
|
||||||
primaryChannelPlaceholder: "mi-canal",
|
primaryChannelPlaceholder: "mi-canal",
|
||||||
addChat: "A\xF1adir",
|
addChat: "A\xF1adir",
|
||||||
// messages
|
// chat
|
||||||
showChatOptions: "Mostrar opciones del chat",
|
showChatOptions: "Mostrar opciones del chat",
|
||||||
configureChatTitle: "Configurar chat",
|
configureChatTitle: "Configurar chat",
|
||||||
secondaryChannel: "Canal segundario",
|
secondaryChannel: "Canal secundario",
|
||||||
secondaryChannelPlaceholder: "A\xF1adir canal segundario",
|
secondaryChannelPlaceholder: "A\xF1adir canal secundario",
|
||||||
addSecondaryChannel: "A\xF1adir canal segundario",
|
addSecondaryChannel: "A\xF1adir canal secundario",
|
||||||
removeSecondaryChannel: "Eliminar canal segundario",
|
removeSecondaryChannel: "Eliminar canal secundario",
|
||||||
encryptionKey: "Clave de cifrado",
|
encryptionKey: "Clave de cifrado",
|
||||||
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
||||||
showKey: "Mostrar clave",
|
showKey: "Mostrar clave",
|
||||||
|
clearObjects: "Eliminar todos los objetos",
|
||||||
|
clearChatMessages: "Eliminar todos los mensajes",
|
||||||
removeChat: "Eliminar chat",
|
removeChat: "Eliminar chat",
|
||||||
clearChatMessages: "Eliminar todos mensajes",
|
|
||||||
noChatSelected: "Selecciona un chat",
|
|
||||||
messageInOutbox: "Pendiente",
|
messageInOutbox: "Pendiente",
|
||||||
|
noChatSelected: "Selecciona un chat",
|
||||||
composerPlaceholder: "Escribe un mensaje...",
|
composerPlaceholder: "Escribe un mensaje...",
|
||||||
sendMessage: "Enviar",
|
sendMessage: "Enviar",
|
||||||
resendMessage: "Enviar de nuevo",
|
// messages
|
||||||
|
resendMessage: "Reenviar mensaje",
|
||||||
decryptMessage: "Descifrar mensaje",
|
decryptMessage: "Descifrar mensaje",
|
||||||
copyMessage: "Copiar mensaje",
|
copyMessage: "Copiar mensaje",
|
||||||
deleteMessage: "Eliminar este mensaje"
|
deleteMessage: "Eliminar este mensaje",
|
||||||
|
// objects
|
||||||
|
showObjects: "mostrar objetos",
|
||||||
|
createObject: "a\xF1adir nuevo objeto",
|
||||||
|
untitledObject: "Sin T\xEDtulo",
|
||||||
|
objectTitle: "T\xEDtulo",
|
||||||
|
objectTitlePlaceholder: "Mi objeto",
|
||||||
|
objectVersion: "Version del objeto",
|
||||||
|
noteContent: "Nota",
|
||||||
|
noteContentPlaceholder: "Toma nota...",
|
||||||
|
resendObjects: "Reenviar todos objetos",
|
||||||
|
deleteObject: "Eliminar objeto"
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
// general
|
// general
|
||||||
set: "OK",
|
set: "OK",
|
||||||
|
save: "Sichern",
|
||||||
back: "Zur\xFCck",
|
back: "Zur\xFCck",
|
||||||
undoChanges: "\xC4nderungen verwerfen",
|
undoChanges: "\xC4nderungen verwerfen",
|
||||||
close: "Schlie\xDFen",
|
close: "Schlie\xDFen",
|
||||||
|
discard: "Verwerfen",
|
||||||
// overview
|
// overview
|
||||||
overview: "\xDCbersicht",
|
overview: "\xDCbersicht",
|
||||||
connection: "Verbindung",
|
connection: "Verbindung",
|
||||||
@@ -899,7 +1111,7 @@
|
|||||||
primaryChannel: "Hauptkanal",
|
primaryChannel: "Hauptkanal",
|
||||||
primaryChannelPlaceholder: "mein-kanal",
|
primaryChannelPlaceholder: "mein-kanal",
|
||||||
addChat: "Hinzuf\xFCgen",
|
addChat: "Hinzuf\xFCgen",
|
||||||
// messages
|
// chat
|
||||||
showChatOptions: "Chatoptionen einblenden",
|
showChatOptions: "Chatoptionen einblenden",
|
||||||
configureChatTitle: "Chat konfigurieren",
|
configureChatTitle: "Chat konfigurieren",
|
||||||
secondaryChannel: "Zweitkanal",
|
secondaryChannel: "Zweitkanal",
|
||||||
@@ -909,22 +1121,232 @@
|
|||||||
encryptionKey: "Schl\xFCssel",
|
encryptionKey: "Schl\xFCssel",
|
||||||
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
||||||
showKey: "Schl\xFCssel anzeigen",
|
showKey: "Schl\xFCssel anzeigen",
|
||||||
removeChat: "Chat l\xF6schen",
|
clearObjects: "Alle Objekte l\xF6schen",
|
||||||
clearChatMessages: "Nachrichtenverlauf leeren",
|
clearChatMessages: "Nachrichtenverlauf leeren",
|
||||||
noChatSelected: "Kein Chat ausgew\xE4hlt",
|
removeChat: "Chat l\xF6schen",
|
||||||
messageInOutbox: "Ausstehend",
|
messageInOutbox: "Ausstehend",
|
||||||
|
noChatSelected: "Kein Chat ausgew\xE4hlt",
|
||||||
composerPlaceholder: "Neue Nachricht...",
|
composerPlaceholder: "Neue Nachricht...",
|
||||||
sendMessage: "Senden",
|
sendMessage: "Senden",
|
||||||
|
// messages
|
||||||
resendMessage: "Erneut senden",
|
resendMessage: "Erneut senden",
|
||||||
decryptMessage: "Nachricht entschl\xFCsseln",
|
decryptMessage: "Nachricht entschl\xFCsseln",
|
||||||
copyMessage: "Nachricht kopieren",
|
copyMessage: "Nachricht kopieren",
|
||||||
deleteMessage: "Nachricht l\xF6schen"
|
deleteMessage: "Nachricht l\xF6schen",
|
||||||
|
// objects
|
||||||
|
showObjects: "Objekte anzeigen",
|
||||||
|
createObject: "Neues Objekt erstellen",
|
||||||
|
untitledObject: "Unbenannt",
|
||||||
|
objectTitle: "Titel",
|
||||||
|
objectTitlePlaceholder: "Mein Objekt",
|
||||||
|
objectVersion: "Version des Objekts",
|
||||||
|
noteContent: "Notiz",
|
||||||
|
noteContentPlaceholder: "Notiz eingeben...",
|
||||||
|
resendObjects: "Objekte erneut senden",
|
||||||
|
deleteObject: "Objekt l\xF6schen"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var language = navigator.language.substring(0, 2);
|
var language = navigator.language.substring(0, 2);
|
||||||
var translation = allTranslations[language] ?? allTranslations.en;
|
var translation = allTranslations[language] ?? allTranslations.en;
|
||||||
|
|
||||||
// src/Views/chatOptionModal.tsx
|
// src/Views/Objects/allObjectsView.tsx
|
||||||
|
function AllObjectsView(chat, selectedObject, isShowingObjectModal) {
|
||||||
|
const objectConverter = (messageObject) => {
|
||||||
|
function select() {
|
||||||
|
selectedObject.value = messageObject;
|
||||||
|
isShowingObjectModal.value = true;
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement("button", { class: "tile", "on:click": select }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, messageObject.title), /* @__PURE__ */ createElement("span", { class: "secondary" }, messageObject.id)));
|
||||||
|
};
|
||||||
|
const content = createProxyState(
|
||||||
|
[chat.objects],
|
||||||
|
() => chat.objects.value.size == 0 ? /* @__PURE__ */ createElement("div", { class: "flex-column width-100 height-100 align-center justify-center secondary" }, translation.noObjects) : /* @__PURE__ */ createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
class: "grid gap padding",
|
||||||
|
style: "grid-template-columns: repeat(auto-fill, minmax(350px, 1fr))",
|
||||||
|
"children:prepend": [chat.objects, objectConverter]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100", "children:set": content });
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/Views/Objects/noteObjectsView.tsx
|
||||||
|
function NoteObjectsView(chat, selectedObject, isShowingObjectModal) {
|
||||||
|
const objectConverter = (messageObject) => {
|
||||||
|
const latest = chat.getMostRecentContent(messageObject);
|
||||||
|
function select() {
|
||||||
|
selectedObject.value = messageObject;
|
||||||
|
isShowingObjectModal.value = true;
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement("button", { class: "tile", "on:click": select }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, messageObject.title), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, latest.noteContent.split("\n")[0])));
|
||||||
|
};
|
||||||
|
const notes = new ListState();
|
||||||
|
chat.objects.subscribe(() => {
|
||||||
|
notes.clear();
|
||||||
|
chat.objects.value.forEach((messageObject) => {
|
||||||
|
const latest = chat.getMostRecentContent(messageObject);
|
||||||
|
if (!latest.noteContent) return;
|
||||||
|
notes.add(messageObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const content = createProxyState(
|
||||||
|
[chat.objects],
|
||||||
|
() => notes.value.size == 0 ? /* @__PURE__ */ createElement("div", { class: "flex-column width-100 height-100 align-center justify-center secondary" }, translation.noNotes) : /* @__PURE__ */ createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
class: "grid gap padding",
|
||||||
|
style: "grid-template-columns: repeat(auto-fill, minmax(350px, 1fr))",
|
||||||
|
"children:prepend": [notes, objectConverter]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100", "children:set": content });
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/Views/Objects/objectDetailModal.tsx
|
||||||
|
function ObjectDetailModal(chat, messageObject, isPresented) {
|
||||||
|
const editingTitle = new State(messageObject.title);
|
||||||
|
const selectedMessageObjectId = new State(
|
||||||
|
chat.getMostRecentContentId(messageObject)
|
||||||
|
);
|
||||||
|
const selectedMessageObject = createProxyState(
|
||||||
|
[selectedMessageObjectId],
|
||||||
|
() => chat.getObjectContentFromId(messageObject, selectedMessageObjectId.value)
|
||||||
|
);
|
||||||
|
const didEditContent = new State(false);
|
||||||
|
const editingNoteContent = createProxyState(
|
||||||
|
[selectedMessageObject],
|
||||||
|
() => selectedMessageObject.value.noteContent ?? ""
|
||||||
|
);
|
||||||
|
bulkSubscribe(
|
||||||
|
[editingNoteContent],
|
||||||
|
() => didEditContent.value = true
|
||||||
|
);
|
||||||
|
function handleKeyDown(e) {
|
||||||
|
if (!e.metaKey && !e.ctrlKey) return;
|
||||||
|
switch (e.key) {
|
||||||
|
case "s":
|
||||||
|
e.preventDefault();
|
||||||
|
saveAndClose();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function closeModal() {
|
||||||
|
isPresented.value = false;
|
||||||
|
}
|
||||||
|
function saveAndClose() {
|
||||||
|
messageObject.title = editingTitle.value;
|
||||||
|
if (didEditContent.value == true) {
|
||||||
|
chat.addObjectContent(messageObject, {
|
||||||
|
isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString(),
|
||||||
|
id: UUID(),
|
||||||
|
noteContent: editingNoteContent.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
chat.addObjectAndSend(messageObject);
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
function deleteAndClose() {
|
||||||
|
chat.deleteObject(messageObject);
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented, "on:keydown": handleKeyDown }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, messageObject.title), /* @__PURE__ */ createElement("span", { class: "secondary" }, messageObject.id), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectTitle), /* @__PURE__ */ createElement(
|
||||||
|
"input",
|
||||||
|
{
|
||||||
|
autofocus: true,
|
||||||
|
"bind:value": editingTitle,
|
||||||
|
placeholder: translation.objectTitlePlaceholder
|
||||||
|
}
|
||||||
|
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectVersion), /* @__PURE__ */ createElement("select", { "bind:value": selectedMessageObjectId }, ...chat.getSortedContents(messageObject).map((content) => /* @__PURE__ */ createElement("option", { value: content.id }, new Date(
|
||||||
|
content.isoDateVersionCreated
|
||||||
|
).toLocaleString()))), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "sticky_note_2"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.noteContent), /* @__PURE__ */ createElement(
|
||||||
|
"textarea",
|
||||||
|
{
|
||||||
|
rows: "5",
|
||||||
|
"bind:value": editingNoteContent,
|
||||||
|
placeholder: translation.noteContentPlaceholder
|
||||||
|
}
|
||||||
|
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("button", { class: "danger width-input", "on:click": deleteAndClose }, translation.deleteObject, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 danger", "on:click": closeModal }, translation.discard), /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 primary", "on:click": saveAndClose }, translation.save, /* @__PURE__ */ createElement("span", { class: "icon" }, "save")))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/Views/Objects/chatObjectView.tsx
|
||||||
|
var viewTypes = {
|
||||||
|
all: [translation.viewAll, "grid_view"],
|
||||||
|
notes: [translation.viewNotes, "sticky_note_2"]
|
||||||
|
};
|
||||||
|
function ChatObjectView(chat) {
|
||||||
|
const isShowingObjectModal = new State(false);
|
||||||
|
const selectedObject = new State(void 0);
|
||||||
|
const objectModal = createProxyState(
|
||||||
|
[chat.objects, selectedObject],
|
||||||
|
() => {
|
||||||
|
if (selectedObject.value == void 0) return /* @__PURE__ */ createElement("div", null);
|
||||||
|
return ObjectDetailModal(
|
||||||
|
chat,
|
||||||
|
selectedObject.value,
|
||||||
|
isShowingObjectModal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const selectedViewType = new State("all");
|
||||||
|
const mainView = createProxyState([selectedViewType], () => {
|
||||||
|
function getViewFunction() {
|
||||||
|
switch (selectedViewType.value) {
|
||||||
|
case "notes":
|
||||||
|
return NoteObjectsView;
|
||||||
|
default:
|
||||||
|
return AllObjectsView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getViewFunction()(chat, selectedObject, isShowingObjectModal);
|
||||||
|
});
|
||||||
|
function createObject() {
|
||||||
|
const newObject = chat.createObjectFromTitle(translation.untitledObject);
|
||||||
|
chat.addObjectAndSend(newObject);
|
||||||
|
selectedObject.value = newObject;
|
||||||
|
isShowingObjectModal.value = true;
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "chat-object-view flex-column" }, /* @__PURE__ */ createElement("div", { class: "flex-row align-center border-bottom" }, /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "primary height-100",
|
||||||
|
"on:click": createObject,
|
||||||
|
"aria-label": translation.createObject
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
||||||
|
), /* @__PURE__ */ createElement("div", { class: "padding-sm flex flex-row gap justify-center scroll-h width-100" }, ...Object.keys(viewTypes).map(
|
||||||
|
(key) => ViewTypeToggle(key, selectedViewType)
|
||||||
|
)), /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "height-100",
|
||||||
|
"on:click": createObject,
|
||||||
|
"aria-label": translation.createObject
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "visibility")
|
||||||
|
)), /* @__PURE__ */ createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
class: "width-100 height-100 flex-1 scroll-h",
|
||||||
|
"children:set": mainView
|
||||||
|
}
|
||||||
|
), /* @__PURE__ */ createElement("div", { "children:set": objectModal }));
|
||||||
|
}
|
||||||
|
function ViewTypeToggle(key, selection) {
|
||||||
|
const [label, icon] = viewTypes[key];
|
||||||
|
function select() {
|
||||||
|
selection.value = key;
|
||||||
|
}
|
||||||
|
const isSelected = createProxyState(
|
||||||
|
[selection],
|
||||||
|
() => selection.value == key
|
||||||
|
);
|
||||||
|
return /* @__PURE__ */ createElement("button", { "aria-label": label, "on:click": select, "toggle:selected": isSelected }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/Views/Chat/chatOptionModal.tsx
|
||||||
function ChatOptionModal(chat, isPresented) {
|
function ChatOptionModal(chat, isPresented) {
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
isPresented.value = false;
|
isPresented.value = false;
|
||||||
@@ -1011,7 +1433,16 @@
|
|||||||
"bind:value": chat.encryptionKey,
|
"bind:value": chat.encryptionKey,
|
||||||
"set:type": inputType
|
"set:type": inputType
|
||||||
}
|
}
|
||||||
))), /* @__PURE__ */ createElement("label", { class: "inline margin-0" }, /* @__PURE__ */ createElement("input", { type: "checkbox", "bind:checked": shouldShowKey }), translation.showKey)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement(
|
))), /* @__PURE__ */ createElement("label", { class: "inline margin-0" }, /* @__PURE__ */ createElement("input", { type: "checkbox", "bind:checked": shouldShowKey }), translation.showKey)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap width-input" }, /* @__PURE__ */ createElement("button", { "on:click": chat.resendObjects }, translation.resendObjects, /* @__PURE__ */ createElement("span", { class: "icon" }, "replay"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap width-input" }, /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "danger",
|
||||||
|
"on:click": chat.clearObjects,
|
||||||
|
"toggle:disabled": chat.cannotClearObjects
|
||||||
|
},
|
||||||
|
translation.clearObjects,
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "deployed_code")
|
||||||
|
), /* @__PURE__ */ createElement(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
class: "danger",
|
class: "danger",
|
||||||
@@ -1019,11 +1450,11 @@
|
|||||||
"toggle:disabled": chat.cannotClearMessages
|
"toggle:disabled": chat.cannotClearMessages
|
||||||
},
|
},
|
||||||
translation.clearChatMessages,
|
translation.clearChatMessages,
|
||||||
/* @__PURE__ */ createElement("span", { class: "icon" }, "delete_sweep")
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "chat_error")
|
||||||
), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteChat }, translation.removeChat, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("button", { "on:click": closeModal }, translation.close, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteChat }, translation.removeChat, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete_forever")))), /* @__PURE__ */ createElement("button", { "on:click": closeModal }, translation.close, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/Views/messageComposer.tsx
|
// src/Views/Chat/messageComposer.tsx
|
||||||
function MessageComposer(chat) {
|
function MessageComposer(chat) {
|
||||||
return /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, " ", /* @__PURE__ */ createElement(
|
return /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, " ", /* @__PURE__ */ createElement(
|
||||||
"input",
|
"input",
|
||||||
@@ -1032,20 +1463,20 @@
|
|||||||
style: "max-width: unset",
|
style: "max-width: unset",
|
||||||
placeholder: translation.composerPlaceholder,
|
placeholder: translation.composerPlaceholder,
|
||||||
"bind:value": chat.composingMessage,
|
"bind:value": chat.composingMessage,
|
||||||
"on:enter": chat.sendNewMessage
|
"on:enter": chat.sendMessageFromComposer
|
||||||
}
|
}
|
||||||
), /* @__PURE__ */ createElement(
|
), /* @__PURE__ */ createElement(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
class: "primary",
|
class: "primary",
|
||||||
"on:click": chat.sendNewMessage,
|
"on:click": chat.sendMessageFromComposer,
|
||||||
"toggle:disabled": chat.cannotSendMessage
|
"toggle:disabled": chat.cannotSendMessage
|
||||||
},
|
},
|
||||||
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/Views/threadView.tsx
|
// src/Views/Chat/threadView.tsx
|
||||||
function ThreadView(chat) {
|
function ThreadView(chat) {
|
||||||
const messageConverter = (message) => {
|
const messageConverter = (message) => {
|
||||||
function resendMessage() {
|
function resendMessage() {
|
||||||
@@ -1108,15 +1539,19 @@
|
|||||||
"children:append": [chat.outbox, outboxMessageConverter]
|
"children:append": [chat.outbox, outboxMessageConverter]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const listWrapper = /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, messageList, outboxList);
|
const listWrapper = /* @__PURE__ */ createElement("div", { class: "thread-view flex-column gap" }, messageList, outboxList);
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
const scrollFromBottom = listWrapper.scrollHeight - (listWrapper.scrollTop + listWrapper.offsetHeight);
|
|
||||||
if (scrollFromBottom > 400) return;
|
|
||||||
listWrapper.scrollTop = listWrapper.scrollHeight;
|
listWrapper.scrollTop = listWrapper.scrollHeight;
|
||||||
}
|
}
|
||||||
chat.messages.handleAddition(scrollToBottom);
|
function scrollToBottomIfAppropriate() {
|
||||||
chat.outbox.handleAddition(scrollToBottom);
|
const scrollFromBottom = listWrapper.scrollHeight - (listWrapper.scrollTop + listWrapper.offsetHeight);
|
||||||
setTimeout(() => listWrapper.scrollTop = listWrapper.scrollHeight, 50);
|
if (scrollFromBottom > 400) return;
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
chat.messages.handleAddition(scrollToBottomIfAppropriate);
|
||||||
|
chat.outbox.handleAddition(scrollToBottomIfAppropriate);
|
||||||
|
isShowingChatTools.subscribe(() => scrollToBottom());
|
||||||
|
setTimeout(() => scrollToBottom(), 50);
|
||||||
return listWrapper;
|
return listWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1132,6 +1567,14 @@
|
|||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
/* @__PURE__ */ createElement("header", { class: "padding-0" }, /* @__PURE__ */ createElement("span", { class: "flex-row align-center" }, /* @__PURE__ */ createElement("button", { "aria-label": translation.back, "on:click": closeChatView }, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")), /* @__PURE__ */ createElement("span", { "subscribe:innerText": chat.primaryChannel })), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
/* @__PURE__ */ createElement("header", { class: "padding-0" }, /* @__PURE__ */ createElement("span", { class: "flex-row align-center" }, /* @__PURE__ */ createElement("button", { "aria-label": translation.back, "on:click": closeChatView }, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")), /* @__PURE__ */ createElement("span", { "subscribe:innerText": chat.primaryChannel })), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
"aria-label": translation.showObjects,
|
||||||
|
"on:click": toggleChatTools,
|
||||||
|
"toggle:selected": isShowingChatTools
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "deployed_code")
|
||||||
|
), /* @__PURE__ */ createElement(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
"aria-label": translation.showChatOptions,
|
"aria-label": translation.showChatOptions,
|
||||||
@@ -1139,15 +1582,23 @@
|
|||||||
},
|
},
|
||||||
/* @__PURE__ */ createElement("span", { class: "icon" }, "tune")
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "tune")
|
||||||
))),
|
))),
|
||||||
|
ChatObjectView(chat),
|
||||||
ThreadView(chat),
|
ThreadView(chat),
|
||||||
/* @__PURE__ */ createElement("footer", null, MessageComposer(chat)),
|
/* @__PURE__ */ createElement("footer", null, MessageComposer(chat)),
|
||||||
ChatOptionModal(chat, isShowingOptions)
|
ChatOptionModal(chat, isShowingOptions)
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
return /* @__PURE__ */ createElement("article", { id: "message-tab", "children:set": messageTabContent });
|
return /* @__PURE__ */ createElement(
|
||||||
|
"article",
|
||||||
|
{
|
||||||
|
"toggle:showingchattools": isShowingChatTools,
|
||||||
|
id: "message-tab",
|
||||||
|
"children:set": messageTabContent
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/Views/chatListSection.tsx
|
// src/Views/Overview/chatListSection.tsx
|
||||||
var chatConverter = (chat) => {
|
var chatConverter = (chat) => {
|
||||||
function select() {
|
function select() {
|
||||||
selectChat(chat);
|
selectChat(chat);
|
||||||
@@ -1194,7 +1645,7 @@
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/Views/connectionSection.tsx
|
// src/Views/Overview/connectionSection.tsx
|
||||||
var addressConverter = (address) => {
|
var addressConverter = (address) => {
|
||||||
return /* @__PURE__ */ createElement("option", null, address);
|
return /* @__PURE__ */ createElement("option", null, address);
|
||||||
};
|
};
|
||||||
@@ -1261,7 +1712,7 @@
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/Views/personalSection.tsx
|
// src/Views/Overview/personalSection.tsx
|
||||||
function PersonalSection() {
|
function PersonalSection() {
|
||||||
return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.yourName), /* @__PURE__ */ createElement(
|
return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.yourName), /* @__PURE__ */ createElement(
|
||||||
"input",
|
"input",
|
||||||
|
|||||||
Vendored
+30
-6
@@ -140,10 +140,12 @@ select,
|
|||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
button:hover,
|
button:hover,
|
||||||
select:hover,
|
select:hover,
|
||||||
button.tile.ghost:hover,
|
button.tile.ghost:hover {
|
||||||
[selected]:active {
|
|
||||||
background-color: var(--surface-hover);
|
background-color: var(--surface-hover);
|
||||||
}
|
}
|
||||||
|
[selected]:hover {
|
||||||
|
background-color: var(--surface);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
button:active,
|
button:active,
|
||||||
select:active,
|
select:active,
|
||||||
@@ -151,6 +153,9 @@ button.tile.ghost:active,
|
|||||||
[selected] {
|
[selected] {
|
||||||
background-color: var(--surface-active);
|
background-color: var(--surface-active);
|
||||||
}
|
}
|
||||||
|
[selected]:active {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
}
|
||||||
header button {
|
header button {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
@@ -318,6 +323,10 @@ hr {
|
|||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid var(--border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):focus:not(
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):focus:not(
|
||||||
label.tile *
|
label.tile *
|
||||||
),
|
),
|
||||||
@@ -480,7 +489,7 @@ input[type="radio"],
|
|||||||
}
|
}
|
||||||
|
|
||||||
.justify-center {
|
.justify-center {
|
||||||
justify-content: center !important;
|
justify-content: safe center !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.justify-end {
|
.justify-end {
|
||||||
@@ -577,6 +586,10 @@ textarea,
|
|||||||
padding: var(--control-padding) !important;
|
padding: var(--control-padding) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.padding-sm {
|
||||||
|
padding: var(--gap-between-tiles) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.modal main {
|
.modal main {
|
||||||
padding: var(--modal-padding);
|
padding: var(--modal-padding);
|
||||||
}
|
}
|
||||||
@@ -597,7 +610,7 @@ label.tile:has(> span.icon) {
|
|||||||
padding-left: var(--control-padding);
|
padding-left: var(--control-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
label.tile:has(> span.icon) input {
|
label.tile:has(> span.icon) :is(input, select, textarea) {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,8 +743,7 @@ button:not(.tile):not(header *),
|
|||||||
footer,
|
footer,
|
||||||
input:not([type="checkbox"]):not([type="radio"]),
|
input:not([type="checkbox"]):not([type="radio"]),
|
||||||
select,
|
select,
|
||||||
select + .icon,
|
select + .icon {
|
||||||
textarea {
|
|
||||||
height: var(--control-height);
|
height: var(--control-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,6 +793,10 @@ input[type="radio"] {
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.tile textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
text
|
text
|
||||||
*/
|
*/
|
||||||
@@ -952,6 +968,14 @@ main {
|
|||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scroll-h {
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-v {
|
||||||
|
overflow-y: scroll !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ellipsis {
|
.ellipsis {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bloatless-react": "^1.3.1-beta1",
|
"bloatless-react": "^1.3.7",
|
||||||
"esbuild": "latest",
|
"esbuild": "latest",
|
||||||
"udn-frontend": "^1.0.7-beta1"
|
"udn-frontend": "^1.0.7-beta1"
|
||||||
},
|
},
|
||||||
|
|||||||
+198
-45
@@ -21,6 +21,22 @@ export interface ChatMessage {
|
|||||||
sender: string;
|
sender: string;
|
||||||
body: string;
|
body: string;
|
||||||
isoDate: string;
|
isoDate: string;
|
||||||
|
|
||||||
|
messageObjectString?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// object
|
||||||
|
export interface MessageObject {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
contentVersions: { [key: string]: MessageObjectContent };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageObjectContent {
|
||||||
|
isoDateVersionCreated: string;
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
noteContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// chat
|
// chat
|
||||||
@@ -34,7 +50,9 @@ export class Chat {
|
|||||||
|
|
||||||
encryptionKey: React.State<string>;
|
encryptionKey: React.State<string>;
|
||||||
messages: React.ListState<ChatMessage>;
|
messages: React.ListState<ChatMessage>;
|
||||||
|
objects: React.MapState<MessageObject>;
|
||||||
outbox: React.ListState<ChatMessage>;
|
outbox: React.ListState<ChatMessage>;
|
||||||
|
objectOutbox: React.MapState<MessageObject>;
|
||||||
isOutBoxEmpty: React.State<boolean>;
|
isOutBoxEmpty: React.State<boolean>;
|
||||||
|
|
||||||
composingMessage: React.State<string>;
|
composingMessage: React.State<string>;
|
||||||
@@ -47,6 +65,7 @@ export class Chat {
|
|||||||
cannotSetChannel: React.State<boolean>;
|
cannotSetChannel: React.State<boolean>;
|
||||||
cannotUndoChannel: React.State<boolean>;
|
cannotUndoChannel: React.State<boolean>;
|
||||||
cannotClearMessages: React.State<boolean>;
|
cannotClearMessages: React.State<boolean>;
|
||||||
|
cannotClearObjects: React.State<boolean>;
|
||||||
|
|
||||||
// init
|
// init
|
||||||
constructor(id: string = React.UUID()) {
|
constructor(id: string = React.UUID()) {
|
||||||
@@ -68,7 +87,9 @@ export class Chat {
|
|||||||
);
|
);
|
||||||
this.encryptionKey = React.restoreState(storageKeys.encyptionKey(id), "");
|
this.encryptionKey = React.restoreState(storageKeys.encyptionKey(id), "");
|
||||||
this.messages = React.restoreListState(storageKeys.messages(id));
|
this.messages = React.restoreListState(storageKeys.messages(id));
|
||||||
|
this.objects = React.restoreMapState(storageKeys.objects(id));
|
||||||
this.outbox = React.restoreListState(storageKeys.outbox(id));
|
this.outbox = React.restoreListState(storageKeys.outbox(id));
|
||||||
|
this.objectOutbox = React.restoreMapState(storageKeys.itemOutbox(id));
|
||||||
this.isOutBoxEmpty = React.createProxyState(
|
this.isOutBoxEmpty = React.createProxyState(
|
||||||
[this.outbox],
|
[this.outbox],
|
||||||
() => this.outbox.value.size == 0
|
() => this.outbox.value.size == 0
|
||||||
@@ -112,6 +133,10 @@ export class Chat {
|
|||||||
[this.messages],
|
[this.messages],
|
||||||
() => this.messages.value.size == 0
|
() => this.messages.value.size == 0
|
||||||
);
|
);
|
||||||
|
this.cannotClearObjects = React.createProxyState(
|
||||||
|
[this.objects],
|
||||||
|
() => this.objects.value.size == 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// general
|
// general
|
||||||
@@ -131,9 +156,21 @@ export class Chat {
|
|||||||
if (channels.indexOf(this.primaryChannel.value) == -1) return;
|
if (channels.indexOf(this.primaryChannel.value) == -1) return;
|
||||||
|
|
||||||
if (data.subscribed != undefined) this.handleSubscription(data.subscribed);
|
if (data.subscribed != undefined) this.handleSubscription(data.subscribed);
|
||||||
|
|
||||||
if (!data.messageBody) return;
|
if (!data.messageBody) return;
|
||||||
const { sender, body, channel, isoDate } = JSON.parse(data.messageBody);
|
|
||||||
|
const message: ChatMessage = JSON.parse(data.messageBody);
|
||||||
|
const { sender, body, channel, isoDate, messageObjectString } = message;
|
||||||
|
|
||||||
|
if (messageObjectString) {
|
||||||
|
const decryptedMessageObjectString = await decryptString(
|
||||||
|
messageObjectString,
|
||||||
|
this.encryptionKey.value
|
||||||
|
);
|
||||||
|
const messageObject = JSON.parse(decryptedMessageObjectString);
|
||||||
|
if (messageObject.id && messageObject.title)
|
||||||
|
return this.handleMessageObject(messageObject);
|
||||||
|
}
|
||||||
|
|
||||||
this.handleMessage({
|
this.handleMessage({
|
||||||
sender,
|
sender,
|
||||||
body: await decryptString(body, this.encryptionKey.value),
|
body: await decryptString(body, this.encryptionKey.value),
|
||||||
@@ -155,25 +192,28 @@ export class Chat {
|
|||||||
if (selectedChat.value != this) this.hasUnreadMessages.value = true;
|
if (selectedChat.value != this) this.hasUnreadMessages.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleMessageObject = (messageObject: MessageObject): void => {
|
||||||
|
this.addObject(messageObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
// sending
|
||||||
|
sendMessagesInOutbox = () => {
|
||||||
|
this.outbox.value.forEach(async (message) => {
|
||||||
|
const isSent = await this.sendMessage(message);
|
||||||
|
if (isSent == true) this.outbox.remove(message);
|
||||||
|
});
|
||||||
|
this.objectOutbox.value.forEach(async (messageObject) => {
|
||||||
|
const chatMessage = await this.createChatMessage("", messageObject);
|
||||||
|
const isSent = await this.sendMessage(chatMessage);
|
||||||
|
if (isSent == true) this.objectOutbox.remove(messageObject.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// messages
|
// messages
|
||||||
sendNewMessage = async (): Promise<void> => {
|
createChatMessage = async (
|
||||||
if (this.cannotSendMessage.value == true) return;
|
messageText: string,
|
||||||
|
messageObject?: MessageObject
|
||||||
await this.sendMessageText(this.composingMessage.value);
|
): Promise<ChatMessage> => {
|
||||||
this.composingMessage.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
sendMessageText = async (text: string): Promise<void> => {
|
|
||||||
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<ChatMessage> => {
|
|
||||||
// get channels
|
// get channels
|
||||||
const secondaryChannelNames: string[] = [
|
const secondaryChannelNames: string[] = [
|
||||||
...this.secondaryChannels.value.values(),
|
...this.secondaryChannels.value.values(),
|
||||||
@@ -185,54 +225,167 @@ export class Chat {
|
|||||||
|
|
||||||
// create object
|
// create object
|
||||||
const joinedChannelName = allChannelNames.join("/");
|
const joinedChannelName = allChannelNames.join("/");
|
||||||
return {
|
const chatMessage: ChatMessage = {
|
||||||
channel: joinedChannelName,
|
channel: joinedChannelName,
|
||||||
sender: senderName.value,
|
sender: senderName.value,
|
||||||
body: messageText,
|
body: messageText,
|
||||||
isoDate: new Date().toISOString(),
|
isoDate: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (messageObject)
|
||||||
|
chatMessage.messageObjectString = await encryptString(
|
||||||
|
JSON.stringify(messageObject),
|
||||||
|
this.encryptionKey.value
|
||||||
|
);
|
||||||
|
return chatMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
sendExistingMessage = async (chatMessage: ChatMessage): Promise<void> => {
|
sendMessageFromComposer = async (): Promise<void> => {
|
||||||
if (isConnected.value == true && this.isSubscribed.value == true) {
|
if (this.cannotSendMessage.value == true) return;
|
||||||
const encrypted =
|
|
||||||
this.encryptionKey.value == ""
|
|
||||||
? chatMessage.body
|
|
||||||
: await encryptString(chatMessage.body, this.encryptionKey.value);
|
|
||||||
|
|
||||||
chatMessage.body = encrypted;
|
await this.sendMessageFromText(this.composingMessage.value);
|
||||||
|
this.composingMessage.value = "";
|
||||||
const messageString = JSON.stringify(chatMessage);
|
|
||||||
UDN.sendMessage(chatMessage.channel, messageString);
|
|
||||||
} else {
|
|
||||||
this.outbox.add(chatMessage);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sendMessagesInOutbox = () => {
|
sendMessageFromText = async (text: string): Promise<void> => {
|
||||||
this.outbox.value.forEach((message) => {
|
const chatMessage = await this.createChatMessage(text);
|
||||||
this.sendExistingMessage(message);
|
this.outbox.add(chatMessage);
|
||||||
this.outbox.remove(message);
|
this.sendMessagesInOutbox();
|
||||||
});
|
};
|
||||||
|
|
||||||
|
resendMessage = (chatMessage: ChatMessage) => {
|
||||||
|
if (this.cannotResendMessage.value == true) return;
|
||||||
|
this.sendMessageFromText(chatMessage.body);
|
||||||
};
|
};
|
||||||
|
|
||||||
clearMessages = (): void => {
|
clearMessages = (): void => {
|
||||||
this.messages.clear();
|
this.messages.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteMessage = (message: ChatMessage): void => {
|
deleteMessage = (chatMessage: ChatMessage): void => {
|
||||||
this.messages.remove(message);
|
this.messages.remove(chatMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteOutboxMessage = (message: ChatMessage): void => {
|
deleteOutboxMessage = (chatMessage: ChatMessage): void => {
|
||||||
this.outbox.remove(message);
|
this.outbox.remove(chatMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
decryptReceivedMessage = async (message: ChatMessage): Promise<void> => {
|
decryptReceivedMessage = async (chatMessage: ChatMessage): Promise<void> => {
|
||||||
message.body = await decryptString(message.body, this.encryptionKey.value);
|
chatMessage.body = await decryptString(
|
||||||
|
chatMessage.body,
|
||||||
|
this.encryptionKey.value
|
||||||
|
);
|
||||||
this.messages.callSubscriptions();
|
this.messages.callSubscriptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sendMessage = async (chatMessage: ChatMessage): Promise<boolean> => {
|
||||||
|
if (isConnected.value == false || this.isSubscribed.value == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const encryptedBody =
|
||||||
|
this.encryptionKey.value == ""
|
||||||
|
? chatMessage.body
|
||||||
|
: await encryptString(chatMessage.body, this.encryptionKey.value);
|
||||||
|
|
||||||
|
chatMessage.body = encryptedBody;
|
||||||
|
|
||||||
|
const messageString = JSON.stringify(chatMessage);
|
||||||
|
UDN.sendMessage(chatMessage.channel, messageString);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// objects
|
||||||
|
createObjectFromTitle = (title: string): MessageObject => {
|
||||||
|
const firstObjectContent = this.createObjectContent();
|
||||||
|
const messageObject: MessageObject = {
|
||||||
|
id: React.UUID(),
|
||||||
|
title,
|
||||||
|
contentVersions: {},
|
||||||
|
};
|
||||||
|
this.addObjectContent(messageObject, firstObjectContent);
|
||||||
|
return messageObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
createObjectContent = () => {
|
||||||
|
return {
|
||||||
|
id: React.UUID(),
|
||||||
|
isoDateVersionCreated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
addObjectContent = (
|
||||||
|
messageObject: MessageObject,
|
||||||
|
content: MessageObjectContent
|
||||||
|
): void => {
|
||||||
|
messageObject.contentVersions[content.id] = content;
|
||||||
|
};
|
||||||
|
|
||||||
|
addObject = (messageObject: MessageObject): void => {
|
||||||
|
const existingObject = this.objects.value.get(messageObject.id);
|
||||||
|
if (existingObject) {
|
||||||
|
existingObject.title = messageObject.title;
|
||||||
|
Object.values(messageObject.contentVersions).forEach((content) => {
|
||||||
|
this.addObjectContent(existingObject, content);
|
||||||
|
});
|
||||||
|
this.objects.set(existingObject.id, existingObject);
|
||||||
|
} else {
|
||||||
|
this.objects.set(messageObject.id, messageObject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addObjectAndSend = (messageObject: MessageObject): void => {
|
||||||
|
this.addObject(messageObject);
|
||||||
|
this.sendObject(messageObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
sendObject = (messageObject: MessageObject): void => {
|
||||||
|
this.objectOutbox.set(messageObject.id, messageObject);
|
||||||
|
this.sendMessagesInOutbox();
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteObject = (messageObject: MessageObject): void => {
|
||||||
|
this.objects.remove(messageObject.id);
|
||||||
|
this.objectOutbox.remove(messageObject.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
resendObjects = (): void => {
|
||||||
|
this.objects.value.forEach((messageObject) => {
|
||||||
|
this.sendObject(messageObject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
clearObjects = (): void => {
|
||||||
|
this.objects.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
getSortedContents = (
|
||||||
|
messageObject: MessageObject
|
||||||
|
): MessageObjectContent[] => {
|
||||||
|
const contents = Object.values(messageObject.contentVersions);
|
||||||
|
contents.sort((a, b) =>
|
||||||
|
a.isoDateVersionCreated > b.isoDateVersionCreated ? 0 : 1
|
||||||
|
);
|
||||||
|
return contents;
|
||||||
|
};
|
||||||
|
|
||||||
|
getMostRecentContentId = (messageObject: MessageObject): string => {
|
||||||
|
const contents = Object.values(messageObject.contentVersions);
|
||||||
|
return contents[contents.length - 1].id;
|
||||||
|
};
|
||||||
|
|
||||||
|
getMostRecentContent = (messageObject: MessageObject): MessageObjectContent => {
|
||||||
|
const id = this.getMostRecentContentId(messageObject);
|
||||||
|
return this.getObjectContentFromId(messageObject, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getObjectContentFromId = (
|
||||||
|
messageObject: MessageObject,
|
||||||
|
contentId: string
|
||||||
|
): MessageObjectContent => {
|
||||||
|
return messageObject.contentVersions[contentId];
|
||||||
|
};
|
||||||
|
|
||||||
// channel
|
// channel
|
||||||
setChannel = (): void => {
|
setChannel = (): void => {
|
||||||
if (this.cannotSetChannel.value == true) return;
|
if (this.cannotSetChannel.value == true) return;
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export const chats = new React.ListState<Chat>();
|
|||||||
export const chatIds = React.restoreListState<string>("chat-ids");
|
export const chatIds = React.restoreListState<string>("chat-ids");
|
||||||
|
|
||||||
export const selectedChat = new React.State<Chat | undefined>(undefined);
|
export const selectedChat = new React.State<Chat | undefined>(undefined);
|
||||||
|
export const isShowingChatTools = new React.State(false);
|
||||||
export const newChatName = new React.State("");
|
export const newChatName = new React.State("");
|
||||||
|
|
||||||
export const cannotCreateChat = React.createProxyState(
|
export const cannotCreateChat = React.createProxyState(
|
||||||
@@ -154,6 +155,7 @@ export function createChat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function closeChatView() {
|
export function closeChatView() {
|
||||||
|
isShowingChatTools.value = false;
|
||||||
selectedChat.value = undefined;
|
selectedChat.value = undefined;
|
||||||
document.getElementById("settings-tab")?.scrollIntoView();
|
document.getElementById("settings-tab")?.scrollIntoView();
|
||||||
}
|
}
|
||||||
@@ -164,6 +166,10 @@ export function selectChat(chat: Chat) {
|
|||||||
document.getElementById("message-tab")?.scrollIntoView();
|
document.getElementById("message-tab")?.scrollIntoView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleChatTools() {
|
||||||
|
isShowingChatTools.value = !isShowingChatTools.value;
|
||||||
|
}
|
||||||
|
|
||||||
chatIds.value.forEach((id) => chats.add(new Chat(id)));
|
chatIds.value.forEach((id) => chats.add(new Chat(id)));
|
||||||
|
|
||||||
// INIT
|
// INIT
|
||||||
|
|||||||
+20
-5
@@ -1,10 +1,11 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import { closeChatView, selectedChat } from "../Model/model";
|
import { closeChatView, isShowingChatTools, selectedChat, toggleChatTools } from "../Model/model";
|
||||||
|
|
||||||
import { ChatOptionModal } from "../Views/chatOptionModal";
|
import { ChatObjectView } from "../Views/Objects/chatObjectView";
|
||||||
import { MessageComposer } from "../Views/messageComposer";
|
import { ChatOptionModal } from "../Views/Chat/chatOptionModal";
|
||||||
import { ThreadView } from "../Views/threadView";
|
import { MessageComposer } from "../Views/Chat/messageComposer";
|
||||||
|
import { ThreadView } from "../Views/Chat/threadView";
|
||||||
import { translation } from "../translations";
|
import { translation } from "../translations";
|
||||||
|
|
||||||
export function MessageTab() {
|
export function MessageTab() {
|
||||||
@@ -33,6 +34,13 @@ export function MessageTab() {
|
|||||||
<span subscribe:innerText={chat.primaryChannel}></span>
|
<span subscribe:innerText={chat.primaryChannel}></span>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
|
<button
|
||||||
|
aria-label={translation.showObjects}
|
||||||
|
on:click={toggleChatTools}
|
||||||
|
toggle:selected={isShowingChatTools}
|
||||||
|
>
|
||||||
|
<span class="icon">deployed_code</span>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label={translation.showChatOptions}
|
aria-label={translation.showChatOptions}
|
||||||
on:click={showOptions}
|
on:click={showOptions}
|
||||||
@@ -42,6 +50,7 @@ export function MessageTab() {
|
|||||||
</span>
|
</span>
|
||||||
</header>,
|
</header>,
|
||||||
|
|
||||||
|
ChatObjectView(chat),
|
||||||
ThreadView(chat),
|
ThreadView(chat),
|
||||||
|
|
||||||
<footer>{MessageComposer(chat)}</footer>,
|
<footer>{MessageComposer(chat)}</footer>,
|
||||||
@@ -50,5 +59,11 @@ export function MessageTab() {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
return <article id="message-tab" children:set={messageTabContent}></article>;
|
return (
|
||||||
|
<article
|
||||||
|
toggle:showingchattools={isShowingChatTools}
|
||||||
|
id="message-tab"
|
||||||
|
children:set={messageTabContent}
|
||||||
|
></article>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import * as React from "bloatless-react";
|
|||||||
|
|
||||||
import { isConnected, isEncryptionAvailable } from "../Model/model";
|
import { isConnected, isEncryptionAvailable } from "../Model/model";
|
||||||
|
|
||||||
import { ChatListSection } from "../Views/chatListSection";
|
import { ChatListSection } from "../Views/Overview/chatListSection";
|
||||||
import { ConnectionSection } from "../Views/connectionSection";
|
import { ConnectionSection } from "../Views/Overview/connectionSection";
|
||||||
import { PersonalSection } from "../Views/personalSection";
|
import { PersonalSection } from "../Views/Overview/personalSection";
|
||||||
import { translation } from "../translations";
|
import { translation } from "../translations";
|
||||||
|
|
||||||
export function OverviewTab() {
|
export function OverviewTab() {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import { Chat } from "../Model/chatModel";
|
import { Chat } from "../../Model/chatModel";
|
||||||
import { closeChatView } from "../Model/model";
|
import { closeChatView } from "../../Model/model";
|
||||||
import { translation } from "../translations";
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
export function ChatOptionModal(chat: Chat, isPresented: React.State<boolean>) {
|
export function ChatOptionModal(chat: Chat, isPresented: React.State<boolean>) {
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
@@ -20,7 +20,7 @@ export function ChatOptionModal(chat: Chat, isPresented: React.State<boolean>) {
|
|||||||
shouldShowKey.value == true ? "text" : "password"
|
shouldShowKey.value == true ? "text" : "password"
|
||||||
);
|
);
|
||||||
|
|
||||||
const secondaryChannelConverter: React.ListItemConverter<string> = (
|
const secondaryChannelConverter: React.StateItemConverter<string> = (
|
||||||
secondaryChannel
|
secondaryChannel
|
||||||
) => {
|
) => {
|
||||||
function remove() {
|
function remove() {
|
||||||
@@ -131,19 +131,37 @@ export function ChatOptionModal(chat: Chat, isPresented: React.State<boolean>) {
|
|||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
<div class="flex-column gap">
|
<div class="flex-column gap width-input">
|
||||||
|
<button on:click={chat.resendObjects}>
|
||||||
|
{translation.resendObjects}
|
||||||
|
<span class="icon">replay</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr></hr>
|
||||||
|
|
||||||
|
<div class="flex-column gap width-input">
|
||||||
|
<button
|
||||||
|
class="danger"
|
||||||
|
on:click={chat.clearObjects}
|
||||||
|
toggle:disabled={chat.cannotClearObjects}
|
||||||
|
>
|
||||||
|
{translation.clearObjects}
|
||||||
|
<span class="icon">deployed_code</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="danger"
|
class="danger"
|
||||||
on:click={chat.clearMessages}
|
on:click={chat.clearMessages}
|
||||||
toggle:disabled={chat.cannotClearMessages}
|
toggle:disabled={chat.cannotClearMessages}
|
||||||
>
|
>
|
||||||
{translation.clearChatMessages}
|
{translation.clearChatMessages}
|
||||||
<span class="icon">delete_sweep</span>
|
<span class="icon">chat_error</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="danger" on:click={deleteChat}>
|
<button class="danger" on:click={deleteChat}>
|
||||||
{translation.removeChat}
|
{translation.removeChat}
|
||||||
<span class="icon">delete</span>
|
<span class="icon">delete_forever</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import { Chat } from "../Model/chatModel";
|
import { Chat } from "../../Model/chatModel";
|
||||||
import { translation } from "../translations";
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
export function MessageComposer(chat: Chat) {
|
export function MessageComposer(chat: Chat) {
|
||||||
return (
|
return (
|
||||||
@@ -12,11 +12,11 @@ export function MessageComposer(chat: Chat) {
|
|||||||
style="max-width: unset"
|
style="max-width: unset"
|
||||||
placeholder={translation.composerPlaceholder}
|
placeholder={translation.composerPlaceholder}
|
||||||
bind:value={chat.composingMessage}
|
bind:value={chat.composingMessage}
|
||||||
on:enter={chat.sendNewMessage}
|
on:enter={chat.sendMessageFromComposer}
|
||||||
></input>
|
></input>
|
||||||
<button
|
<button
|
||||||
class="primary"
|
class="primary"
|
||||||
on:click={chat.sendNewMessage}
|
on:click={chat.sendMessageFromComposer}
|
||||||
toggle:disabled={chat.cannotSendMessage}
|
toggle:disabled={chat.cannotSendMessage}
|
||||||
>
|
>
|
||||||
<span class="icon">send</span>
|
<span class="icon">send</span>
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import { Chat, ChatMessage } from "../Model/chatModel";
|
import { Chat, ChatMessage } from "../../Model/chatModel";
|
||||||
|
|
||||||
import { translation } from "../translations";
|
import { isShowingChatTools } from "../../Model/model";
|
||||||
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
export function ThreadView(chat: Chat) {
|
export function ThreadView(chat: Chat) {
|
||||||
const messageConverter: React.ListItemConverter<ChatMessage> = (message) => {
|
const messageConverter: React.StateItemConverter<ChatMessage> = (message) => {
|
||||||
function resendMessage() {
|
function resendMessage() {
|
||||||
chat.resendMessage(message);
|
chat.resendMessage(message);
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,7 @@ export function ThreadView(chat: Chat) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const outboxMessageConverter: React.ListItemConverter<ChatMessage> = (
|
const outboxMessageConverter: React.StateItemConverter<ChatMessage> = (
|
||||||
message
|
message
|
||||||
) => {
|
) => {
|
||||||
function remove() {
|
function remove() {
|
||||||
@@ -116,24 +117,29 @@ export function ThreadView(chat: Chat) {
|
|||||||
></div>
|
></div>
|
||||||
);
|
);
|
||||||
const listWrapper = (
|
const listWrapper = (
|
||||||
<div class="flex-column gap">
|
<div class="thread-view flex-column gap">
|
||||||
{messageList}
|
{messageList}
|
||||||
{outboxList}
|
{outboxList}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
|
listWrapper.scrollTop = listWrapper.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToBottomIfAppropriate() {
|
||||||
const scrollFromBottom =
|
const scrollFromBottom =
|
||||||
listWrapper.scrollHeight -
|
listWrapper.scrollHeight -
|
||||||
(listWrapper.scrollTop + listWrapper.offsetHeight);
|
(listWrapper.scrollTop + listWrapper.offsetHeight);
|
||||||
if (scrollFromBottom > 400) return;
|
if (scrollFromBottom > 400) return;
|
||||||
|
|
||||||
listWrapper.scrollTop = listWrapper.scrollHeight;
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
chat.messages.handleAddition(scrollToBottom);
|
chat.messages.handleAddition(scrollToBottomIfAppropriate);
|
||||||
chat.outbox.handleAddition(scrollToBottom);
|
chat.outbox.handleAddition(scrollToBottomIfAppropriate);
|
||||||
setTimeout(() => (listWrapper.scrollTop = listWrapper.scrollHeight), 50);
|
isShowingChatTools.subscribe(() => scrollToBottom());
|
||||||
|
setTimeout(() => scrollToBottom(), 50);
|
||||||
|
|
||||||
return listWrapper;
|
return listWrapper;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { Chat, MessageObject } from "../../Model/chatModel";
|
||||||
|
|
||||||
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
|
export function AllObjectsView(
|
||||||
|
chat: Chat,
|
||||||
|
selectedObject: React.State<MessageObject | undefined>,
|
||||||
|
isShowingObjectModal: React.State<boolean>
|
||||||
|
) {
|
||||||
|
const objectConverter: React.StateItemConverter<MessageObject> = (
|
||||||
|
messageObject
|
||||||
|
) => {
|
||||||
|
function select() {
|
||||||
|
selectedObject.value = messageObject;
|
||||||
|
isShowingObjectModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button class="tile" on:click={select}>
|
||||||
|
<div>
|
||||||
|
<b>{messageObject.title}</b>
|
||||||
|
<span class="secondary">{messageObject.id}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = React.createProxyState([chat.objects], () =>
|
||||||
|
chat.objects.value.size == 0 ? (
|
||||||
|
<div class="flex-column width-100 height-100 align-center justify-center secondary">
|
||||||
|
{translation.noObjects}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
class="grid gap padding"
|
||||||
|
style="grid-template-columns: repeat(auto-fill, minmax(350px, 1fr))"
|
||||||
|
children:prepend={[chat.objects, objectConverter]}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return <div class="width-100 height-100" children:set={content}></div>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { Chat, MessageObject } from "../../Model/chatModel";
|
||||||
|
|
||||||
|
import { AllObjectsView } from "./allObjectsView";
|
||||||
|
import { NoteObjectsView } from "./noteObjectsView";
|
||||||
|
import { ObjectDetailModal } from "./objectDetailModal";
|
||||||
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
|
export const viewTypes = {
|
||||||
|
all: [translation.viewAll, "grid_view"],
|
||||||
|
notes: [translation.viewNotes, "sticky_note_2"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ChatObjectView(chat: Chat) {
|
||||||
|
// selection and modal
|
||||||
|
const isShowingObjectModal = new React.State(false);
|
||||||
|
const selectedObject = new React.State<MessageObject | undefined>(undefined);
|
||||||
|
const objectModal = React.createProxyState(
|
||||||
|
[chat.objects, selectedObject],
|
||||||
|
() => {
|
||||||
|
if (selectedObject.value == undefined) return <div></div>;
|
||||||
|
|
||||||
|
return ObjectDetailModal(
|
||||||
|
chat,
|
||||||
|
selectedObject.value,
|
||||||
|
isShowingObjectModal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// view type
|
||||||
|
const selectedViewType = new React.State<keyof typeof viewTypes>("all");
|
||||||
|
const mainView = React.createProxyState([selectedViewType], () => {
|
||||||
|
function getViewFunction() {
|
||||||
|
switch (selectedViewType.value) {
|
||||||
|
case "notes":
|
||||||
|
return NoteObjectsView;
|
||||||
|
default:
|
||||||
|
return AllObjectsView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getViewFunction()(chat, selectedObject, isShowingObjectModal);
|
||||||
|
});
|
||||||
|
|
||||||
|
// methods
|
||||||
|
function createObject() {
|
||||||
|
const newObject = chat.createObjectFromTitle(translation.untitledObject);
|
||||||
|
chat.addObjectAndSend(newObject);
|
||||||
|
selectedObject.value = newObject;
|
||||||
|
isShowingObjectModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="chat-object-view flex-column">
|
||||||
|
<div class="flex-row align-center border-bottom">
|
||||||
|
<button
|
||||||
|
class="primary height-100"
|
||||||
|
on:click={createObject}
|
||||||
|
aria-label={translation.createObject}
|
||||||
|
>
|
||||||
|
<span class="icon">add</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="padding-sm flex flex-row gap justify-center scroll-h width-100">
|
||||||
|
{...Object.keys(viewTypes).map((key: any) =>
|
||||||
|
ViewTypeToggle(key, selectedViewType)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="height-100"
|
||||||
|
on:click={createObject}
|
||||||
|
aria-label={translation.createObject}
|
||||||
|
>
|
||||||
|
<span class="icon">visibility</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="width-100 height-100 flex-1 scroll-h"
|
||||||
|
children:set={mainView}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div children:set={objectModal}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ViewTypeToggle(
|
||||||
|
key: keyof typeof viewTypes,
|
||||||
|
selection: React.State<keyof typeof viewTypes>
|
||||||
|
) {
|
||||||
|
const [label, icon] = viewTypes[key];
|
||||||
|
|
||||||
|
function select() {
|
||||||
|
selection.value = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSelected = React.createProxyState(
|
||||||
|
[selection],
|
||||||
|
() => selection.value == key
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button aria-label={label} on:click={select} toggle:selected={isSelected}>
|
||||||
|
<span class="icon">{icon}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { Chat, MessageObject } from "../../Model/chatModel";
|
||||||
|
|
||||||
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
|
export function NoteObjectsView(
|
||||||
|
chat: Chat,
|
||||||
|
selectedObject: React.State<MessageObject | undefined>,
|
||||||
|
isShowingObjectModal: React.State<boolean>
|
||||||
|
) {
|
||||||
|
const objectConverter: React.StateItemConverter<MessageObject> = (
|
||||||
|
messageObject
|
||||||
|
) => {
|
||||||
|
const latest = chat.getMostRecentContent(messageObject);
|
||||||
|
|
||||||
|
function select() {
|
||||||
|
selectedObject.value = messageObject;
|
||||||
|
isShowingObjectModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button class="tile" on:click={select}>
|
||||||
|
<div>
|
||||||
|
<b>{messageObject.title}</b>
|
||||||
|
<span class="secondary ellipsis">
|
||||||
|
{latest.noteContent!.split("\n")[0]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const notes = new React.ListState<MessageObject>();
|
||||||
|
chat.objects.subscribe(() => {
|
||||||
|
notes.clear();
|
||||||
|
chat.objects.value.forEach((messageObject) => {
|
||||||
|
const latest = chat.getMostRecentContent(messageObject);
|
||||||
|
if (!latest.noteContent) return;
|
||||||
|
|
||||||
|
notes.add(messageObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = React.createProxyState([chat.objects], () =>
|
||||||
|
notes.value.size == 0 ? (
|
||||||
|
<div class="flex-column width-100 height-100 align-center justify-center secondary">
|
||||||
|
{translation.noNotes}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
class="grid gap padding"
|
||||||
|
style="grid-template-columns: repeat(auto-fill, minmax(350px, 1fr))"
|
||||||
|
children:prepend={[notes, objectConverter]}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return <div class="width-100 height-100" children:set={content}></div>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { Chat, MessageObject } from "../../Model/chatModel";
|
||||||
|
|
||||||
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
|
export function ObjectDetailModal(
|
||||||
|
chat: Chat,
|
||||||
|
messageObject: MessageObject,
|
||||||
|
isPresented: React.State<boolean>
|
||||||
|
) {
|
||||||
|
// state
|
||||||
|
const editingTitle = new React.State(messageObject.title);
|
||||||
|
|
||||||
|
const selectedMessageObjectId = new React.State(
|
||||||
|
chat.getMostRecentContentId(messageObject)
|
||||||
|
);
|
||||||
|
const selectedMessageObject = React.createProxyState(
|
||||||
|
[selectedMessageObjectId],
|
||||||
|
() =>
|
||||||
|
chat.getObjectContentFromId(messageObject, selectedMessageObjectId.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
const didEditContent = new React.State(false);
|
||||||
|
const editingNoteContent = React.createProxyState(
|
||||||
|
[selectedMessageObject],
|
||||||
|
() => selectedMessageObject.value.noteContent ?? ""
|
||||||
|
);
|
||||||
|
React.bulkSubscribe(
|
||||||
|
[editingNoteContent],
|
||||||
|
() => (didEditContent.value = true)
|
||||||
|
);
|
||||||
|
|
||||||
|
// methods
|
||||||
|
function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (!e.metaKey && !e.ctrlKey) return;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case "s":
|
||||||
|
e.preventDefault();
|
||||||
|
saveAndClose();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
isPresented.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAndClose() {
|
||||||
|
messageObject.title = editingTitle.value;
|
||||||
|
|
||||||
|
if (didEditContent.value == true) {
|
||||||
|
chat.addObjectContent(messageObject, {
|
||||||
|
isoDateVersionCreated: new Date().toISOString(),
|
||||||
|
id: React.UUID(),
|
||||||
|
|
||||||
|
noteContent: editingNoteContent.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
chat.addObjectAndSend(messageObject);
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAndClose() {
|
||||||
|
chat.deleteObject(messageObject);
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="modal" toggle:open={isPresented} on:keydown={handleKeyDown}>
|
||||||
|
<div>
|
||||||
|
<main>
|
||||||
|
<h2>{messageObject.title}</h2>
|
||||||
|
<span class="secondary">{messageObject.id}</span>
|
||||||
|
|
||||||
|
<hr></hr>
|
||||||
|
|
||||||
|
<div class="flex-column gap">
|
||||||
|
<label class="tile">
|
||||||
|
<span class="icon">label</span>
|
||||||
|
<div>
|
||||||
|
<span>{translation.objectTitle}</span>
|
||||||
|
<input
|
||||||
|
autofocus
|
||||||
|
bind:value={editingTitle}
|
||||||
|
placeholder={translation.objectTitlePlaceholder}
|
||||||
|
></input>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<hr></hr>
|
||||||
|
|
||||||
|
<label class="tile">
|
||||||
|
<span class="icon">history</span>
|
||||||
|
<div>
|
||||||
|
<span>{translation.objectVersion}</span>
|
||||||
|
<select bind:value={selectedMessageObjectId}>
|
||||||
|
{...chat
|
||||||
|
.getSortedContents(messageObject)
|
||||||
|
.map((content) => (
|
||||||
|
<option value={content.id}>
|
||||||
|
{new Date(
|
||||||
|
content.isoDateVersionCreated
|
||||||
|
).toLocaleString()}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<span class="icon">arrow_drop_down</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="tile">
|
||||||
|
<span class="icon">sticky_note_2</span>
|
||||||
|
<div>
|
||||||
|
<span>{translation.noteContent}</span>
|
||||||
|
<textarea
|
||||||
|
rows="5"
|
||||||
|
bind:value={editingNoteContent}
|
||||||
|
placeholder={translation.noteContentPlaceholder}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<hr></hr>
|
||||||
|
|
||||||
|
<button class="danger width-input" on:click={deleteAndClose}>
|
||||||
|
{translation.deleteObject}
|
||||||
|
<span class="icon">delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="flex-row">
|
||||||
|
<button class="flex-1 width-100 danger" on:click={closeModal}>
|
||||||
|
{translation.discard}
|
||||||
|
</button>
|
||||||
|
<button class="flex-1 width-100 primary" on:click={saveAndClose}>
|
||||||
|
{translation.save}
|
||||||
|
<span class="icon">save</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,12 +7,12 @@ import {
|
|||||||
newChatName,
|
newChatName,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectedChat,
|
selectedChat,
|
||||||
} from "../Model/model";
|
} from "../../Model/model";
|
||||||
|
|
||||||
import { Chat } from "../Model/chatModel";
|
import { Chat } from "../../Model/chatModel";
|
||||||
import { translation } from "../translations";
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
const chatConverter: React.ListItemConverter<Chat> = (chat) => {
|
const chatConverter: React.StateItemConverter<Chat> = (chat) => {
|
||||||
function select() {
|
function select() {
|
||||||
selectChat(chat);
|
selectChat(chat);
|
||||||
}
|
}
|
||||||
@@ -13,11 +13,11 @@ import {
|
|||||||
requestMailbox,
|
requestMailbox,
|
||||||
resetAddressInput,
|
resetAddressInput,
|
||||||
serverAddressInput,
|
serverAddressInput,
|
||||||
} from "../Model/model";
|
} from "../../Model/model";
|
||||||
|
|
||||||
import { translation } from "../translations";
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
const addressConverter: React.ListItemConverter<string> = (address) => {
|
const addressConverter: React.StateItemConverter<string> = (address) => {
|
||||||
return <option>{address}</option>;
|
return <option>{address}</option>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import { senderName } from "../Model/model";
|
import { senderName } from "../../Model/model";
|
||||||
import { translation } from "../translations";
|
import { translation } from "../../translations";
|
||||||
|
|
||||||
export function PersonalSection() {
|
export function PersonalSection() {
|
||||||
return (
|
return (
|
||||||
@@ -1,3 +1,89 @@
|
|||||||
[unread] {
|
[unread] {
|
||||||
border-left: 10px solid var(--text-warn);
|
border-left: 10px solid var(--text-warn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TOOLS */
|
||||||
|
:root {
|
||||||
|
--thread-view-height-with-tools: 8rem;
|
||||||
|
--thread-view-width-with-tools: 20rem;
|
||||||
|
--footer-top-line: calc(var(--control-height) + var(--safe-area-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
#message-tab {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-tab-content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-object-view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[showingchattools] :is(.thread-view, .chat-object-view) {
|
||||||
|
overflow: scroll;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[showingchattools] header {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[showingchattools] .chat-object-view {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
top: var(--header-height);
|
||||||
|
bottom: calc(var(--thread-view-height-with-tools) + var(--footer-top-line));
|
||||||
|
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[showingchattools] .thread-view {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
height: var(--thread-view-height-with-tools);
|
||||||
|
|
||||||
|
top: unset;
|
||||||
|
bottom: var(--footer-top-line);
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) and (max-width: 1400px) {
|
||||||
|
main.split:has([showingchattools]) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.split:has([showingchattools]) #settings-tab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
[showingchattools] :is(.thread-view, .chat-object-view) {
|
||||||
|
top: var(--header-height);
|
||||||
|
bottom: var(--footer-top-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
[showingchattools] .chat-object-view {
|
||||||
|
left: 0px;
|
||||||
|
right: var(--thread-view-width-with-tools);
|
||||||
|
|
||||||
|
border-bottom: none;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[showingchattools] .thread-view {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
left: unset;
|
||||||
|
right: 0px;
|
||||||
|
|
||||||
|
height: unset;
|
||||||
|
width: var(--thread-view-width-with-tools);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+93
-18
@@ -1,9 +1,11 @@
|
|||||||
const englishTranslations = {
|
const englishTranslations = {
|
||||||
// general
|
// general
|
||||||
set: "Set",
|
set: "Set",
|
||||||
|
save: "Save",
|
||||||
back: "Back",
|
back: "Back",
|
||||||
undoChanges: "Undo changes",
|
undoChanges: "Undo changes",
|
||||||
close: "Close",
|
close: "Close",
|
||||||
|
discard: "Discard",
|
||||||
|
|
||||||
// overview
|
// overview
|
||||||
overview: "Overview",
|
overview: "Overview",
|
||||||
@@ -22,7 +24,7 @@ const englishTranslations = {
|
|||||||
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
||||||
|
|
||||||
connectToServer: "Connect",
|
connectToServer: "Connect",
|
||||||
disconnect: "Disonnect",
|
disconnect: "Disconnect",
|
||||||
|
|
||||||
mailbox: "Mailbox",
|
mailbox: "Mailbox",
|
||||||
requestMailbox: "Enable",
|
requestMailbox: "Enable",
|
||||||
@@ -34,7 +36,7 @@ const englishTranslations = {
|
|||||||
primaryChannelPlaceholder: "my-channel",
|
primaryChannelPlaceholder: "my-channel",
|
||||||
addChat: "Add",
|
addChat: "Add",
|
||||||
|
|
||||||
// messages
|
// chat
|
||||||
showChatOptions: "show chat options",
|
showChatOptions: "show chat options",
|
||||||
configureChatTitle: "Configure Chat",
|
configureChatTitle: "Configure Chat",
|
||||||
|
|
||||||
@@ -47,8 +49,9 @@ const englishTranslations = {
|
|||||||
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
||||||
showKey: "Show key",
|
showKey: "Show key",
|
||||||
|
|
||||||
removeChat: "Remove chat",
|
clearObjects: "Delete all objects",
|
||||||
clearChatMessages: "Clear chat messages",
|
clearChatMessages: "Clear chat messages",
|
||||||
|
removeChat: "Remove chat",
|
||||||
|
|
||||||
messageInOutbox: "Pending",
|
messageInOutbox: "Pending",
|
||||||
|
|
||||||
@@ -57,10 +60,32 @@ const englishTranslations = {
|
|||||||
composerPlaceholder: "Type a message...",
|
composerPlaceholder: "Type a message...",
|
||||||
sendMessage: "Send",
|
sendMessage: "Send",
|
||||||
|
|
||||||
|
// messages
|
||||||
resendMessage: "Resend message",
|
resendMessage: "Resend message",
|
||||||
decryptMessage: "Decrypt message",
|
decryptMessage: "Decrypt message",
|
||||||
copyMessage: "Copy message",
|
copyMessage: "Copy message",
|
||||||
deleteMessage: "Delete message",
|
deleteMessage: "Delete message",
|
||||||
|
|
||||||
|
// objects
|
||||||
|
showObjects: "show objects",
|
||||||
|
createObject: "create object",
|
||||||
|
untitledObject: "Untitled Object",
|
||||||
|
|
||||||
|
viewAll: "All",
|
||||||
|
viewNotes: "Notes",
|
||||||
|
|
||||||
|
noObjects: "No objects",
|
||||||
|
noNotes: "No notes",
|
||||||
|
|
||||||
|
objectTitle: "Object title",
|
||||||
|
objectTitlePlaceholder: "My object",
|
||||||
|
|
||||||
|
objectVersion: "Object version",
|
||||||
|
noteContent: "Note",
|
||||||
|
noteContentPlaceholder: "Take a note...",
|
||||||
|
|
||||||
|
resendObjects: "Resend all objects",
|
||||||
|
deleteObject: "Delete object",
|
||||||
};
|
};
|
||||||
|
|
||||||
const allTranslations: { [key: string]: typeof englishTranslations } = {
|
const allTranslations: { [key: string]: typeof englishTranslations } = {
|
||||||
@@ -68,14 +93,16 @@ const allTranslations: { [key: string]: typeof englishTranslations } = {
|
|||||||
es: {
|
es: {
|
||||||
// general
|
// general
|
||||||
set: "Guardar",
|
set: "Guardar",
|
||||||
|
save: "Guardar",
|
||||||
back: "Atrás",
|
back: "Atrás",
|
||||||
undoChanges: "Deshacer",
|
undoChanges: "Deshacer",
|
||||||
close: "Cerrar",
|
close: "Cerrar",
|
||||||
|
discard: "Descartar",
|
||||||
|
|
||||||
// overview
|
// overview
|
||||||
overview: "Resumen",
|
overview: "Resumen",
|
||||||
|
|
||||||
connection: "Conexion",
|
connection: "Conexión",
|
||||||
chats: "Chats",
|
chats: "Chats",
|
||||||
|
|
||||||
yourName: "Tu nombre",
|
yourName: "Tu nombre",
|
||||||
@@ -85,7 +112,7 @@ const allTranslations: { [key: string]: typeof englishTranslations } = {
|
|||||||
encryptionUnavailableMessage:
|
encryptionUnavailableMessage:
|
||||||
"Obtén esta aplicación a traves de HTTPS o continúa sin cifrado",
|
"Obtén esta aplicación a traves de HTTPS o continúa sin cifrado",
|
||||||
|
|
||||||
serverAddress: "Direccion del servidor",
|
serverAddress: "Dirección del servidor",
|
||||||
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
||||||
|
|
||||||
connectToServer: "Conectar",
|
connectToServer: "Conectar",
|
||||||
@@ -101,40 +128,65 @@ const allTranslations: { [key: string]: typeof englishTranslations } = {
|
|||||||
primaryChannelPlaceholder: "mi-canal",
|
primaryChannelPlaceholder: "mi-canal",
|
||||||
addChat: "Añadir",
|
addChat: "Añadir",
|
||||||
|
|
||||||
// messages
|
// chat
|
||||||
showChatOptions: "Mostrar opciones del chat",
|
showChatOptions: "Mostrar opciones del chat",
|
||||||
configureChatTitle: "Configurar chat",
|
configureChatTitle: "Configurar chat",
|
||||||
|
|
||||||
secondaryChannel: "Canal segundario",
|
secondaryChannel: "Canal secundario",
|
||||||
secondaryChannelPlaceholder: "Añadir canal segundario",
|
secondaryChannelPlaceholder: "Añadir canal secundario",
|
||||||
addSecondaryChannel: "Añadir canal segundario",
|
addSecondaryChannel: "Añadir canal secundario",
|
||||||
removeSecondaryChannel: "Eliminar canal segundario",
|
removeSecondaryChannel: "Eliminar canal secundario",
|
||||||
|
|
||||||
encryptionKey: "Clave de cifrado",
|
encryptionKey: "Clave de cifrado",
|
||||||
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
||||||
showKey: "Mostrar clave",
|
showKey: "Mostrar clave",
|
||||||
|
|
||||||
|
clearObjects: "Eliminar todos los objetos",
|
||||||
|
clearChatMessages: "Eliminar todos los mensajes",
|
||||||
removeChat: "Eliminar chat",
|
removeChat: "Eliminar chat",
|
||||||
clearChatMessages: "Eliminar todos mensajes",
|
|
||||||
|
|
||||||
noChatSelected: "Selecciona un chat",
|
|
||||||
|
|
||||||
messageInOutbox: "Pendiente",
|
messageInOutbox: "Pendiente",
|
||||||
|
|
||||||
|
noChatSelected: "Selecciona un chat",
|
||||||
|
|
||||||
composerPlaceholder: "Escribe un mensaje...",
|
composerPlaceholder: "Escribe un mensaje...",
|
||||||
sendMessage: "Enviar",
|
sendMessage: "Enviar",
|
||||||
|
|
||||||
resendMessage: "Enviar de nuevo",
|
// messages
|
||||||
|
resendMessage: "Reenviar mensaje",
|
||||||
decryptMessage: "Descifrar mensaje",
|
decryptMessage: "Descifrar mensaje",
|
||||||
copyMessage: "Copiar mensaje",
|
copyMessage: "Copiar mensaje",
|
||||||
deleteMessage: "Eliminar este mensaje",
|
deleteMessage: "Eliminar este mensaje",
|
||||||
|
|
||||||
|
// objects
|
||||||
|
showObjects: "mostrar objetos",
|
||||||
|
createObject: "añadir nuevo objeto",
|
||||||
|
untitledObject: "Sin Título",
|
||||||
|
|
||||||
|
viewAll: "Todos",
|
||||||
|
viewNotes: "Notas",
|
||||||
|
|
||||||
|
noObjects: "Sin objetos",
|
||||||
|
noNotes: "Sin notas",
|
||||||
|
|
||||||
|
objectTitle: "Título",
|
||||||
|
objectTitlePlaceholder: "Mi objeto",
|
||||||
|
|
||||||
|
objectVersion: "Version del objeto",
|
||||||
|
noteContent: "Nota",
|
||||||
|
noteContentPlaceholder: "Toma nota...",
|
||||||
|
|
||||||
|
resendObjects: "Reenviar todos objetos",
|
||||||
|
deleteObject: "Eliminar objeto",
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
// general
|
// general
|
||||||
set: "OK",
|
set: "OK",
|
||||||
|
save: "Sichern",
|
||||||
back: "Zurück",
|
back: "Zurück",
|
||||||
undoChanges: "Änderungen verwerfen",
|
undoChanges: "Änderungen verwerfen",
|
||||||
close: "Schließen",
|
close: "Schließen",
|
||||||
|
discard: "Verwerfen",
|
||||||
|
|
||||||
// overview
|
// overview
|
||||||
overview: "Übersicht",
|
overview: "Übersicht",
|
||||||
@@ -165,7 +217,7 @@ const allTranslations: { [key: string]: typeof englishTranslations } = {
|
|||||||
primaryChannelPlaceholder: "mein-kanal",
|
primaryChannelPlaceholder: "mein-kanal",
|
||||||
addChat: "Hinzufügen",
|
addChat: "Hinzufügen",
|
||||||
|
|
||||||
// messages
|
// chat
|
||||||
showChatOptions: "Chatoptionen einblenden",
|
showChatOptions: "Chatoptionen einblenden",
|
||||||
configureChatTitle: "Chat konfigurieren",
|
configureChatTitle: "Chat konfigurieren",
|
||||||
|
|
||||||
@@ -178,20 +230,43 @@ const allTranslations: { [key: string]: typeof englishTranslations } = {
|
|||||||
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
encryptionKeyPlaceholder: "n10d2482dg283hg",
|
||||||
showKey: "Schlüssel anzeigen",
|
showKey: "Schlüssel anzeigen",
|
||||||
|
|
||||||
removeChat: "Chat löschen",
|
clearObjects: "Alle Objekte löschen",
|
||||||
clearChatMessages: "Nachrichtenverlauf leeren",
|
clearChatMessages: "Nachrichtenverlauf leeren",
|
||||||
|
removeChat: "Chat löschen",
|
||||||
noChatSelected: "Kein Chat ausgewählt",
|
|
||||||
|
|
||||||
messageInOutbox: "Ausstehend",
|
messageInOutbox: "Ausstehend",
|
||||||
|
|
||||||
|
noChatSelected: "Kein Chat ausgewählt",
|
||||||
|
|
||||||
composerPlaceholder: "Neue Nachricht...",
|
composerPlaceholder: "Neue Nachricht...",
|
||||||
sendMessage: "Senden",
|
sendMessage: "Senden",
|
||||||
|
|
||||||
|
// messages
|
||||||
resendMessage: "Erneut senden",
|
resendMessage: "Erneut senden",
|
||||||
decryptMessage: "Nachricht entschlüsseln",
|
decryptMessage: "Nachricht entschlüsseln",
|
||||||
copyMessage: "Nachricht kopieren",
|
copyMessage: "Nachricht kopieren",
|
||||||
deleteMessage: "Nachricht löschen",
|
deleteMessage: "Nachricht löschen",
|
||||||
|
|
||||||
|
// objects
|
||||||
|
showObjects: "Objekte anzeigen",
|
||||||
|
createObject: "Neues Objekt erstellen",
|
||||||
|
untitledObject: "Unbenannt",
|
||||||
|
|
||||||
|
viewAll: "Alle",
|
||||||
|
viewNotes: "Notizen",
|
||||||
|
|
||||||
|
noObjects: "Keine Objekte",
|
||||||
|
noNotes: "Keine Notizen",
|
||||||
|
|
||||||
|
objectTitle: "Titel",
|
||||||
|
objectTitlePlaceholder: "Mein Objekt",
|
||||||
|
|
||||||
|
objectVersion: "Version des Objekts",
|
||||||
|
noteContent: "Notiz",
|
||||||
|
noteContentPlaceholder: "Notiz eingeben...",
|
||||||
|
|
||||||
|
resendObjects: "Objekte erneut senden",
|
||||||
|
deleteObject: "Objekt löschen",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,18 @@ export const storageKeys = {
|
|||||||
return id + "messages";
|
return id + "messages";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
objects(id: string): string {
|
||||||
|
return id + "items";
|
||||||
|
},
|
||||||
|
|
||||||
outbox(id: string): string {
|
outbox(id: string): string {
|
||||||
return id + "outbox";
|
return id + "outbox";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
itemOutbox(id: string): string {
|
||||||
|
return id + "item-outbox";
|
||||||
|
},
|
||||||
|
|
||||||
composingMessage(id: string): string {
|
composingMessage(id: string): string {
|
||||||
return id + "composing-message";
|
return id + "composing-message";
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user