add inline replies, ui improvements, rename ViewPersistence->ViewController, add scroll method and highlight

This commit is contained in:
marlon-erler
2026-04-14 13:15:38 +00:00
parent 634e898317
commit f4a3450b14
19 changed files with 508 additions and 122 deletions
+164 -53
View File
@@ -1312,7 +1312,7 @@
const combinedChannel = allChannels.join("/");
return [senderName, combinedChannel];
};
this.sendMessage = async (body, replyId, fileContent) => {
this.sendMessage = async (body, inlineReplyId, fileContent) => {
const nameAndChannel = this.getNameAndChannel();
if (nameAndChannel == false) return;
const [senderName, combinedChannel] = nameAndChannel;
@@ -1321,6 +1321,7 @@
senderName,
this.info.encryptionKey,
body,
inlineReplyId,
fileContent
);
this.addMessage(chatMessage);
@@ -1462,7 +1463,7 @@
};
}
static {
this.createChatMessage = async (channel, sender, encryptionKey, body, fileContent) => {
this.createChatMessage = async (channel, sender, encryptionKey, body, inlineReplyId, fileContent) => {
const chatMessage = {
dataVersion: DATA_VERSION,
id: v4_default(),
@@ -1470,6 +1471,7 @@
sender,
body,
dateSent: createTimestamp(),
inlineReplyId,
status: "outbox" /* Outbox */,
stringifiedFile: ""
};
@@ -1773,6 +1775,7 @@
///
showMessageInfoButtonAudioLabel: "show message info",
messageInfoHeadline: "Message Info",
cancelReplyAudioLabel: "cancel reply",
sentBy: "Sent by",
timeSent: "Time sent",
channel: "Channel",
@@ -1780,6 +1783,7 @@
copyMessageButton: "Copy message",
resendMessageButton: "Resend message",
decryptMessageButton: "Decrypt message",
replyToMessageButton: "Reply to message",
deleteMessageButton: "Delete message",
//
thumbsUpReaction: "Reaction: thumbs up",
@@ -1981,6 +1985,7 @@
filterMessagesButtonAudioLabel: "nachrichten filtern",
showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen",
messageInfoHeadline: "Nachrichteninfo",
cancelReplyAudioLabel: "antwort abbrechen",
sentBy: "Gesendet von",
timeSent: "Sendezeit",
channel: "Kanal",
@@ -1988,6 +1993,7 @@
copyMessageButton: "Nachricht kopieren",
resendMessageButton: "Nachricht erneut senden",
decryptMessageButton: "Nachricht entschl\xFCsseln",
replyToMessageButton: "Auf Nachricht antworten",
deleteMessageButton: "Nachricht l\xF6schen",
//
thumbsUpReaction: "Reaktion: Daumen hoch",
@@ -2179,6 +2185,7 @@
filterMessagesButtonAudioLabel: "filtrar mensajes",
showMessageInfoButtonAudioLabel: "mostrar informaci\xF3n del mensaje",
messageInfoHeadline: "Informaci\xF3n del Mensaje",
cancelReplyAudioLabel: "abortar la respuesta",
sentBy: "Enviado por",
timeSent: "Hora de env\xEDo",
channel: "Canal",
@@ -2186,6 +2193,7 @@
copyMessageButton: "Copiar mensaje",
resendMessageButton: "Reenviar mensaje",
decryptMessageButton: "Desencriptar mensaje",
replyToMessageButton: "Responder al mensaje",
deleteMessageButton: "Eliminar mensaje",
//
thumbsUpReaction: "Reacci\xF3n: pulgar hacia arriba",
@@ -2768,6 +2776,7 @@
this.coreViewModel = coreViewModel2;
this.messagePageViewModel = messagePageViewModel;
this.body = new State("");
this.inlineReply = void 0;
this.status = new State(
void 0
);
@@ -2820,6 +2829,14 @@
this.decryptMessage = () => {
this.messagePageViewModel.decryptMessage(this);
};
this.reply = () => {
this.messagePageViewModel.replyingMessage.value = this;
this.hideInfoModal();
};
this.cancelReply = () => {
if (this.messagePageViewModel.replyingMessage.value != this) return;
this.messagePageViewModel.replyingMessage.value = void 0;
};
// view
this.showInfoModal = () => {
this.isPresentingInfoModal.value = true;
@@ -2852,6 +2869,9 @@
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
this.body.value = this.chatMessage.body;
this.status.value = this.chatMessage.status;
if (this.chatMessage.inlineReplyId) {
this.inlineReply = this.messagePageViewModel.chatMessageViewModels.value.get(this.chatMessage.inlineReplyId);
}
};
this.chatMessage = chatMessage;
this.sentByUser = sentByUser;
@@ -2958,7 +2978,9 @@
this.filteredMessageViewModels = new ListState();
this.isFilterModalOpen = new State(false);
this.reactionFilter = new State(void 0);
this.replyingMessageId = new State(void 0);
this.replyingMessage = new State(
void 0
);
this.composingMessage = new State("");
// methods
this.sendMessage = () => {
@@ -2967,8 +2989,15 @@
this.composingMessage.value = "";
};
this.sendMessageFromBody = (body) => {
this.chatViewModel.chatModel.sendMessage(body, this.replyingMessageId.value);
this.replyingMessageId.value = void 0;
let replyId = void 0;
if (this.replyingMessage.value) {
replyId = this.replyingMessage.value.chatMessage.id;
}
this.chatViewModel.chatModel.sendMessage(
body,
replyId
);
this.replyingMessage.value = void 0;
};
this.decryptMessage = async (messageViewModel) => {
const chatMessage = messageViewModel.chatMessage;
@@ -3383,9 +3412,18 @@
this.registerKeyStroke("," /* Settings */, this.showSettings);
this.registerKeyStroke("enter" /* Apply */, this.hideSettings);
this.registerKeyStroke(";" /* Create */, this.createTask);
this.registerKeyStroke("j", () => this.selectedPage.value = "list" /* List */);
this.registerKeyStroke("k", () => this.selectedPage.value = "kanban" /* Kanban */);
this.registerKeyStroke("l", () => this.selectedPage.value = "status-grid" /* StatusGrid */);
this.registerKeyStroke(
"j",
() => this.selectedPage.value = "list" /* List */
);
this.registerKeyStroke(
"k",
() => this.selectedPage.value = "kanban" /* Kanban */
);
this.registerKeyStroke(
"l",
() => this.selectedPage.value = "status-grid" /* StatusGrid */
);
this.taskPageViewModel.registerContext(this.boardInfo.fileId, this);
}
};
@@ -4435,7 +4473,7 @@
"description",
coreViewModel2.translations.chatPage.message.messageContent,
chatMessageViewModel.body
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, coreViewModel2.translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, coreViewModel2.translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, coreViewModel2.translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, MessageReactionButtonRow(
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, coreViewModel2.translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, coreViewModel2.translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, coreViewModel2.translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.reply }, coreViewModel2.translations.chatPage.message.replyToMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "reply"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, MessageReactionButtonRow(
coreViewModel2,
chatMessageViewModel
), /* @__PURE__ */ createElement(
@@ -4451,9 +4489,70 @@
);
}
// src/View/viewController.ts
var ItemTracker = class {
};
var ViewTrackerMap = class {
constructor() {
this.trackers = /* @__PURE__ */ new Map();
this.register = (key) => {
if (this.trackers.has(key)) {
return this.trackers.get(key);
}
const tracker = new ItemTracker();
this.trackers.set(key, tracker);
return tracker;
};
this.setItems = (key, views) => {
const tracker = this.register(key);
if (tracker.items != void 0) {
return tracker.items;
} else {
tracker.items = views();
return tracker.items;
}
};
}
};
var ViewController = class {
static {
this.chatPages = new ItemTracker();
}
static {
this.taskPages = new ViewTrackerMap();
}
static {
this.boardPages = new ViewTrackerMap();
}
static {
this.inlineReplies = new ViewTrackerMap();
}
static {
this.messageIcons = new ViewTrackerMap();
}
static scrollToView(id) {
const element = document.getElementById(id);
if (!element) return;
element.scrollIntoView();
element.setAttribute("scrolled", "");
setTimeout(() => element.removeAttribute("scrolled"), 1e3);
}
};
// src/View/Components/inlineReply.tsx
function InlineReply(chatMessageViewModel) {
const reply = chatMessageViewModel.inlineReply;
if (reply == void 0) return /* @__PURE__ */ createElement("div", null);
const scroll = () => {
ViewController.scrollToView(reply.chatMessage.id);
};
return /* @__PURE__ */ createElement("div", { class: "inline-reply", "on:click": scroll }, /* @__PURE__ */ createElement("span", null, reply.sender), /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": reply.body }));
}
// src/View/Components/chatMessage.tsx
function ChatMessage4(coreViewModel2, chatMessageViewModel) {
const statusIcon = createProxyState(
const persistenceId = chatMessageViewModel.chatMessage.id;
const statusIcon = ViewController.messageIcons.setItems(persistenceId, () => createProxyState(
[chatMessageViewModel.status],
() => {
switch (chatMessageViewModel.status.value) {
@@ -4467,14 +4566,16 @@
return "warning";
}
}
);
));
return /* @__PURE__ */ createElement(
"div",
{
class: "message-bubble",
id: chatMessageViewModel.chatMessage.id,
"toggle:sentbyuser": chatMessageViewModel.sentByUser,
"toggle:hidden": chatMessageViewModel.isHidden
},
InlineReply(chatMessageViewModel),
/* @__PURE__ */ createElement("div", { class: "main tile" }, /* @__PURE__ */ createElement("div", { class: "text-container" }, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
"span",
{
@@ -4494,6 +4595,13 @@
"aria-label": coreViewModel2.translations.chatPage.message.showMessageInfoButtonAudioLabel
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "info")
), /* @__PURE__ */ createElement(
"button",
{
"on:click": chatMessageViewModel.reply,
"aria-label": coreViewModel2.translations.chatPage.message.replyToMessageButton
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "reply")
))),
MessageReactionButtonRow(coreViewModel2, chatMessageViewModel),
ChatMessageInfoModal(coreViewModel2, chatMessageViewModel)
@@ -4572,6 +4680,22 @@
)))), /* @__PURE__ */ createElement("button", { "on:click": messagePageViewModel.hideFilterModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
}
// src/View/Components/replyPreview.tsx
function ReplyPreview(coreViewModel2, chatMessageViewModel) {
const scroll = () => {
chatMessageViewModel.scrollIntoView();
};
return /* @__PURE__ */ createElement("div", { class: "reply-preview slide-up", "on:click": scroll }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": chatMessageViewModel.body })), /* @__PURE__ */ createElement(
"button",
{
class: "standard square",
"on:click": chatMessageViewModel.cancelReply,
"aria-label": coreViewModel2.translations.chatPage.message.cancelReplyAudioLabel
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
));
}
// src/View/ChatPages/messagePage.tsx
function MessagePage(coreViewModel2, messagePageViewModel) {
messagePageViewModel.loadData();
@@ -4588,6 +4712,21 @@
]
}
);
const persistenceId = messagePageViewModel.chatViewModel.chatModel.id;
const replyPreview = ViewController.inlineReplies.setItems(
persistenceId,
() => createProxyState(
[messagePageViewModel.replyingMessage],
() => {
if (messagePageViewModel.replyingMessage.value == void 0)
return /* @__PURE__ */ createElement("div", null);
return ReplyPreview(
coreViewModel2,
messagePageViewModel.replyingMessage.value
);
}
)
);
function scrollDown(hard = false) {
if (hard) {
messageContainer.setAttribute("scroll-hard", "");
@@ -4604,6 +4743,9 @@
scrollDownIfApplicable
);
setTimeout(() => scrollDown(true), 100);
messagePageViewModel.replyingMessage.subscribeSilent(() => {
setFocusWithDelay();
});
setFocusWithDelay();
return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { class: "title" }, coreViewModel2.translations.chatPage.message.messagesHeadline), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
"button",
@@ -4614,7 +4756,13 @@
"toggle:selected": messagePageViewModel.isFilterActive
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt")
))), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement(
))), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement(
"div",
{
class: "reply-preview-wrapper",
"children:set": replyPreview
}
), /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement(
"input",
{
id: "focused",
@@ -5205,48 +5353,11 @@
)), /* @__PURE__ */ createElement("button", { "on:click": close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
}
// src/View/viewPersistence.ts
var PageTracker = class {
};
var PageTrackerMap = class {
constructor() {
this.trackers = /* @__PURE__ */ new Map();
this.register = (key) => {
if (this.trackers.has(key)) {
return this.trackers.get(key);
}
const tracker = new PageTracker();
this.trackers.set(key, tracker);
return tracker;
};
this.setPages = (key, pages) => {
const tracker = this.register(key);
if (tracker.pages != void 0) {
return tracker.pages;
} else {
tracker.pages = pages();
return tracker.pages;
}
};
}
};
var ViewPersistence = class {
static {
this.chatPages = new PageTracker();
}
static {
this.taskPages = new PageTrackerMap();
}
static {
this.boardPages = new PageTrackerMap();
}
};
// src/View/ChatPages/boardPage.tsx
function BoardPage(coreViewModel2, boardViewModel) {
boardViewModel.loadData();
const persistenceId = boardViewModel.boardInfo.fileId;
const pages = ViewPersistence.boardPages.setPages(
const pages = ViewController.boardPages.setItems(
persistenceId,
() => createProxyState([boardViewModel.selectedPage], () => {
switch (boardViewModel.selectedPage.value) {
@@ -5389,7 +5500,7 @@
() => taskPageViewModel.selectedBoardId.value != void 0
);
const persistenceId = taskPageViewModel.chatViewModel.chatModel.id;
const pages = ViewPersistence.taskPages.setPages(
const pages = ViewController.taskPages.setItems(
persistenceId,
() => createProxyState([taskPageViewModel.selectedBoardId], () => {
const selectedBoardId = taskPageViewModel.selectedBoardId.value;
@@ -5461,7 +5572,7 @@
// src/View/chatPage.tsx
function ChatPage(coreViewModel2, chatViewModel) {
ViewPersistence.chatPages.pages = createProxyState(
ViewController.chatPages.items = createProxyState(
[chatViewModel.selectedPage],
() => {
switch (chatViewModel.selectedPage.value) {
@@ -5556,7 +5667,7 @@
"div",
{
id: "main",
"children:set": ViewPersistence.chatPages.pages
"children:set": ViewController.chatPages.items
}
))
);