diff --git a/dist/index.css b/dist/index.css index 20b0221a..7eea13fd 100644 --- a/dist/index.css +++ b/dist/index.css @@ -709,6 +709,13 @@ body[showing-chat] #chat-page-wrapper { .message-bubble .tile.main .body { word-break: break-word; } +.modal .message-bubble .tile.main .body { + word-break: keep-all; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; +} .message-bubble[sentbyuser] { justify-content: flex-end; } diff --git a/dist/index.js b/dist/index.js index 3575b0f5..e56edd1f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2022,7 +2022,9 @@ void 0 ); this.allReactions = new MapState(); - this.userReaction = new State(void 0); + this.userReaction = new State( + void 0 + ); this.reactionsThumbsUp = MapState; this.reactionsCheck = MapState; this.reactionsStop = MapState; @@ -2081,7 +2083,8 @@ } else { this.allReactions.set(reaction.sender, reaction); } - if (reaction.sender != this.coreViewModel.settingsModel.username) return; + if (reaction.sender != this.coreViewModel.settingsModel.username) + return; this.userReaction.value = reaction.isDeleting ? void 0 : reaction.content; }; this.sendReaction = (content, isDeleting) => { @@ -2105,6 +2108,59 @@ } }; + // src/ViewModel/Utility/searchViewModel.ts + var SearchViewModel = class { + // init + constructor(allObjects, matchingObjects, getStringsOfObject, suggestions) { + // state + this.appliedQuery = new State(""); + this.searchInput = new State(""); + // guards + this.cannotApplySearch = createProxyState( + [this.searchInput, this.appliedQuery], + () => this.searchInput.value == this.appliedQuery.value + ); + // methods + this.search = (searchTerm) => { + this.searchInput.value = searchTerm; + this.applySearch(); + }; + this.applySearch = () => { + this.appliedQuery.value = this.searchInput.value; + this.matchingObjects.clear(); + for (const object of this.allObjects.value.values()) { + const doesMatch = this.checkDoesMatchSearch(object); + if (doesMatch == false) continue; + this.matchingObjects.add(object); + } + }; + // utility + this.checkDoesMatchSearch = (object) => { + return checkDoesObjectMatchSearch( + this.appliedQuery.value, + this.getStringsOfObject, + object + ); + }; + this.allObjects = allObjects; + this.matchingObjects = matchingObjects; + this.getStringsOfObject = getStringsOfObject; + this.suggestions = suggestions; + this.allObjects.handleAddition((newObject) => { + const doesMatch = this.checkDoesMatchSearch(newObject); + if (doesMatch == false) { + this.matchingObjects.remove(newObject); + } else { + if (this.matchingObjects.value.has(newObject)) return; + this.matchingObjects.add(newObject); + this.allObjects.handleRemoval(newObject, () => { + this.matchingObjects.remove(newObject); + }); + } + }); + } + }; + // src/ViewModel/Pages/messagePageViewModel.ts var MessagePageViewModel = class { // init @@ -2113,6 +2169,8 @@ this.chatViewModel = chatViewModel; // state this.chatMessageViewModels = new MapState(); + this.filteredMessageViewModels = new ListState(); + this.isFilterModalOpen = new State(false); this.composingMessage = new State(""); // methods this.sendMessage = () => { @@ -2160,6 +2218,9 @@ if (messageViewModel == void 0) return; messageViewModel.handleReaction(reaction); }; + this.openFilterModal = () => { + this.isFilterModalOpen.value = true; + }; // load this.loadData = () => { this.chatMessageViewModels.clear(); @@ -2177,6 +2238,12 @@ ], () => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == "" ); + this.searchViewModel = new SearchViewModel( + this.chatMessageViewModels, + this.filteredMessageViewModels, + (chatMessageViewModel) => [chatMessageViewModel.body.value], + new ListState() + ); } }; @@ -2271,59 +2338,6 @@ } }; - // src/ViewModel/Utility/searchViewModel.ts - var SearchViewModel = class { - // init - constructor(allObjects, matchingObjects, getStringsOfObject, suggestions) { - // state - this.appliedQuery = new State(""); - this.searchInput = new State(""); - // guards - this.cannotApplySearch = createProxyState( - [this.searchInput, this.appliedQuery], - () => this.searchInput.value == this.appliedQuery.value - ); - // methods - this.search = (searchTerm) => { - this.searchInput.value = searchTerm; - this.applySearch(); - }; - this.applySearch = () => { - this.appliedQuery.value = this.searchInput.value; - this.matchingObjects.clear(); - for (const object of this.allObjects.value.values()) { - const doesMatch = this.checkDoesMatchSearch(object); - if (doesMatch == false) continue; - this.matchingObjects.add(object); - } - }; - // utility - this.checkDoesMatchSearch = (object) => { - return checkDoesObjectMatchSearch( - this.appliedQuery.value, - this.getStringsOfObject, - object - ); - }; - this.allObjects = allObjects; - this.matchingObjects = matchingObjects; - this.getStringsOfObject = getStringsOfObject; - this.suggestions = suggestions; - this.allObjects.handleAddition((newObject) => { - const doesMatch = this.checkDoesMatchSearch(newObject); - if (doesMatch == false) { - this.matchingObjects.remove(newObject); - } else { - if (this.matchingObjects.value.has(newObject)) return; - this.matchingObjects.add(newObject); - this.allObjects.handleRemoval(newObject, () => { - this.matchingObjects.remove(newObject); - }); - } - }); - } - }; - // src/ViewModel/Pages/boardViewModel.ts var BoardViewModel = class extends TaskContainingPageViewModel { // init @@ -3407,7 +3421,10 @@ function MessageReactionButton(coreViewModel2, chatMessageViewModel, content) { let audioLabel; let count; - let isActive = createProxyState([chatMessageViewModel.userReaction], () => chatMessageViewModel.userReaction.value == content); + let isActive = createProxyState( + [chatMessageViewModel.userReaction], + () => chatMessageViewModel.userReaction.value == content + ); function sendReaction() { chatMessageViewModel.sendReaction(content, isActive.value); } @@ -3590,6 +3607,52 @@ ); } + // src/View/Modals/searchModal.tsx + function SearchModal(coreViewModel2, searchViewModel, headline, converter, isOpen) { + function close() { + isOpen.value = false; + } + const suggestionId = v4_default(); + return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( + "input", + { + placeholder: coreViewModel2.translations.general.searchLabel, + "bind:value": searchViewModel.searchInput, + "on:enter": searchViewModel.applySearch, + list: suggestionId + } + ), /* @__PURE__ */ createElement( + "datalist", + { + hidden: true, + id: suggestionId, + "children:append": [ + searchViewModel.suggestions, + StringToOption + ] + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": coreViewModel2.translations.general.searchButtonAudioLabel, + "on:click": searchViewModel.applySearch, + "toggle:disabled": searchViewModel.cannotApplySearch + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "search") + )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( + "div", + { + class: "grid gap", + style: "grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr))", + "children:append": [ + searchViewModel.matchingObjects, + converter + ] + } + )), /* @__PURE__ */ createElement("button", { "on:click": close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + } + // src/View/ChatPages/messagePage.tsx function MessagePage(coreViewModel2, messagePageViewModel) { messagePageViewModel.loadData(); @@ -3601,7 +3664,7 @@ { id: "message-container", "children:append": [ - messagePageViewModel.chatMessageViewModels, + messagePageViewModel.filteredMessageViewModels, ChatMessageViewModelToView ] } @@ -3618,11 +3681,19 @@ if (scrollFromBottom > 400) return; scrollDown(); } - messagePageViewModel.chatMessageViewModels.subscribeSilent( + messagePageViewModel.filteredMessageViewModels.subscribeSilent( scrollDownIfApplicable ); setTimeout(() => scrollDown(true), 100); - 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("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( + 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", + { + class: "ghost", + "on:click": messagePageViewModel.openFilterModal, + "aria-label": coreViewModel2.translations.chatPage.message.filterMessagesButtonAudioLabel + }, + /* @__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( "input", { "bind:value": messagePageViewModel.composingMessage, @@ -3638,7 +3709,13 @@ "toggle:disabled": messagePageViewModel.cannotSendMessage }, /* @__PURE__ */ createElement("span", { class: "icon" }, "send") - )))))))); + ))))))), SearchModal( + coreViewModel2, + messagePageViewModel.searchViewModel, + coreViewModel2.translations.chatPage.message.messageFilterHeadline, + ChatMessageViewModelToView, + messagePageViewModel.isFilterModalOpen + )); } // src/View/Components/colorPicker.tsx @@ -4158,52 +4235,6 @@ return RibbonButton(label, icon, isSelected, select); } - // src/View/Modals/searchModal.tsx - function SearchModal(coreViewModel2, searchViewModel, headline, converter, isOpen) { - function close() { - isOpen.value = false; - } - const suggestionId = v4_default(); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: coreViewModel2.translations.general.searchLabel, - "bind:value": searchViewModel.searchInput, - "on:enter": searchViewModel.applySearch, - list: suggestionId - } - ), /* @__PURE__ */ createElement( - "datalist", - { - hidden: true, - id: suggestionId, - "children:append": [ - searchViewModel.suggestions, - StringToOption - ] - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": coreViewModel2.translations.general.searchButtonAudioLabel, - "on:click": searchViewModel.applySearch, - "toggle:disabled": searchViewModel.cannotApplySearch - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "search") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( - "div", - { - class: "grid gap", - style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))", - "children:append": [ - searchViewModel.matchingObjects, - converter - ] - } - )), /* @__PURE__ */ createElement("button", { "on:click": close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); - } - // src/View/ChatPages/boardPage.tsx function BoardPage(coreViewModel2, boardViewModel) { boardViewModel.loadData(); @@ -5110,9 +5141,11 @@ }, message: { messagesHeadline: "Messages", + messageFilterHeadline: "Filter messages", /// composerInputPlaceholder: "Type a message...", sendMessageButtonAudioLabel: "send message", + filterMessagesButtonAudioLabel: "filter messages", /// showMessageInfoButtonAudioLabel: "show message info", messageInfoHeadline: "Message Info", @@ -5316,8 +5349,10 @@ }, message: { messagesHeadline: "Nachrichten", + messageFilterHeadline: "Nachrichten filtern", composerInputPlaceholder: "Schreib eine Nachricht...", sendMessageButtonAudioLabel: "nachricht senden", + filterMessagesButtonAudioLabel: "nachrichten filtern", showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen", messageInfoHeadline: "Nachrichteninfo", sentBy: "Gesendet von", @@ -5510,8 +5545,10 @@ }, message: { messagesHeadline: "Mensajes", + messageFilterHeadline: "Filtrar mensajes", composerInputPlaceholder: "Escribe un mensaje...", sendMessageButtonAudioLabel: "enviar mensaje", + filterMessagesButtonAudioLabel: "filtrar mensajes", showMessageInfoButtonAudioLabel: "mostrar informaci\xF3n del mensaje", messageInfoHeadline: "Informaci\xF3n del Mensaje", sentBy: "Enviado por", @@ -5592,7 +5629,7 @@ this.connectionModel = connectionModel2; this.chatListModel = chatListModel2; this.fileTransferModel = fileTransferModel2; - this.BUILD = "Build 26.04.10.B"; + this.BUILD = "Build 26.04.11.A"; // DRAG & DROP this.draggedObject = new State(void 0); // SUGGESTIONS diff --git a/src/View/ChatPages/messagePage.css b/src/View/ChatPages/messagePage.css index f60e08a5..d18ce805 100644 --- a/src/View/ChatPages/messagePage.css +++ b/src/View/ChatPages/messagePage.css @@ -149,6 +149,14 @@ word-break: break-word; } +.modal .message-bubble .tile.main .body { + word-break: keep-all; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; +} + .message-bubble[sentbyuser] { justify-content: flex-end; } diff --git a/src/View/ChatPages/messagePage.tsx b/src/View/ChatPages/messagePage.tsx index 3694d278..f9d73f05 100644 --- a/src/View/ChatPages/messagePage.tsx +++ b/src/View/ChatPages/messagePage.tsx @@ -6,6 +6,7 @@ import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { ChatMessage } from "../Components/chatMessage"; +import { SearchModal } from "../Modals/searchModal"; export function MessagePage( coreViewModel: CoreViewModel, @@ -22,7 +23,7 @@ export function MessagePage(
@@ -43,7 +44,7 @@ export function MessagePage( scrollDown(); } - messagePageViewModel.chatMessageViewModels.subscribeSilent( + messagePageViewModel.filteredMessageViewModels.subscribeSilent( scrollDownIfApplicable, ); setTimeout(() => scrollDown(true), 100); @@ -59,6 +60,18 @@ export function MessagePage( .messagesHeadline } + + +
{messageContainer} @@ -100,6 +113,15 @@ export function MessagePage(
+ + {SearchModal( + coreViewModel, + messagePageViewModel.searchViewModel, + coreViewModel.translations.chatPage.message + .messageFilterHeadline, + ChatMessageViewModelToView, + messagePageViewModel.isFilterModalOpen, + )} ); } diff --git a/src/View/Components/messageReactionButton.tsx b/src/View/Components/messageReactionButton.tsx index 6087ccca..73ca0904 100644 --- a/src/View/Components/messageReactionButton.tsx +++ b/src/View/Components/messageReactionButton.tsx @@ -13,7 +13,10 @@ export function MessageReactionButton( ) { let audioLabel: string; let count: React.State; - let isActive = React.createProxyState([chatMessageViewModel.userReaction], (() => chatMessageViewModel.userReaction.value == content)) + let isActive = React.createProxyState( + [chatMessageViewModel.userReaction], + () => chatMessageViewModel.userReaction.value == content, + ); function sendReaction() { chatMessageViewModel.sendReaction(content, isActive.value); diff --git a/src/View/translations.ts b/src/View/translations.ts index f9f65599..d1597da7 100644 --- a/src/View/translations.ts +++ b/src/View/translations.ts @@ -184,11 +184,13 @@ const englishTranslations = { message: { messagesHeadline: "Messages", + messageFilterHeadline: "Filter messages", /// composerInputPlaceholder: "Type a message...", sendMessageButtonAudioLabel: "send message", + filterMessagesButtonAudioLabel: "filter messages", /// @@ -480,9 +482,11 @@ export const allTranslations: { message: { messagesHeadline: "Nachrichten", + messageFilterHeadline: "Nachrichten filtern", composerInputPlaceholder: "Schreib eine Nachricht...", sendMessageButtonAudioLabel: "nachricht senden", + filterMessagesButtonAudioLabel: "nachrichten filtern", showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen", messageInfoHeadline: "Nachrichteninfo", @@ -750,9 +754,11 @@ export const allTranslations: { message: { messagesHeadline: "Mensajes", + messageFilterHeadline: "Filtrar mensajes", composerInputPlaceholder: "Escribe un mensaje...", sendMessageButtonAudioLabel: "enviar mensaje", + filterMessagesButtonAudioLabel: "filtrar mensajes", showMessageInfoButtonAudioLabel: "mostrar información del mensaje", diff --git a/src/ViewModel/Chat/chatMessageViewModel.ts b/src/ViewModel/Chat/chatMessageViewModel.ts index 61cf2793..cc08d8b2 100644 --- a/src/ViewModel/Chat/chatMessageViewModel.ts +++ b/src/ViewModel/Chat/chatMessageViewModel.ts @@ -23,7 +23,9 @@ export default class ChatMessageViewModel { sentByUser: boolean; allReactions = new React.MapState(); - userReaction = new React.State(undefined); + userReaction = new React.State( + undefined, + ); reactionsThumbsUp = React.MapState; reactionsCheck = React.MapState; reactionsStop = React.MapState; @@ -93,8 +95,11 @@ export default class ChatMessageViewModel { this.allReactions.set(reaction.sender, reaction); } - if (reaction.sender != this.coreViewModel.settingsModel.username) return; - this.userReaction.value = reaction.isDeleting ? undefined : reaction.content; + if (reaction.sender != this.coreViewModel.settingsModel.username) + return; + this.userReaction.value = reaction.isDeleting + ? undefined + : reaction.content; }; sendReaction = (content: ReactionSymbols, isDeleting: boolean): void => { diff --git a/src/ViewModel/Pages/messagePageViewModel.ts b/src/ViewModel/Pages/messagePageViewModel.ts index 97207638..fada131a 100644 --- a/src/ViewModel/Pages/messagePageViewModel.ts +++ b/src/ViewModel/Pages/messagePageViewModel.ts @@ -8,11 +8,16 @@ import { import ChatMessageViewModel from "../Chat/chatMessageViewModel"; import ChatViewModel from "../Chat/chatViewModel"; import CoreViewModel from "../Global/coreViewModel"; +import SearchViewModel from "../Utility/searchViewModel"; export default class MessagePageViewModel { // state chatMessageViewModels: React.MapState = new React.MapState(); + filteredMessageViewModels: React.ListState = + new React.ListState(); + searchViewModel: SearchViewModel; + isFilterModalOpen = new React.State(false); composingMessage: React.State = new React.State(""); // guards @@ -81,6 +86,10 @@ export default class MessagePageViewModel { messageViewModel.handleReaction(reaction); }; + openFilterModal = (): void => { + this.isFilterModalOpen.value = true; + }; + // load loadData = (): void => { this.chatMessageViewModels.clear(); @@ -106,5 +115,12 @@ export default class MessagePageViewModel { this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == "", ); + + this.searchViewModel = new SearchViewModel( + this.chatMessageViewModels, + this.filteredMessageViewModels, + (chatMessageViewModel) => [chatMessageViewModel.body.value], + new React.ListState(), + ); } }