add MessageInfoModal
This commit is contained in:
Vendored
+32
-9
@@ -422,24 +422,44 @@ body[showing-chat] #chat-page-wrapper {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: var(--max-content-width);
|
max-width: var(--max-content-width);
|
||||||
}
|
}
|
||||||
.message-bubble > div {
|
.message-bubble .main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
background-color: var(--surface);
|
background-color: var(--surface);
|
||||||
padding: var(--control-padding);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: var(--max-input-width);
|
max-width: var(--max-input-width);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.message-bubble .timestamp,
|
.message-bubble .main .text-container {
|
||||||
.message-bubble .sender-name {
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.message-bubble .main .button-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
.message-bubble .main button {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0px;
|
||||||
|
width: var(--control-height);
|
||||||
|
--surface: transparent;
|
||||||
|
}
|
||||||
|
.message-bubble .main .text-container {
|
||||||
|
padding: var(--control-padding);
|
||||||
|
}
|
||||||
|
.message-bubble .main .timestamp,
|
||||||
|
.message-bubble .main .sender-name {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
.message-bubble .sender-name {
|
.message-bubble .main .sender-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.message-bubble .body {
|
.message-bubble .main .body {
|
||||||
word-break: break-all;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
.message-bubble[sentbyuser] {
|
.message-bubble[sentbyuser] {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -447,7 +467,10 @@ body[showing-chat] #chat-page-wrapper {
|
|||||||
.message-bubble[sentbyuser] {
|
.message-bubble[sentbyuser] {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
.message-bubble[sentbyuser] span {
|
.message-bubble[sentbyuser] .main {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
.message-bubble[sentbyuser] .main span {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+153
-36
@@ -125,6 +125,49 @@
|
|||||||
return json;
|
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).forEach((handler) => handler(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) {
|
||||||
|
if (!this.removalHandlers.has(item))
|
||||||
|
this.removalHandlers.set(item, /* @__PURE__ */ new Set());
|
||||||
|
this.removalHandlers.get(item).add(handler);
|
||||||
|
}
|
||||||
|
// stringification
|
||||||
|
toString() {
|
||||||
|
const array = [...this.value.entries()];
|
||||||
|
const json = JSON.stringify(array);
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
};
|
||||||
function createProxyState(statesToSubscibe, fn) {
|
function createProxyState(statesToSubscibe, fn) {
|
||||||
const proxyState = new State(fn());
|
const proxyState = new State(fn());
|
||||||
statesToSubscibe.forEach(
|
statesToSubscibe.forEach(
|
||||||
@@ -223,6 +266,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
element.setAttribute(attributename, value);
|
element.setAttribute(attributename, value);
|
||||||
@@ -604,7 +648,6 @@
|
|||||||
const chatMessage = _ChatModel.parseMessage(body);
|
const chatMessage = _ChatModel.parseMessage(body);
|
||||||
if (chatMessage == null) return;
|
if (chatMessage == null) return;
|
||||||
await this.decryptMessage(chatMessage);
|
await this.decryptMessage(chatMessage);
|
||||||
this.addMessage(chatMessage);
|
|
||||||
};
|
};
|
||||||
decryptMessage = async (chatMessage) => {
|
decryptMessage = async (chatMessage) => {
|
||||||
const decryptedBody = await decryptString(
|
const decryptedBody = await decryptString(
|
||||||
@@ -612,6 +655,7 @@
|
|||||||
this.info.encryptionKey
|
this.info.encryptionKey
|
||||||
);
|
);
|
||||||
chatMessage.body = decryptedBody;
|
chatMessage.body = decryptedBody;
|
||||||
|
this.addMessage(chatMessage);
|
||||||
};
|
};
|
||||||
setMessageHandler = (handler) => {
|
setMessageHandler = (handler) => {
|
||||||
this.chatMessageHandler = handler;
|
this.chatMessageHandler = handler;
|
||||||
@@ -744,20 +788,37 @@
|
|||||||
|
|
||||||
// src/Model/chatMessageViewModel.ts
|
// src/Model/chatMessageViewModel.ts
|
||||||
var ChatMessageViewModel = class {
|
var ChatMessageViewModel = class {
|
||||||
chatModel;
|
chatViewModel;
|
||||||
// data
|
// data
|
||||||
|
chatMessage;
|
||||||
channel;
|
channel;
|
||||||
sender;
|
sender;
|
||||||
dateSent;
|
dateSent;
|
||||||
body = new State("");
|
body = new State("");
|
||||||
sentByUser;
|
sentByUser;
|
||||||
constructor(chatModel, chatMessage, sentByUser) {
|
// methods
|
||||||
this.chatModel = chatModel;
|
copyMessage = () => {
|
||||||
this.channel = chatMessage.channel;
|
navigator.clipboard.writeText(this.body.value);
|
||||||
this.sender = chatMessage.sender;
|
};
|
||||||
this.dateSent = new Date(chatMessage.dateSent).toLocaleString();
|
resendMessage = () => {
|
||||||
this.body.value = chatMessage.body;
|
this.chatViewModel.sendMessageFromBody(this.body.value);
|
||||||
|
};
|
||||||
|
decryptMessage = () => {
|
||||||
|
this.chatViewModel.decryptMessage(this);
|
||||||
|
};
|
||||||
|
// load
|
||||||
|
loadData = () => {
|
||||||
|
this.channel = this.chatMessage.channel;
|
||||||
|
this.sender = this.chatMessage.sender;
|
||||||
|
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
|
||||||
|
this.body.value = this.chatMessage.body;
|
||||||
|
};
|
||||||
|
// init
|
||||||
|
constructor(chatViewModel, chatMessage, sentByUser) {
|
||||||
|
this.chatViewModel = chatViewModel;
|
||||||
|
this.chatMessage = chatMessage;
|
||||||
this.sentByUser = sentByUser;
|
this.sentByUser = sentByUser;
|
||||||
|
this.loadData();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -783,7 +844,7 @@
|
|||||||
selectedPage = new State(
|
selectedPage = new State(
|
||||||
"messages" /* Messages */
|
"messages" /* Messages */
|
||||||
);
|
);
|
||||||
chatMessageViewModels = new ListState();
|
chatMessageViewModels = new MapState();
|
||||||
composingMessage = new State("");
|
composingMessage = new State("");
|
||||||
// guards
|
// guards
|
||||||
cannotSetPrimaryChannel = createProxyState(
|
cannotSetPrimaryChannel = createProxyState(
|
||||||
@@ -810,11 +871,16 @@
|
|||||||
// add
|
// add
|
||||||
addChatMessage = (chatMessage) => {
|
addChatMessage = (chatMessage) => {
|
||||||
const chatMessageModel = new ChatMessageViewModel(
|
const chatMessageModel = new ChatMessageViewModel(
|
||||||
this.chatModel,
|
this,
|
||||||
chatMessage,
|
chatMessage,
|
||||||
chatMessage.sender == this.settingsViewModel.username.value
|
chatMessage.sender == this.settingsViewModel.username.value
|
||||||
);
|
);
|
||||||
this.chatMessageViewModels.add(chatMessageModel);
|
const existingChatMessageViewModel = this.chatMessageViewModels.value.get(chatMessage.id);
|
||||||
|
if (existingChatMessageViewModel != void 0) {
|
||||||
|
existingChatMessageViewModel.body.value = chatMessage.body;
|
||||||
|
} else {
|
||||||
|
this.chatMessageViewModels.set(chatMessage.id, chatMessageModel);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// settings
|
// settings
|
||||||
setPrimaryChannel = () => {
|
setPrimaryChannel = () => {
|
||||||
@@ -852,9 +918,16 @@
|
|||||||
};
|
};
|
||||||
// messaging
|
// messaging
|
||||||
sendMessage = () => {
|
sendMessage = () => {
|
||||||
this.chatModel.sendMessage(this.composingMessage.value);
|
this.sendMessageFromBody(this.composingMessage.value);
|
||||||
this.composingMessage.value = "";
|
this.composingMessage.value = "";
|
||||||
};
|
};
|
||||||
|
sendMessageFromBody = (body) => {
|
||||||
|
this.chatModel.sendMessage(body);
|
||||||
|
};
|
||||||
|
decryptMessage = async (messageViewModel) => {
|
||||||
|
await this.chatModel.decryptMessage(messageViewModel.chatMessage);
|
||||||
|
messageViewModel.loadData();
|
||||||
|
};
|
||||||
// restore
|
// restore
|
||||||
restorePageSelection = () => {
|
restorePageSelection = () => {
|
||||||
const path = storageKeys.chatLastUsedPage(this.chatModel.id);
|
const path = storageKeys.chatLastUsedPage(this.chatModel.id);
|
||||||
@@ -991,31 +1064,10 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/View/Components/chatMessage.tsx
|
|
||||||
function ChatMessage(chatMessageViewModel) {
|
|
||||||
return /* @__PURE__ */ createElement(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
class: "message-bubble",
|
|
||||||
"toggle:sentbyuser": chatMessageViewModel.sentByUser
|
|
||||||
},
|
|
||||||
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
class: "body",
|
|
||||||
"subscribe:innerText": chatMessageViewModel.body
|
|
||||||
}
|
|
||||||
), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, chatMessageViewModel.dateSent))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
var ChatMessageViewModelToView = (chatMessageViewModel) => {
|
|
||||||
return ChatMessage(chatMessageViewModel);
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/View/translations.ts
|
// src/View/translations.ts
|
||||||
var englishTranslations = {
|
var englishTranslations = {
|
||||||
general: {
|
general: {
|
||||||
closeButtonAudioLabel: "Close",
|
closeButton: "Close",
|
||||||
deleteItemButtonAudioLabel: "delete item",
|
deleteItemButtonAudioLabel: "delete item",
|
||||||
setButton: "Set"
|
setButton: "Set"
|
||||||
},
|
},
|
||||||
@@ -1089,7 +1141,17 @@
|
|||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
composerInputPlaceholder: "Type a message...",
|
composerInputPlaceholder: "Type a message...",
|
||||||
sendMessageButtonAudioLabel: "send message"
|
sendMessageButtonAudioLabel: "send message",
|
||||||
|
showMessageInfoButtonAudioLabel: "show message info",
|
||||||
|
messageInfoHeadline: "Message Info",
|
||||||
|
sentBy: "Sent by",
|
||||||
|
timeSent: "Time sent",
|
||||||
|
channel: "Channel",
|
||||||
|
messageContent: "Message content",
|
||||||
|
copyMessageButton: "Copy message",
|
||||||
|
resendMessageButton: "Resend message",
|
||||||
|
decryptMessageButton: "Decrypt message",
|
||||||
|
deleteMessageButton: "Delete message"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1099,6 +1161,61 @@
|
|||||||
var language = navigator.language.substring(0, 2);
|
var language = navigator.language.substring(0, 2);
|
||||||
var translations = allTranslations[language] || allTranslations.en;
|
var translations = allTranslations[language] || allTranslations.en;
|
||||||
|
|
||||||
|
// src/View/Components/chatMessageInfoModal.tsx
|
||||||
|
function ChatMessageInfoModal(chatMessageViewModel, isOpen) {
|
||||||
|
function closeModal() {
|
||||||
|
isOpen.value = false;
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.sentBy), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.sender))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.timeSent), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.dateSent))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.channel), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.channel))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.messageContent), /* @__PURE__ */ createElement(
|
||||||
|
"b",
|
||||||
|
{
|
||||||
|
class: "break-word",
|
||||||
|
"subscribe:innerText": chatMessageViewModel.body
|
||||||
|
}
|
||||||
|
)))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")))), /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
"on:click": closeModal,
|
||||||
|
"aria-label": translations.general.closeButton
|
||||||
|
},
|
||||||
|
translations.general.closeButton,
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/View/Components/chatMessage.tsx
|
||||||
|
function ChatMessage(chatMessageViewModel) {
|
||||||
|
const isInfoModalOpen = new State(false);
|
||||||
|
function openModal() {
|
||||||
|
isInfoModalOpen.value = true;
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
class: "message-bubble",
|
||||||
|
"toggle:sentbyuser": chatMessageViewModel.sentByUser
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ createElement("div", { class: "main" }, /* @__PURE__ */ createElement("div", { class: "text-container" }, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: "body",
|
||||||
|
"subscribe:innerText": chatMessageViewModel.body
|
||||||
|
}
|
||||||
|
), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, chatMessageViewModel.dateSent)), /* @__PURE__ */ createElement("div", { class: "button-container" }, /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
"on:click": openModal,
|
||||||
|
"aria-label": translations.chatPage.message.showMessageInfoButtonAudioLabel
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "info")
|
||||||
|
))),
|
||||||
|
ChatMessageInfoModal(chatMessageViewModel, isInfoModalOpen)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var ChatMessageViewModelToView = (chatMessageViewModel) => {
|
||||||
|
return ChatMessage(chatMessageViewModel);
|
||||||
|
};
|
||||||
|
|
||||||
// src/View/ChatPages/messagePage.tsx
|
// src/View/ChatPages/messagePage.tsx
|
||||||
function MessagePage(chatViewModel) {
|
function MessagePage(chatViewModel) {
|
||||||
const messageContainer = /* @__PURE__ */ createElement(
|
const messageContainer = /* @__PURE__ */ createElement(
|
||||||
@@ -1369,7 +1486,7 @@
|
|||||||
previousAddressConverter
|
previousAddressConverter
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
)), /* @__PURE__ */ createElement("button", { "on:click": connectionViewModel2.hideConnectionModal }, translations.general.closeButtonAudioLabel, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
)), /* @__PURE__ */ createElement("button", { "on:click": connectionViewModel2.hideConnectionModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,27 +4,48 @@ import * as React from "bloatless-react";
|
|||||||
|
|
||||||
import { ChatMessage, ChatModel } from "./chatModel";
|
import { ChatMessage, ChatModel } from "./chatModel";
|
||||||
|
|
||||||
|
import ChatViewModel from "../ViewModel/chatViewModel";
|
||||||
|
|
||||||
export default class ChatMessageViewModel {
|
export default class ChatMessageViewModel {
|
||||||
chatModel: ChatModel;
|
chatViewModel: ChatViewModel;
|
||||||
|
|
||||||
// data
|
// data
|
||||||
|
chatMessage: ChatMessage;
|
||||||
channel: string;
|
channel: string;
|
||||||
sender: string;
|
sender: string;
|
||||||
dateSent: string;
|
dateSent: string;
|
||||||
body: React.State<string> = new React.State("");
|
body: React.State<string> = new React.State("");
|
||||||
sentByUser: boolean;
|
sentByUser: boolean;
|
||||||
|
|
||||||
|
// methods
|
||||||
|
copyMessage = (): void => {
|
||||||
|
navigator.clipboard.writeText(this.body.value);
|
||||||
|
};
|
||||||
|
resendMessage = (): void => {
|
||||||
|
this.chatViewModel.sendMessageFromBody(this.body.value);
|
||||||
|
};
|
||||||
|
decryptMessage = (): void => {
|
||||||
|
this.chatViewModel.decryptMessage(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
// load
|
||||||
|
loadData = (): void => {
|
||||||
|
this.channel = this.chatMessage.channel;
|
||||||
|
this.sender = this.chatMessage.sender;
|
||||||
|
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
|
||||||
|
this.body.value = this.chatMessage.body;
|
||||||
|
};
|
||||||
|
|
||||||
|
// init
|
||||||
constructor(
|
constructor(
|
||||||
chatModel: ChatModel,
|
chatViewModel: ChatViewModel,
|
||||||
chatMessage: ChatMessage,
|
chatMessage: ChatMessage,
|
||||||
sentByUser: boolean
|
sentByUser: boolean
|
||||||
) {
|
) {
|
||||||
this.chatModel = chatModel;
|
this.chatViewModel = chatViewModel;
|
||||||
|
|
||||||
this.channel = chatMessage.channel;
|
this.chatMessage = chatMessage;
|
||||||
this.sender = chatMessage.sender;
|
|
||||||
this.dateSent = new Date(chatMessage.dateSent).toLocaleString();
|
|
||||||
this.body.value = chatMessage.body;
|
|
||||||
this.sentByUser = sentByUser;
|
this.sentByUser = sentByUser;
|
||||||
|
this.loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ export class ChatModel {
|
|||||||
if (chatMessage == null) return;
|
if (chatMessage == null) return;
|
||||||
|
|
||||||
await this.decryptMessage(chatMessage);
|
await this.decryptMessage(chatMessage);
|
||||||
this.addMessage(chatMessage);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
decryptMessage = async (chatMessage: ChatMessage): Promise<void> => {
|
decryptMessage = async (chatMessage: ChatMessage): Promise<void> => {
|
||||||
@@ -205,6 +204,7 @@ export class ChatModel {
|
|||||||
this.info.encryptionKey
|
this.info.encryptionKey
|
||||||
);
|
);
|
||||||
chatMessage.body = decryptedBody;
|
chatMessage.body = decryptedBody;
|
||||||
|
this.addMessage(chatMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => {
|
setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
|
|
||||||
#message-page #composer div {
|
#message-page #composer div {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
@@ -79,29 +78,57 @@
|
|||||||
max-width: var(--max-content-width);
|
max-width: var(--max-content-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble > div {
|
.message-bubble .main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
|
|
||||||
background-color: var(--surface);
|
background-color: var(--surface);
|
||||||
padding: var(--control-padding);
|
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: var(--max-input-width);
|
max-width: var(--max-input-width);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble .timestamp,
|
.message-bubble .main .text-container {
|
||||||
.message-bubble .sender-name {
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble .main .button-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble .main button {
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding: 0px;
|
||||||
|
width: var(--control-height);
|
||||||
|
|
||||||
|
--surface: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble .main .text-container {
|
||||||
|
padding: var(--control-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble .main .timestamp,
|
||||||
|
.message-bubble .main .sender-name {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble .sender-name {
|
.message-bubble .main .sender-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble .body {
|
.message-bubble .main .body {
|
||||||
word-break: break-all;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble[sentbyuser] {
|
.message-bubble[sentbyuser] {
|
||||||
@@ -112,6 +139,10 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble[sentbyuser] span {
|
.message-bubble[sentbyuser] .main {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble[sentbyuser] .main span {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,46 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { ChatMessageInfoModal } from "./chatMessageInfoModal";
|
||||||
import ChatMessageViewModel from "../../Model/chatMessageViewModel";
|
import ChatMessageViewModel from "../../Model/chatMessageViewModel";
|
||||||
|
import { translations } from "../translations";
|
||||||
|
|
||||||
export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
|
export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
|
||||||
|
const isInfoModalOpen = new React.State(false);
|
||||||
|
function openModal() {
|
||||||
|
isInfoModalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="message-bubble"
|
class="message-bubble"
|
||||||
toggle:sentbyuser={chatMessageViewModel.sentByUser}
|
toggle:sentbyuser={chatMessageViewModel.sentByUser}
|
||||||
>
|
>
|
||||||
<div>
|
<div class="main">
|
||||||
<span class="sender-name ellipsis">{chatMessageViewModel.sender}</span>
|
<div class="text-container">
|
||||||
|
<span class="sender-name ellipsis">
|
||||||
|
{chatMessageViewModel.sender}
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
class="body"
|
class="body"
|
||||||
subscribe:innerText={chatMessageViewModel.body}
|
subscribe:innerText={chatMessageViewModel.body}
|
||||||
></span>
|
></span>
|
||||||
<span class="timestamp ellipsis">{chatMessageViewModel.dateSent}</span>
|
<span class="timestamp ellipsis">
|
||||||
|
{chatMessageViewModel.dateSent}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="button-container">
|
||||||
|
<button
|
||||||
|
on:click={openModal}
|
||||||
|
aria-label={
|
||||||
|
translations.chatPage.message.showMessageInfoButtonAudioLabel
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span class="icon">info</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ChatMessageInfoModal(chatMessageViewModel, isInfoModalOpen)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import ChatMessageViewModel from "../../Model/chatMessageViewModel";
|
||||||
|
import { translations } from "../translations";
|
||||||
|
|
||||||
|
export function ChatMessageInfoModal(
|
||||||
|
chatMessageViewModel: ChatMessageViewModel,
|
||||||
|
isOpen: React.State<boolean>
|
||||||
|
) {
|
||||||
|
function closeModal() {
|
||||||
|
isOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="modal" toggle:open={isOpen}>
|
||||||
|
<div>
|
||||||
|
<main>
|
||||||
|
<h2>{translations.chatPage.message.messageInfoHeadline}</h2>
|
||||||
|
|
||||||
|
<div class="flex-column gap">
|
||||||
|
<div class="tile">
|
||||||
|
<span class="icon">account_circle</span>
|
||||||
|
<div>
|
||||||
|
<span>{translations.chatPage.message.sentBy}</span>
|
||||||
|
<b class="break-word">{chatMessageViewModel.sender}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tile">
|
||||||
|
<span class="icon">schedule</span>
|
||||||
|
<div>
|
||||||
|
<span>{translations.chatPage.message.timeSent}</span>
|
||||||
|
<b class="break-word">{chatMessageViewModel.dateSent}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tile">
|
||||||
|
<span class="icon">forum</span>
|
||||||
|
<div>
|
||||||
|
<span>{translations.chatPage.message.channel}</span>
|
||||||
|
<b class="break-word">{chatMessageViewModel.channel}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tile">
|
||||||
|
<span class="icon">description</span>
|
||||||
|
<div>
|
||||||
|
<span>{translations.chatPage.message.messageContent}</span>
|
||||||
|
<b
|
||||||
|
class="break-word"
|
||||||
|
subscribe:innerText={chatMessageViewModel.body}
|
||||||
|
></b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr></hr>
|
||||||
|
|
||||||
|
<div class="flex-column gap">
|
||||||
|
<button on:click={chatMessageViewModel.copyMessage}>
|
||||||
|
{translations.chatPage.message.copyMessageButton}
|
||||||
|
<span class="icon">content_copy</span>
|
||||||
|
</button>
|
||||||
|
<button on:click={chatMessageViewModel.resendMessage}>
|
||||||
|
{translations.chatPage.message.resendMessageButton}
|
||||||
|
<span class="icon">redo</span>
|
||||||
|
</button>
|
||||||
|
<button on:click={chatMessageViewModel.decryptMessage}>
|
||||||
|
{translations.chatPage.message.decryptMessageButton}
|
||||||
|
<span class="icon">key</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<button
|
||||||
|
on:click={closeModal}
|
||||||
|
aria-label={translations.general.closeButton}
|
||||||
|
>
|
||||||
|
{translations.general.closeButton}
|
||||||
|
<span class="icon">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ export function ConnectionModal(connectionViewModel: ConnectionViewModel) {
|
|||||||
></div>
|
></div>
|
||||||
</main>
|
</main>
|
||||||
<button on:click={connectionViewModel.hideConnectionModal}>
|
<button on:click={connectionViewModel.hideConnectionModal}>
|
||||||
{translations.general.closeButtonAudioLabel}
|
{translations.general.closeButton}
|
||||||
<span class="icon">close</span>
|
<span class="icon">close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const englishTranslations = {
|
const englishTranslations = {
|
||||||
general: {
|
general: {
|
||||||
closeButtonAudioLabel: "Close",
|
closeButton: "Close",
|
||||||
deleteItemButtonAudioLabel: "delete item",
|
deleteItemButtonAudioLabel: "delete item",
|
||||||
|
|
||||||
setButton: "Set",
|
setButton: "Set",
|
||||||
@@ -102,6 +102,19 @@ const englishTranslations = {
|
|||||||
message: {
|
message: {
|
||||||
composerInputPlaceholder: "Type a message...",
|
composerInputPlaceholder: "Type a message...",
|
||||||
sendMessageButtonAudioLabel: "send message",
|
sendMessageButtonAudioLabel: "send message",
|
||||||
|
|
||||||
|
showMessageInfoButtonAudioLabel: "show message info",
|
||||||
|
messageInfoHeadline: "Message Info",
|
||||||
|
|
||||||
|
sentBy: "Sent by",
|
||||||
|
timeSent: "Time sent",
|
||||||
|
channel: "Channel",
|
||||||
|
messageContent: "Message content",
|
||||||
|
|
||||||
|
copyMessageButton: "Copy message",
|
||||||
|
resendMessageButton: "Resend message",
|
||||||
|
decryptMessageButton: "Decrypt message",
|
||||||
|
deleteMessageButton: "Delete message",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import StorageModel, { storageKeys } from "../Model/storageModel";
|
|||||||
import ChatListViewModel from "./chatListViewModel";
|
import ChatListViewModel from "./chatListViewModel";
|
||||||
import ChatMessageViewModel from "../Model/chatMessageViewModel";
|
import ChatMessageViewModel from "../Model/chatMessageViewModel";
|
||||||
import { Color } from "./colors";
|
import { Color } from "./colors";
|
||||||
import SettingsModel from "../Model/settingsModel";
|
|
||||||
import SettingsViewModel from "./settingsViewModel";
|
import SettingsViewModel from "./settingsViewModel";
|
||||||
import { localeCompare } from "../Model/Utility/utility";
|
import { localeCompare } from "../Model/Utility/utility";
|
||||||
|
|
||||||
@@ -38,8 +37,8 @@ export default class ChatViewModel {
|
|||||||
ChatPageType.Messages
|
ChatPageType.Messages
|
||||||
);
|
);
|
||||||
|
|
||||||
chatMessageViewModels: React.ListState<ChatMessageViewModel> =
|
chatMessageViewModels: React.MapState<ChatMessageViewModel> =
|
||||||
new React.ListState();
|
new React.MapState();
|
||||||
composingMessage: React.State<string> = new React.State("");
|
composingMessage: React.State<string> = new React.State("");
|
||||||
|
|
||||||
// guards
|
// guards
|
||||||
@@ -73,11 +72,18 @@ export default class ChatViewModel {
|
|||||||
// add
|
// add
|
||||||
addChatMessage = (chatMessage: ChatMessage): void => {
|
addChatMessage = (chatMessage: ChatMessage): void => {
|
||||||
const chatMessageModel = new ChatMessageViewModel(
|
const chatMessageModel = new ChatMessageViewModel(
|
||||||
this.chatModel,
|
this,
|
||||||
chatMessage,
|
chatMessage,
|
||||||
chatMessage.sender == this.settingsViewModel.username.value
|
chatMessage.sender == this.settingsViewModel.username.value
|
||||||
);
|
);
|
||||||
this.chatMessageViewModels.add(chatMessageModel);
|
|
||||||
|
const existingChatMessageViewModel: ChatMessageViewModel | undefined =
|
||||||
|
this.chatMessageViewModels.value.get(chatMessage.id);
|
||||||
|
if (existingChatMessageViewModel != undefined) {
|
||||||
|
existingChatMessageViewModel.body.value = chatMessage.body;
|
||||||
|
} else {
|
||||||
|
this.chatMessageViewModels.set(chatMessage.id, chatMessageModel);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
@@ -125,10 +131,21 @@ export default class ChatViewModel {
|
|||||||
|
|
||||||
// messaging
|
// messaging
|
||||||
sendMessage = (): void => {
|
sendMessage = (): void => {
|
||||||
this.chatModel.sendMessage(this.composingMessage.value);
|
this.sendMessageFromBody(this.composingMessage.value);
|
||||||
this.composingMessage.value = "";
|
this.composingMessage.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sendMessageFromBody = (body: string): void => {
|
||||||
|
this.chatModel.sendMessage(body);
|
||||||
|
};
|
||||||
|
|
||||||
|
decryptMessage = async (
|
||||||
|
messageViewModel: ChatMessageViewModel
|
||||||
|
): Promise<void> => {
|
||||||
|
await this.chatModel.decryptMessage(messageViewModel.chatMessage);
|
||||||
|
messageViewModel.loadData();
|
||||||
|
};
|
||||||
|
|
||||||
// restore
|
// restore
|
||||||
restorePageSelection = (): void => {
|
restorePageSelection = (): void => {
|
||||||
const path: string[] = storageKeys.chatLastUsedPage(this.chatModel.id);
|
const path: string[] = storageKeys.chatLastUsedPage(this.chatModel.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user