From f9ac6810d17b1d17a65c68d0784559a4cd0f19c2 Mon Sep 17 00:00:00 2001 From: Dzi-Mieha Date: Fri, 30 Aug 2024 07:16:02 +0300 Subject: [PATCH 0001/2090] Update be-by.json correcting grammatical errors --- src/locales/list/be-by.json | 230 ++++++++++++++++++------------------ 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/src/locales/list/be-by.json b/src/locales/list/be-by.json index 715d9c547..23b9c4997 100644 --- a/src/locales/list/be-by.json +++ b/src/locales/list/be-by.json @@ -1,12 +1,12 @@ { "header": { - "logoutButton": "Выйсці", - "openAppButton": "Адкрыць праграму", - "loginButton": "Увайсці", + "logoutButton": "Выйсьці", + "openAppButton": "Адкрыць праґраму", + "loginButton": "Увайсьці", "joinNowButton": "Далучыцца" }, "homePage": { - "slogan": "Сучасная і элегантная праграма для абмена паведамленнямі.", + "slogan": "Сучасная й элегантная праґрама для памену паведамленьнямі.", "joinButton": "Далучыцца да {{appName}}", "viewGitHubButton": "Праглядзець на GitHub" }, @@ -14,30 +14,30 @@ "title": "Увайдзіце, каб працягнуць", "emailOrUsernameAndTag": "Эл. адрас / імя карыстальніка:тэг", "password": "Пароль", - "login": "Увайсці", + "login": "Увайсьці", "createAccountInstead": "Замест гэтага стварыць уліковы запіс", - "loggingIn": "Ідзе ўваход...", - "loginButton": "Увайсці" + "loggingIn": "Уваходзім...", + "loginButton": "Увайсьці" }, "registerPage": { "title": "Вітаем у {{appName}}", - "email": "Электронны адрас", + "email": "Электронная адрэса", "username": "Імя карыстальніка", "password": "Пароль", - "confirmPassword": "Пацвярджэнне пароля", + "confirmPassword": "Пацьвердзіце пароль", "registerButton": "Зарэгістравацца", - "registering": "Ідзе рэгістрацыя...", - "loginInstead": "Замест гэтага увайсці" + "registering": "Рэгіструем...", + "loginInstead": "Замест гэтага ўвайсьці" }, "dashboard": { - "title": "Панэль кіравання", + "title": "Панэль кіраваньня", "activeUsers": "Зараз у сетцы", "noActiveUsers": "Няма актыўных карыстальнікаў", - "servers": "Серверы", - "posts": "Публікацыі", - "feed": "Лента", - "discover": "Даследаваць", - "notifications": "Апавяшчэнні" + "servers": "Сэрвэры", + "posts": "Публікацыйі", + "feed": "Стужка", + "discover": "Дасьледваць", + "notifications": "Апавяшчэньні" }, "inbox": { "drawer": { @@ -52,23 +52,23 @@ }, "explore": { "drawer": { - "title": "Даследаваць", - "servers": "Серверы" + "title": "Дасьледваць", + "servers": "Сэрвэры" }, "servers": { - "title": "Даследаванне - Серверы", - "noticeMessage": "Сервер можна падтрымаць раз у {{hours}} гадзін. Усе падтрымліванні скідываюцца кожны {{date}}.", - "sortMostBumps": "Найбольш падтрымак", + "title": "Дасьледаваньне - Сэрвэры", + "noticeMessage": "Сэрвэр можна ўсхваліць раз у {{hours}} гадзін. Усе ўсхваленьні скідваюцца кожны {{date}}.", + "sortMostBumps": "Найбольш усхваленьняў", "sortMostMembers": "Найбольш удзельнікаў", "sortRecentlyAdded": "Нядаўна даданыя", - "sortRecentlyBumped": "Нядаўна падтрыманыя", + "sortRecentlyBumped": "Нядаўна ўсхвалёныя", "filterAll": "Усе", - "filterVerified": "Правераныя", + "filterVerified": "Верыфікаваныя", "memberCount": "{{count}} удзельнікаў", - "lifetimeBumpCount": "{{count}} падтрымак(кі) за ўвесь час", + "lifetimeBumpCount": "{{count}} усхваленьняў за ўвесь час", "visitServerButton": "Наведаць", "joinServerButton": "Далучыцца", - "bumpButton": "Падтрымаць ({{count}})" + "bumpButton": "Усхваліць ({{count}})" } }, "servers": { @@ -77,27 +77,27 @@ "general": "Агульныя", "roles": "Ролі", "channels": "Каналы", - "bans": "Блакіроўкі", - "invites": "Запрашэнні", - "publishServer": "Апублікаваць сервер", - "verify": "Пацвердзіць", + "bans": "Блякаваньня", + "invites": "Запрашэньні", + "publishServer": "Зрабіць сэрвэр публічным", + "verify": "Пацьвердзіць", "role": "Роля", - "welcome-screen": "Экран запрашэння", + "welcome-screen": "Экран запрашэньня", "channel": "Канал", "emojis": "Эмодзі", - "notifications": "Апавяшчэнні" + "notifications": "Апавяшчэньні" }, "general": { - "serverRenameNotice": "Вы згубіце ўсе асаблівасці праверанага сервера, калі парайменуйце яго.", - "serverName": "Назва серверу", - "defaultChannel": "Стандартны канал", - "defaultChannelDescription": "Новыя ўдзельнікі будуць накіраваны ў гэты канал.", - "systemMessages": "Сістэмныя паведамленні", - "systemMessagesDescription": "Тут павінны з'яўляцца сістэмныя паведамленні.", - "deleteThisServer": "Выдаліць гэты сервер", - "deleteThisServerDescription": "Гэта дзеянне нельга адрабіць!", - "deleteServerButton": "Выдаліць сервер", - "saveChangesButton": "Захаваць змены", + "serverRenameNotice": "Вы страціце ўсе йльготы верыфікаванага сэрвэру, калі пераназавяце яго.", + "serverName": "Назва сэрвэру", + "defaultChannel": "Канал па змаўчаньні", + "defaultChannelDescription": "Новыя ўдзельнікі будуць накіраваныя ў гэты канал.", + "systemMessages": "Сыстэмныя паведамленьні", + "systemMessagesDescription": "Тут павінны зьяўляцца сыстэмныя паведамленьні.", + "deleteThisServer": "Выдаліць гэты сэрвэр", + "deleteThisServerDescription": "Гэта дзеяньне нельга адрабіць!", + "deleteServerButton": "Выдаліць сэрвэр", + "saveChangesButton": "Захаваць зьмены", "saving": "Захоўваем..." }, "roles": { @@ -105,19 +105,19 @@ "addRoleButton": "Стварыць ролю" }, "role": { - "saveChangesButton": "Захаваць змены", + "saveChangesButton": "Захаваць зьмены", "saving": "Захоўваем...", "roleName": "Назва ролі", - "roleColor": "Колер роли", + "roleColor": "Колер ролі", "hideRole": "Схаваць ролю", - "hideRoleDescription": "Паказваць удзельникаў з гэтай роляю зараз са ўсімі асстатнімі ўдзельнікамі", + "hideRoleDescription": "Паказваць гэтую ролю ў сьпісе ўдзельнікаў", "permissions": "Дазволы", - "permissionsDescription": "Кіраванне дазволамі гэтай ролі.", + "permissionsDescription": "Кіраваньне дазволамі гэтай ролі.", "deleteRoleButton": "Выдаліць гэту ролю", - "deleteRoleButtonDescription": "Гэта дзеянне нельга адрабіць!" + "deleteRoleButtonDescription": "Гэта дзеяньне нельга адмяніць!" }, "header": { - "editServer": "Рэдагаваць сервер", + "editServer": "Рэдагаваць сэрвэр", "serverMemberCount": "{{count}} удзельнікаў" }, "channels": { @@ -127,56 +127,56 @@ "channel": { "saving": "Захоўваем...", "saveChangesButton": "Захаваць", - "channelName": "Назва канала", + "channelName": "Назва каналу", "permissions": "Дазволы", - "permissionsDescription": "Кіраванне дазволамі гэтага канала.", + "permissionsDescription": "Кіраваньне дазволамі гэтага каналу.", "deleteThisChannel": "Выдаліць гэты канал", - "deleteThisChannelDescription": "Гэта дзеянне нельга адрабіць!", + "deleteThisChannelDescription": "Гэта дзеяньне нельга адмяніць!", "deleteChannelButton": "Выдаліць канал" }, "invites": { - "createInviteButton": "Стварыць запрашэнне", - "serverInvites": "Запрашэнні сервера", - "serverInvitesDescription": "Запрасіце сваіх сяброў на гэты сервер.", - "customInviteVerifiedOnlyNotice": "Карыстальніцкія запрашэнні даступны толькі на правераных серверах.", + "createInviteButton": "Стварыць запрашэньне", + "serverInvites": "Запрашэньні сэрвэру", + "serverInvitesDescription": "Запрасіце свайіх сяброў на гэты сэрвэр.", + "customInviteVerifiedOnlyNotice": "Карыстальніцкія запрашэньні даступныя толькі на верыфікаваных сэрвэрах.", "customLink": "Карыстальніцкая спасылка", "saveButton": "Захаваць", "deleteButton": "Выдаліць", "copyLinkButton": "Скапіяваць спасылку" }, "publishServer": { - "publishNotice": "Пасля публікацыі, сервер з'явіцца ва ўкладцы <1>*Даследаваць*.", + "publishNotice": "Пасьля публікацыйі, сэрвэр зьявіцца ва ўкладцы <1>*Дасьледаваць*.", "public": "Публічны", - "publicDescription": "Зрабіць гэты сервер публічным.", - "bumpServer": "Падтрымаць сервер", - "bumpServerDescription": "Падтрымайце сервер, каб ён падняўся наверх.", - "publishServerButton": "Апублікаваць сервер", - "unpublishServerButton": "Зрабіць сервер прыватным" + "publicDescription": "Зрабіць гэты сэрвэр публічным.", + "bumpServer": "Усхваліць сэрвэр", + "bumpServerDescription": "Падтрымайце сэрвэр, каб ён падняўся ўгору.", + "publishServerButton": "Апублікаваць сэрвэр", + "unpublishServerButton": "Зрабіць сэрвэр прыватным" } }, "rolePermissions": { "admin": "Адмін", "adminDescription": "Уключае ўсе дазволы.", - "sendMessage": "Адпраўляць паведамленні", - "sendMessageDescription": "Дазваляе адпраўку паведамленняў на серверы. Адміны могуць адпраўляць паведамленні незалежна ад гэтага дазволу.", + "sendMessage": "Дасылаць паведамленьні", + "sendMessageDescription": "Дазваляе адпраўляць паведамленьні на сэрвэры. Адміны могуць адпраўляць паведамленьні незалежна ад гэтага дазволу.", "manageRoles": "Кіраваць ролямі", - "manageRolesDescription": "Дазвол на рэдагаванне і выдаленне роляў.", + "manageRolesDescription": "Дазвол на рэдагаваньне й выдаленьне роляў.", "manageChannels": "Кіраваць каналамі", - "manageChannelsDescription": "Дазвол на рэдагаванне і выдаленне каналаў.", + "manageChannelsDescription": "Дазвол на рэдагаваньне й выдаленьне каналаў.", "kick": "Выганяць", - "kickDescription": "Дазвол на выган удзельнікаў", - "ban": "Блакіраваць", - "banDescription": "Дазвол на блакіроўку ўдзельнікаў.", + "kickDescription": "Дазвол выганяць удзельнікаў", + "ban": "Блякаваць", + "banDescription": "Дазвол на блякіроўку ўдзельнікаў.", "mentionEveryone": "Згадваць усіх", - "mentionEveryoneDescription": "Дазваляе згадванне @everyone. Адміны могуць згадваць усіх незалежна ад гэтага дазволу.", - "nicknameMember": "Змяняць нікнэймы", - "nicknameMemberDescription": "Дазваляе карыстальнікам змяняць іх нікнэйм на серверы." + "mentionEveryoneDescription": "Дазваляе згадваньне @everyone. Адміны могуць згадваць усіх незалежна ад гэтага дазволу.", + "nicknameMember": "Зьмяняць нікнэймы", + "nicknameMemberDescription": "Дазваляе карыстальнікам зьмяняць іх нікнэйм на сэрвэры." }, "channelPermissions": { "privateChannel": "Прыватны канал", "privateChannelDescription": "Абмежаваць доступ да гэтага каналу. Адміны могуць праглядваць канал незалежна ад гэтага дазволу.", - "sendMessage": "Адпраўляць паведамленні", - "sendMessageDescription": "Дазваляе адпраўку паведамленняў у гэтым канале. Адміны могуць адпраўляць паведамленні незалежна ад гэтага дазволу.", + "sendMessage": "Адпраўляць паведамленьні", + "sendMessageDescription": "Дазваляе адпараўляць паведамленьні ў гэтым канале. Адміны могуць адпраўляць паведамленьні незалежна ад гэтага дазволу.", "joinVoice": "Далучацца да выкліку", "joinVoiceDescription": "Дазваляе далучацца да выклікаў у гэтым канале. Адміны могуць далучацца незалежна ад гэтага дазволу." } @@ -185,35 +185,35 @@ "drawer": { "title": "Налады", "account": "Уліковы запіс", - "interface": "Інтэрфейс", + "interface": "Інтэрфэйс", "language": "Мова", - "notifications": "Апавяшчэнні", - "logout": "Выйсці", + "notifications": "Паведамленьні", + "logout": "Выйсьці", "developer": "Для распрацоўшчыкаў", "applications": "Боты", - "experiments": "Эксперыменты", + "experiments": "Экспэрымэнты", "supportMe": "Падтрымаць мяне", - "viewSource": "Праглядзець код", - "changelog": "Спіс змен", + "viewSource": "Глядзець код", + "changelog": "Сьпіс зьменаў", "window-settings": "Налады акна", "call-settings": "Налады выклікаў", "activity-status": "Статус", - "tickets": "Білеты", - "privacy": "Прыватнасць", - "connections": "Злучэнні" + "tickets": "Квіткі", + "privacy": "Прыватнасьць", + "connections": "Злучэньні" }, "privacy": { "directMessage": { - "title": "Прамыя паведамленні", - "description": "Хто можа гаварыць са мной у прамых паведамленнях.", + "title": "Асабістыя паведамленьні", + "description": "Хто можа гутарыць са мной у васабістых паведамленьнях.", "anyone": "Усе", - "friendsAndServersOnly": "Толькі сябры і удзельнікі агульных са мной сервероў", + "friendsAndServersOnly": "Толькі сябры й удзельнікі агульных са мной сэрвэраў", "friendsOnly": "Толькі сябры" }, "profileOptions": { "title": "Налады профілю", - "hideFollowers": "Схаваць падпісчыкаў", - "hideFollowersDescription": "Схаваць падпісчыкаў з вашага профілю.", + "hideFollowers": "Схаваць падпісантаў", + "hideFollowersDescription": "Схаваць падпісантаў з вашага профілю.", "hideFollowing": "Схаваць падпіскі", "hideFollowingDescription": "Схаваць карыстальнікаў, на якіх вы падпісаны, з вашага профілю." }, @@ -221,11 +221,11 @@ "title": "Запыты сяброўства", "description": "Хто можа адпраўляць мяне запыты сяброўства.", "anyone": "Усе", - "serversOnly": "Толькі ўдзельнікі агульных са мной сервероў", - "nobody": "Ніхто" + "serversOnly": "Толькі ўдзельнікі агульных са мной сэрвэраў", + "nobody": "Аніхто" }, "lastOnline": { - "description": "Задайце, хто можа бачыць, калі вы былі ў сетцы апошні раз.", + "description": "Хто можа бачыць, калі вы былі ў сетцы ў апошні раз.", "title": "Апошні раз у сетцы" } } @@ -233,72 +233,72 @@ "userContextMenu": { "viewProfile": "Праглядзець профіль", - "sendMessage": "Адправіць паведамленне", + "sendMessage": "Адправіць паведамленьне", "copyId": "Скапіяваць ID", "editRoles": "Рэдагаваць ролі", "kick": "Выгнаць", - "ban": "Заблакіраваць", - "changeNickname": "Змяніць нікнэйм" + "ban": "Заблякаваць", + "changeNickname": "Зьмяніць нікнэйм" }, "kickServerMemberModal": { "title": "Выгнаць {{username}}", "kickButton": "Выгнаць", "kicking": "Выганяем...", "backButton": "Назад", - "message": "Вы ўпэўнены, што хочаце выгнаць <1>{{username}}?" + "message": "Вы ўпэўненыя, што хочаце выгнаць <1>{{username}}?" }, "editServerRolesModal": { "title": "Рэдагаваць ролі" }, "banModal": { - "title": "Заблакіраваць {{username}}?", - "message": "Вы ўпэўнены, што хочаце заблакіраваць <1>{{username}}?", - "deletePastMessagesCheckbox": "Выдаліць паведамленні карыстальніка за апошнія 7 гадзін.", - "banButton": "Заблакіраваць", - "banning": "Блакіруем..." + "title": "Заблякаваць {{username}}?", + "message": "Вы ўпэўнены, што хочаце заблякаваць <1>{{username}}?", + "deletePastMessagesCheckbox": "Выдаліць паведамленьні, адпраўленыя за апошнія 7 гадзін.", + "banButton": "Заблякаваць", + "banning": "Блякуем..." }, "messageContextMenu": { - "editMessage": "Адрэдагаваць паведамленне", - "deleteMessage": "Выдаліць паведамленне", - "copyMessage": "Скапіяваць паведамленне", + "editMessage": "Адрэдагаваць паведамленьне", + "deleteMessage": "Выдаліць паведамленьне", + "copyMessage": "Скапіяваць паведамленьне", "copyId": "Скапіяваць ID" }, "profile": { "followButton": "Падпісацца", "unfollowButton": "Адпісацца", - "removeFriendButton": "Выдаліць з сяброў", + "removeFriendButton": "Выдаліць зь сяброў", "addFriendButton": "Дадаць у сябры", - "pendingRequest": "У чаканні", + "pendingRequest": "У чаканьні", "acceptRequestButton": "Прыняць запыт", "messageButton": "Напісаць", "mutualFriends": "Агульныя сябры ({{count}})", - "mutualServers": "Агульныя серверы ({{count}})", - "postsTab": "Публікацыі", - "followersTab": "Падпісчыкі", + "mutualServers": "Агульныя сэрвэры ({{count}})", + "postsTab": "Публікацыйі", + "followersTab": "Падпісанты", "followingTab": "Падпіскі", - "postsAndRepliesTab": "Публікацыі й адказы ({{count}})", - "likedPostsTab": "Упадабаныя публікацыі ({{count}})" + "postsAndRepliesTab": "Публікацыйі й адказы ({{count}})", + "likedPostsTab": "Упадабаныя публікацыйі ({{count}})" }, "posts": { "deletePostModal": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэтую публікацыю?", + "message": "Вы ўпэўненыя, што хочаце выдаліць гэтую публікацыю?", "cancelButton": "Скасаваць", "deleteButton": "Выдаліць" }, "postWasDeleted": "Гэта публікацыя была выдалена!", - "someoneLikedYourPost": "<1>{{username}} упадабаў вашу публікацыю!", + "someoneLikedYourPost": "<1>{{username}} упадабаў вашую публікацыю!", "someoneFollowedYou": "<1>{{username}} падпісаўся на вас!", - "someoneRepliedToPost": "<1>{{username}} пакінуў адказ на вашу публікацыю!", + "someoneRepliedToPost": "<1>{{username}} пакінуў адказ на вашую публікацыю!", "createAPostInputPlaceholder": "Дадаць публікацыю...", - "replyInputPlaceholder": "Адказ...", + "replyInputPlaceholder": "Адказваем...", "replyButton": "Адказаць", "createButton": "Дадаць", - "pollOptions": "Варыянты ў апытанні", + "pollOptions": "Варыянты апытаньня", "optionNumberPlaceholder": "Вырыянт {{number}}" }, "informationDrawer": { "title": "Інфармацыя", - "channelNotice": "Папярэджанне канала", + "channelNotice": "Папярэджаньне каналу", "attachments": "Далучаныя файлы", "attachmentsBack": "Назад", "members": "Удзельнікі" From 202836c1602f2f85cc7b2593ae36395212f7150f Mon Sep 17 00:00:00 2001 From: Supertiger Date: Tue, 17 Sep 2024 11:29:40 +0100 Subject: [PATCH 0002/2090] Add silent message support --- src/chat-api/RawData.ts | 1 + src/chat-api/services/MessageService.ts | 6 ++ src/chat-api/store/useMessages.ts | 18 ++++- src/common/Sound.ts | 67 +++++++++++++------ src/components/message-pane/MessagePane.tsx | 10 +++ .../message-pane/message-item/MessageItem.tsx | 10 +++ 6 files changed, 89 insertions(+), 23 deletions(-) diff --git a/src/chat-api/RawData.ts b/src/chat-api/RawData.ts index 1642c8868..3b58bc47d 100644 --- a/src/chat-api/RawData.ts +++ b/src/chat-api/RawData.ts @@ -39,6 +39,7 @@ export interface HtmlEmbedItem { export interface RawMessage { id: string; channelId: string; + silent?: boolean; content?: string; createdBy: RawUser; type: MessageType; diff --git a/src/chat-api/services/MessageService.ts b/src/chat-api/services/MessageService.ts index c60e2bbab..19464a2c4 100644 --- a/src/chat-api/services/MessageService.ts +++ b/src/chat-api/services/MessageService.ts @@ -54,6 +54,7 @@ interface PostMessageOpts { attachment?: File; replyToMessageIds?: string[]; mentionReplies?: boolean; + silent?: boolean; googleDriveAttachment?: { id: string; mime: string; @@ -65,6 +66,8 @@ export const postMessage = async (opts: PostMessageOpts) => { let body: any = { content: opts.content?.trim() || undefined, + ...(opts.silent ? { silent: true } : {}), + ...(opts.replyToMessageIds?.length ? { replyToMessageIds: opts.replyToMessageIds, @@ -89,6 +92,9 @@ export const postMessage = async (opts: PostMessageOpts) => { fd.append("replyToMessageIds", JSON.stringify(opts.replyToMessageIds)); fd.append("mentionReplies", String(opts.mentionReplies)); } + if (opts.silent) { + fd.append("silent", String(opts.silent)); + } fd.append("attachment", opts.attachment); body = fd; diff --git a/src/chat-api/store/useMessages.ts b/src/chat-api/store/useMessages.ts index cff313fe4..e4cde1e2d 100644 --- a/src/chat-api/store/useMessages.ts +++ b/src/chat-api/store/useMessages.ts @@ -143,19 +143,35 @@ const updateLocalMessage = async ( setMessages(channelId, index, message); }; +const silentRegex = /^@silent([\s]|$)/; + const sendAndStoreMessage = async (channelId: string, content?: string) => { const channels = useChannels(); const channelProperties = useChannelProperties(); const properties = channelProperties.get(channelId); + const file = properties?.attachment; const tempMessageId = `${Date.now()}-${Math.random()}`; const channel = channels.get(channelId); + const isSilent = !!content && silentRegex.test(content); + + if (content && isSilent) { + if (content === "@silent") { + content = undefined; + if (!file) return; + } else { + content = content.replace(silentRegex, "").trim(); + if (!content && !file) return; + } + } + const user = account.user(); if (!user) return; const localMessage: Message = { id: "", tempId: tempMessageId, + silent: isSilent, channelId, content, createdAt: Date.now(), @@ -204,7 +220,6 @@ const sendAndStoreMessage = async (channelId: string, content?: string) => { properties?.attachment && properties.attachment.size > 12 * 1024 * 1024; const shouldUploadToGoogleDrive = !isImage || isMoreThan12MB; - const file = properties?.attachment; let googleDriveFileId: string | undefined; if (file && shouldUploadToGoogleDrive) { try { @@ -254,6 +269,7 @@ const sendAndStoreMessage = async (channelId: string, content?: string) => { const message: void | Message = await postMessage({ content, + silent: isSilent, channelId, socketId: socketClient.id(), replyToMessageIds, diff --git a/src/common/Sound.ts b/src/common/Sound.ts index d90366123..6cd2f289d 100644 --- a/src/common/Sound.ts +++ b/src/common/Sound.ts @@ -1,4 +1,9 @@ -import { getStorageBoolean, getStorageNumber, getStorageObject, StorageKeys } from "./localStorage"; +import { + getStorageBoolean, + getStorageNumber, + getStorageObject, + StorageKeys, +} from "./localStorage"; import useStore from "@/chat-api/store/useStore"; import { UserStatus } from "@/chat-api/store/useUsers"; import { RawMessage, ServerNotificationSoundMode } from "@/chat-api/RawData"; @@ -22,13 +27,11 @@ export const Sounds = [ "soft-notice", "start", "system-notification", - "the-notification-email" + "the-notification-email", ] as const; - - const audio = new Audio(); -export function playSound(name: typeof Sounds[number] = "default") { +export function playSound(name: (typeof Sounds)[number] = "default") { if (name === "nerimity-mute") return; audio.src = `/assets/sounds/${name}.mp3`; audio.volume = getStorageNumber(StorageKeys.NOTIFICATION_VOLUME, 10) / 100; @@ -36,56 +39,76 @@ export function playSound(name: typeof Sounds[number] = "default") { audio.play(); } - interface MessageNotificationOpts { - force?: boolean - message?: RawMessage + force?: boolean; + message?: RawMessage; serverId?: string; } export function playMessageNotification(opts?: MessageNotificationOpts) { + if (opts?.message?.silent) return; if (opts?.force) return playSound(getCustomSound("MESSAGE")); if (getStorageBoolean(StorageKeys.ARE_NOTIFICATIONS_MUTED, false)) return; - const {account, users, serverMembers} = useStore(); + const { account, users, serverMembers } = useStore(); const userId = account.user()?.id; const user = users.get(userId!); if (user?.presence()?.status === UserStatus.DND) return; - const notificationSoundMode = !opts?.serverId ? undefined : account.getCombinedNotificationSettings(opts.serverId, opts.message?.channelId)?.notificationSoundMode; + const notificationSoundMode = !opts?.serverId + ? undefined + : account.getCombinedNotificationSettings( + opts.serverId, + opts.message?.channelId + )?.notificationSoundMode; if (notificationSoundMode === ServerNotificationSoundMode.MUTE) return; - + if (opts?.message) { - const mentionedMe = opts.message.mentions?.find(m => m.id === account.user()?.id); + const mentionedMe = opts.message.mentions?.find( + (m) => m.id === account.user()?.id + ); if (mentionedMe) { return playSound(getCustomSound("MESSAGE_MENTION")); } - const quoteMention = opts.message.quotedMessages?.find(m => m.createdBy?.id === userId); + const quoteMention = opts.message.quotedMessages?.find( + (m) => m.createdBy?.id === userId + ); - const replyMention = opts.message.mentionReplies && opts.message.replyMessages.find(m => m.replyToMessage?.createdBy?.id === userId); + const replyMention = + opts.message.mentionReplies && + opts.message.replyMessages.find( + (m) => m.replyToMessage?.createdBy?.id === userId + ); if (quoteMention || replyMention) { return playSound(getCustomSound("MESSAGE_MENTION")); } - const everyoneMentioned = opts.message.content?.includes("[@:e]"); if (everyoneMentioned && opts.serverId) { - const member = serverMembers.get(opts.serverId, opts.message.createdBy.id); - const hasPerm = member?.isServerCreator() || member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE); + const member = serverMembers.get( + opts.serverId, + opts.message.createdBy.id + ); + const hasPerm = + member?.isServerCreator() || + member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE); if (hasPerm) { return playSound(getCustomSound("MESSAGE_MENTION")); } } } - - if (notificationSoundMode === ServerNotificationSoundMode.MENTIONS_ONLY) return; + + if (notificationSoundMode === ServerNotificationSoundMode.MENTIONS_ONLY) + return; playSound(getCustomSound("MESSAGE")); } -function getCustomSound (type: "MESSAGE" | "MESSAGE_MENTION") { - const storage = getStorageObject<{[key: string]: typeof Sounds[number] | undefined}>(StorageKeys.NOTIFICATION_SOUNDS, {}); +function getCustomSound(type: "MESSAGE" | "MESSAGE_MENTION") { + const storage = getStorageObject<{ + [key: string]: (typeof Sounds)[number] | undefined; + }>(StorageKeys.NOTIFICATION_SOUNDS, {}); return storage[type]; -} \ No newline at end of file +} diff --git a/src/components/message-pane/MessagePane.tsx b/src/components/message-pane/MessagePane.tsx index 5e80c94cc..974ce27c4 100644 --- a/src/components/message-pane/MessagePane.tsx +++ b/src/components/message-pane/MessagePane.tsx @@ -1515,6 +1515,13 @@ function FloatingUserSuggestions(props: { username: "someone", }), }, + { + user: () => ({ + special: true, + id: "si", + username: "silent", + }), + }, ] as any[], props.search, { @@ -1624,6 +1631,9 @@ function UserSuggestionItem(props: {
Mentions someone.
+ +
Silent message.
+
); } diff --git a/src/components/message-pane/message-item/MessageItem.tsx b/src/components/message-pane/message-item/MessageItem.tsx index 4618607a6..4b941c82c 100644 --- a/src/components/message-pane/message-item/MessageItem.tsx +++ b/src/components/message-pane/message-item/MessageItem.tsx @@ -80,6 +80,7 @@ import { stat } from "fs"; import { AudioEmbed } from "./AudioEmbed"; import { ImagePreviewModal } from "@/components/ui/ImagePreviewModal"; import { ButtonsEmbed } from "./ButtonsEmbed"; +import { Tooltip } from "@/components/ui/Tooltip"; const DeleteMessageModal = lazy( () => import("../message-delete-modal/MessageDeleteModal") @@ -226,6 +227,15 @@ const Details = (props: DetailsProps) => (
Bot
{formatTimestamp(props.message.createdAt)}
+ + + + + ); From 1eb7f44b7f57db1653c8d148263f841dda5b1197 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Tue, 17 Sep 2024 11:31:50 +0100 Subject: [PATCH 0003/2090] change anchor --- src/components/message-pane/message-item/MessageItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/message-pane/message-item/MessageItem.tsx b/src/components/message-pane/message-item/MessageItem.tsx index 4b941c82c..44b31b72a 100644 --- a/src/components/message-pane/message-item/MessageItem.tsx +++ b/src/components/message-pane/message-item/MessageItem.tsx @@ -228,7 +228,7 @@ const Details = (props: DetailsProps) => (
{formatTimestamp(props.message.createdAt)}
- + Date: Wed, 18 Sep 2024 09:44:46 +0100 Subject: [PATCH 0004/2090] add font-variant-numeric: tabular-nums to timestamps --- src/components/Markup.scss | 117 +++++++++--------- src/components/activity/Activity.tsx | 4 +- src/components/activity/styles.module.scss | 17 ++- .../FloatingProfile.module.scss | 4 + .../floating-profile/FloatingProfile.tsx | 6 +- src/components/markup/TimestampMention.tsx | 77 ++++++++---- 6 files changed, 137 insertions(+), 88 deletions(-) diff --git a/src/components/Markup.scss b/src/components/Markup.scss index 6b75569c6..a5275f244 100644 --- a/src/components/Markup.scss +++ b/src/components/Markup.scss @@ -1,6 +1,6 @@ .markup { - white-space: pre-wrap; line-height: 1.3; + white-space: pre-wrap; } .markup .heading { @@ -25,39 +25,39 @@ } .markup .code { - color: #d0d0d0; - background: rgba(0, 0, 0, 0.6); - padding-left: 1px; padding-right: 1px; + padding-left: 1px; border-radius: 4px; + color: #d0d0d0; + background: rgba(0, 0, 0, 0.6); } .markup blockquote { - margin: 0; + width: fit-content; padding: 0.3rem 0.6rem; + margin: 0; border-left: 2px var(--primary-color) solid; - background: rgba(255, 255, 255, 0.1); border-radius: 4px; - width: fit-content; + background: rgba(255, 255, 255, 0.1); &.inline { display: inline; padding: 0; - padding-left: 4px; padding-right: 4px; + padding-left: 4px; } } .markup .quoteContainer { display: flex; + align-items: start; flex-direction: column; - background: rgba(255, 255, 255, 0.02); + width: fit-content; padding: 6px; - gap: 3px; - border-radius: 6px; border: solid 1px rgba(255, 255, 255, 0.1); - align-items: start; border-left: 2px var(--primary-color) solid; - width: fit-content; + border-radius: 6px; + background: rgba(255, 255, 255, 0.02); + gap: 3px; } .markup .quoteContainer .headerContainer { @@ -79,57 +79,57 @@ .markup .hiddenQuote { display: inline-flex; - color: rgba(255, 255, 255, 0.5); - background: rgba(0, 0, 0, 0.2); - border-radius: 6px; + align-items: baseline; padding: 2px; - padding-left: 5px; padding-right: 5px; + padding-left: 5px; margin-top: 1px; margin-bottom: 1px; - align-items: baseline; + border-radius: 6px; + color: rgba(255, 255, 255, 0.5); + background: rgba(0, 0, 0, 0.2); } .markup .code-block { - background: rgba(0, 0, 0, 0.6); - border-radius: 8px; padding: 5px; + border-radius: 8px; + background: rgba(0, 0, 0, 0.6); &.no-wrap { .content { + overflow: auto; word-break: keep-all; white-space: pre; - overflow: auto; } } .header { display: flex; align-items: center; - font-weight: bold; padding: 3px; - border-bottom: solid 1px rgba(255, 255, 255, 0.4); margin-bottom: 5px; + border-bottom: solid 1px rgba(255, 255, 255, 0.4); + font-weight: bold; .lang-name { margin-right: 10px; } .button { + padding: 3px; + border-radius: 4px; + opacity: 0.8; cursor: pointer; user-select: none; - opacity: 0.8; - border-radius: 4px; transition: 0.2s; - padding: 3px; - &.active { - color: var(--primary-color); - opacity: 1; - } &:hover { opacity: 1; background-color: rgb(71, 71, 71); } + &.active { + opacity: 1; + color: var(--primary-color); + } } } } @@ -139,12 +139,19 @@ border-radius: 3px; &:not(.spoiled) { - background-color: #0e0f10; - user-select: none; - cursor: pointer; border-radius: 4px; + background-color: #0e0f10; -webkit-box-decoration-break: clone; box-decoration-break: clone; + cursor: pointer; + user-select: none; + + &:hover { + background-color: #1c1e20; + .code-block::before { + background-color: #1c1e20; + } + } .emoji { opacity: 0; @@ -154,67 +161,63 @@ position: relative; overflow: hidden; &::before { - content: "spoiler"; position: absolute; - inset: 0; - background-color: #0e0f10; z-index: 11111111; + background-color: #0e0f10; + content: "spoiler"; + inset: 0; } } * { - pointer-events: none; color: transparent; - } - - &:hover { - background-color: #1c1e20; - .code-block::before { - background-color: #1c1e20; - } + pointer-events: none; } } } .markup.largeEmoji .emoji { - height: 48px; width: 48px; + height: 48px; } .markup .emoji { - height: 1.572em; width: 1.572em; - vertical-align: -0.4em; + height: 1.572em; cursor: pointer; + vertical-align: -0.4em; object-fit: contain; } .markup .mention { display: inline-flex; - color: var(--primary-color); - background: rgba(0, 0, 0, 0.2); - border-radius: 8px; + align-items: baseline; padding: 3px; - padding-left: 5px; padding-right: 5px; + padding-left: 5px; margin-top: 1px; margin-bottom: 1px; - align-items: baseline; + border-radius: 8px; + color: var(--primary-color); + background: rgba(0, 0, 0, 0.2); text-decoration: none; + &:hover { + background: rgba(0, 0, 0, 0.6); + } + &.timestamp { + font-variant-numeric: tabular-nums; + } .icon { align-self: center; margin-right: 4px; opacity: 0.8; } .type { - opacity: 0.5; margin-right: 5px; + opacity: 0.5; } .avatar { - margin-right: 5px; align-self: center; - } - &:hover { - background: rgba(0, 0, 0, 0.6); + margin-right: 5px; } } diff --git a/src/components/activity/Activity.tsx b/src/components/activity/Activity.tsx index b04f83464..1c5cfb4d9 100644 --- a/src/components/activity/Activity.tsx +++ b/src/components/activity/Activity.tsx @@ -82,10 +82,10 @@ export const RichProgressBar = (props: { return (
- + {playedFor()} - + {endsAt()}
diff --git a/src/components/activity/styles.module.scss b/src/components/activity/styles.module.scss index cb7701cdb..9c6b97878 100644 --- a/src/components/activity/styles.module.scss +++ b/src/components/activity/styles.module.scss @@ -1,4 +1,4 @@ -.richProgressBar{ +.richProgressBar { display: flex; flex-direction: column; gap: 4px; @@ -7,14 +7,21 @@ justify-content: space-between; } .progressBar { - flex: 1;; - background-color: rgba(255, 255, 255, 0.1); - border-radius: 6px; overflow: hidden; + flex: 1; + border-radius: 6px; + background-color: rgba(255, 255, 255, 0.1); } .progress { height: 4px; border-radius: 999999999px; background-color: var(--primary-color); } -} \ No newline at end of file +} + +.playedFor { + font-variant-numeric: tabular-nums; +} +.endsAt { + font-variant-numeric: tabular-nums; +} diff --git a/src/components/floating-profile/FloatingProfile.module.scss b/src/components/floating-profile/FloatingProfile.module.scss index 5cd17419d..6f554f0cf 100644 --- a/src/components/floating-profile/FloatingProfile.module.scss +++ b/src/components/floating-profile/FloatingProfile.module.scss @@ -220,3 +220,7 @@ body .postItemContainer { flex: 1; } } + +.playedFor { + font-variant-numeric: tabular-nums; +} diff --git a/src/components/floating-profile/FloatingProfile.tsx b/src/components/floating-profile/FloatingProfile.tsx index 468473c48..5578d2083 100644 --- a/src/components/floating-profile/FloatingProfile.tsx +++ b/src/components/floating-profile/FloatingProfile.tsx @@ -725,7 +725,7 @@ export const UserActivity = (props: { {activity()?.subtitle} - + {playedFor()} @@ -742,7 +742,9 @@ export const UserActivity = (props: {
- For {playedFor()} + + For {playedFor()} + diff --git a/src/components/markup/TimestampMention.tsx b/src/components/markup/TimestampMention.tsx index 58cd1a503..e4ba7e0b5 100644 --- a/src/components/markup/TimestampMention.tsx +++ b/src/components/markup/TimestampMention.tsx @@ -3,10 +3,13 @@ import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js"; import Icon from "../ui/icon/Icon"; export enum TimestampType { - RELATIVE = "tr" + RELATIVE = "tr", } -export function TimestampMention(props: { type: TimestampType; timestamp: number }) { +export function TimestampMention(props: { + type: TimestampType; + timestamp: number; +}) { const [formattedTime, setFormattedTime] = createSignal("..."); const updateTime = () => { @@ -14,52 +17,82 @@ export function TimestampMention(props: { type: TimestampType; timestamp: number return setFormattedTime(timeSince(props.timestamp)); } }; - - createEffect(on([() => props.timestamp, () => props.type] ,() => { - updateTime(); - const timeoutId = setInterval(updateTime, 1000); - onCleanup(() => { - clearInterval(timeoutId); - }); - })); + createEffect( + on([() => props.timestamp, () => props.type], () => { + updateTime(); + const timeoutId = setInterval(updateTime, 1000); + + onCleanup(() => { + clearInterval(timeoutId); + }); + }) + ); return ( -
- +
+ {formattedTime()}
); } - function timeSince(timestamp: number) { const now = new Date(); const rawSecondsPast = (now.getTime() - timestamp) / 1000; const secondsPast = Math.abs(rawSecondsPast); - const text = (value: string) => rawSecondsPast < 0 ? `In ${value}` : `${value} ago`; - + const text = (value: string) => + rawSecondsPast < 0 ? `In ${value}` : `${value} ago`; if (secondsPast < 60) { return text(Math.trunc(secondsPast) + " seconds"); } if (secondsPast < 3600) { - return text(Math.trunc(secondsPast / 60) + " minutes " + (Math.trunc(secondsPast) % 60 ) + " seconds"); + return text( + Math.trunc(secondsPast / 60) + + " minutes " + + (Math.trunc(secondsPast) % 60) + + " seconds" + ); } if (secondsPast <= 86400) { - return text(Math.trunc(secondsPast / 3600) + " hours " + (Math.trunc(secondsPast / 60) % 60 ) + " minutes"); + return text( + Math.trunc(secondsPast / 3600) + + " hours " + + (Math.trunc(secondsPast / 60) % 60) + + " minutes" + ); } if (secondsPast <= 604800) { - return text(Math.trunc(secondsPast / 86400) + " days " + (Math.trunc(secondsPast / 3600) % 24 ) + " hours"); + return text( + Math.trunc(secondsPast / 86400) + + " days " + + (Math.trunc(secondsPast / 3600) % 24) + + " hours" + ); } if (secondsPast <= 2629743) { - return text(Math.trunc(secondsPast / 604800) + " weeks " + (Math.trunc(secondsPast / 86400) % 7 ) + " days"); + return text( + Math.trunc(secondsPast / 604800) + + " weeks " + + (Math.trunc(secondsPast / 86400) % 7) + + " days" + ); } if (secondsPast <= 31556926) { - return text(Math.trunc(secondsPast / 2629743) + " months " + (Math.trunc(secondsPast / 604800) % 4 ) + " weeks"); + return text( + Math.trunc(secondsPast / 2629743) + + " months " + + (Math.trunc(secondsPast / 604800) % 4) + + " weeks" + ); } return text(Math.trunc(secondsPast / 31556926) + " years"); - -} \ No newline at end of file +} From 75a2fce8fa56e29a33b69fe52636bc2aa8f609c9 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Wed, 18 Sep 2024 09:46:15 +0100 Subject: [PATCH 0005/2090] add to more places --- src/components/profile-pane/ProfilePane.tsx | 3 ++- src/components/profile-pane/styles.module.scss | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/profile-pane/ProfilePane.tsx b/src/components/profile-pane/ProfilePane.tsx index fbdaa5db1..0fea2cd51 100644 --- a/src/components/profile-pane/ProfilePane.tsx +++ b/src/components/profile-pane/ProfilePane.tsx @@ -778,7 +778,7 @@ const UserActivity = (props: { {activity()?.subtitle} - + {playedFor()} @@ -797,6 +797,7 @@ const UserActivity = (props: { Date: Wed, 18 Sep 2024 10:10:24 +0100 Subject: [PATCH 0006/2090] Fix attachment count decreasing incorrectly --- src/chat-api/events/messageEvents.ts | 152 +++++++++++++------- src/components/right-drawer/RightDrawer.tsx | 26 ---- 2 files changed, 103 insertions(+), 75 deletions(-) diff --git a/src/chat-api/events/messageEvents.ts b/src/chat-api/events/messageEvents.ts index 4ce3c1365..8542be77e 100644 --- a/src/chat-api/events/messageEvents.ts +++ b/src/chat-api/events/messageEvents.ts @@ -14,69 +14,94 @@ import useServerMembers from "../store/useServerMembers"; import { ROLE_PERMISSIONS } from "../Bitwise"; import useFriends from "../store/useFriends"; +export function onMessageCreated(payload: { + socketId: string; + message: RawMessage; +}) { + const channels = useChannels(); + const channel = channels.get(payload.message.channelId); - -export function onMessageCreated(payload: {socketId: string, message: RawMessage}) { + batch(() => { + if (payload.message.attachments?.length) { + const attachmentCreatedCount = payload.message.attachments.length; + const channelAttachments = channel?._count?.attachments; + const attachmentCount = channelAttachments || 0; + channel?.update({ + _count: { + attachments: attachmentCount + attachmentCreatedCount, + }, + }); + } + }); if (socketClient.id() === payload.socketId) return; const header = useHeader(); const messages = useMessages(); - const channels = useChannels(); const mentions = useMention(); const members = useServerMembers(); const users = useUsers(); const account = useAccount(); - const channel = channels.get(payload.message.channelId); const friends = useFriends(); - const {hasFocus} = useWindowProperties(); + const { hasFocus } = useWindowProperties(); const accountUser = account.user(); - const hasBlockedRecipient = friends.get(payload.message.createdBy.id)?.status === FriendStatus.BLOCKED; + const hasBlockedRecipient = + friends.get(payload.message.createdBy.id)?.status === FriendStatus.BLOCKED; batch(() => { - channel?.updateLastMessaged(payload.message.createdAt); if (accountUser?.id === payload.message.createdBy.id) { channel?.updateLastSeen(payload.message.createdAt + 1); - } - else if (!channel || channel.recipient()) { + } else if (!channel || channel.recipient()) { const user = users.get(payload.message.createdBy.id); if (!user) { users.set(payload.message.createdBy); } - } - const mentionCount = () => mentions.get(payload.message.channelId)?.count || 0; + const mentionCount = () => + mentions.get(payload.message.channelId)?.count || 0; const isMentioned = () => { if (hasBlockedRecipient) return false; const everyoneMentioned = payload.message.content?.includes("[@:e]"); if (everyoneMentioned && channel?.serverId) { - const member = members.get(channel.serverId, payload.message.createdBy.id); - const hasPerm = member?.isServerCreator() || member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE); + const member = members.get( + channel.serverId, + payload.message.createdBy.id + ); + const hasPerm = + member?.isServerCreator() || + member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE); if (hasPerm) return true; } - const mention = payload.message.mentions?.find(u => u.id === accountUser?.id); + const mention = payload.message.mentions?.find( + (u) => u.id === accountUser?.id + ); if (mention) return true; - const quoteMention = payload.message.quotedMessages?.find(m => m.createdBy?.id === accountUser?.id); + const quoteMention = payload.message.quotedMessages?.find( + (m) => m.createdBy?.id === accountUser?.id + ); if (quoteMention) return true; - - const replyMention = payload.message.mentionReplies && payload.message.replyMessages.find(m => m.replyToMessage?.createdBy?.id === accountUser?.id); + + const replyMention = + payload.message.mentionReplies && + payload.message.replyMessages.find( + (m) => m.replyToMessage?.createdBy?.id === accountUser?.id + ); return replyMention; }; - + if (payload.message.createdBy.id !== accountUser?.id) { - if (!channel?.serverId || isMentioned()) { mentions.set({ channelId: payload.message.channelId, userId: payload.message.createdBy.id, count: mentionCount() + 1, - serverId: channel?.serverId + serverId: channel?.serverId, }); } } @@ -84,48 +109,76 @@ export function onMessageCreated(payload: {socketId: string, message: RawMessage messages.pushMessage(payload.message.channelId, payload.message); }); - // only play notifications if: + // only play notifications if: // it does not have focus (has focus) // channel is not selected (is selected) if (payload.message.createdBy.id !== accountUser?.id) { if (hasBlockedRecipient) return; - const isChannelSelected = header.details().id === "MessagePane" && header.details().channelId === payload.message.channelId; + const isChannelSelected = + header.details().id === "MessagePane" && + header.details().channelId === payload.message.channelId; if (hasFocus() && isChannelSelected) return; - playMessageNotification({message: payload.message, serverId: channel?.serverId}); + playMessageNotification({ + message: payload.message, + serverId: channel?.serverId, + }); createDesktopNotification(payload.message); } - } -export function onMessageUpdated(payload: {channelId: string, messageId: string, updated: Partial}) { +export function onMessageUpdated(payload: { + channelId: string; + messageId: string; + updated: Partial; +}) { const messages = useMessages(); - messages.updateLocalMessage({...payload.updated, sentStatus: undefined}, payload.channelId, payload.messageId); + messages.updateLocalMessage( + { ...payload.updated, sentStatus: undefined }, + payload.channelId, + payload.messageId + ); } - -export function onMessageDeleted(payload: {channelId: string, messageId: string}) { +export function onMessageDeleted(payload: { + channelId: string; + messageId: string; + deletedAttachmentCount: number; +}) { const messages = useMessages(); messages.locallyRemoveMessage(payload.channelId, payload.messageId); + + if (payload.deletedAttachmentCount) { + const channels = useChannels(); + const channel = channels.get(payload.channelId); + const attachmentDeletedCount = payload.deletedAttachmentCount; + const channelAttachments = channel?._count?.attachments; + const attachmentCount = channelAttachments || attachmentDeletedCount; + channel?.update({ + _count: { + attachments: attachmentCount - attachmentDeletedCount, + }, + }); + } } export function onMessageDeletedBatch(payload: { - userId: string - serverId: string - fromTime: number, - toTime: number, + userId: string; + serverId: string; + fromTime: number; + toTime: number; }) { const messages = useMessages(); messages.locallyRemoveServerMessagesBatch(payload); } interface ReactionAddedPayload { - messageId: string, - channelId: string, - count: number - reactedByUserId: string, - emojiId?: string, - name: string, - gif?: boolean, + messageId: string; + channelId: string; + count: number; + reactedByUserId: string; + emojiId?: string; + name: string; + gif?: boolean; } export function onMessageReactionAdded(payload: ReactionAddedPayload) { @@ -138,26 +191,27 @@ export function onMessageReactionAdded(payload: ReactionAddedPayload) { name: payload.name, emojiId: payload.emojiId || null, gif: payload.gif, - ...(reactedByMe? { reacted: true } : undefined) + ...(reactedByMe ? { reacted: true } : undefined), }); } interface ReactionRemovedPayload { - messageId: string, - channelId: string, - count: number - reactionRemovedByUserId: string, - emojiId?: string, - name: string, + messageId: string; + channelId: string; + count: number; + reactionRemovedByUserId: string; + emojiId?: string; + name: string; } export function onMessageReactionRemoved(payload: ReactionRemovedPayload) { const messages = useMessages(); const account = useAccount(); - const reactionRemovedByMe = account.user()?.id === payload.reactionRemovedByUserId; + const reactionRemovedByMe = + account.user()?.id === payload.reactionRemovedByUserId; messages.updateMessageReaction(payload.channelId, payload.messageId, { count: payload.count, name: payload.name, emojiId: payload.emojiId, - ...(reactionRemovedByMe ? { reacted: false } : undefined) + ...(reactionRemovedByMe ? { reacted: false } : undefined), }); -} \ No newline at end of file +} diff --git a/src/components/right-drawer/RightDrawer.tsx b/src/components/right-drawer/RightDrawer.tsx index 7de57b9c7..61d41ffb8 100644 --- a/src/components/right-drawer/RightDrawer.tsx +++ b/src/components/right-drawer/RightDrawer.tsx @@ -307,32 +307,6 @@ const MainDrawer = (props: { onShowAttachmentClick(): void }) => { const channel = () => channels.get(params.channelId!); - const incrAttachments = (channelId: string) => { - const channel = channels.get(channelId); - - const count = channel?._count?.attachments || 0; - channel?.update({ _count: { attachments: count + 1 } }); - }; - - const decrAttachments = (channelId: string) => { - const channel = channels.get(channelId); - - const count = channel?._count?.attachments || 1; - channel?.update({ _count: { attachments: count - 1 } }); - }; - - const onMessage = (payload: { message: RawMessage }) => { - const attachment = payload?.message.attachments?.[0]; - if (!attachment) return; - incrAttachments(payload.message.channelId); - }; - socketClient.useSocketOn(ServerEvents.MESSAGE_CREATED, onMessage); - - const onDelete = (payload: { messageId: string; channelId: string }) => { - decrAttachments(payload.channelId); - }; - socketClient.useSocketOn(ServerEvents.MESSAGE_DELETED, onDelete); - const cachedNotice = () => params.channelId ? getCachedNotice(() => params.channelId!) : undefined; From d6e7c2e7b93922cec07cc628122f6eb10ad3f692 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Thu, 19 Sep 2024 13:42:22 +0100 Subject: [PATCH 0007/2090] Add In-App Notification Previews experiment --- src/App.tsx | 6 + src/chat-api/events/messageEvents.ts | 2 + src/common/Sound.ts | 71 ++++--- src/common/experiments.tsx | 5 + src/common/localStorage.ts | 1 + .../InAppNotificationPreviews.module.scss | 58 +++++ .../InAppNotificationPreviews.tsx | 63 ++++++ .../in-app-notification-previews/index.tsx | 1 + .../useInAppNotificationPreviews.tsx | 101 +++++++++ .../message-log-area/MessageLogArea.tsx | 2 + .../settings/NotificationsSettings.tsx | 198 ++++++++++++++---- 11 files changed, 435 insertions(+), 73 deletions(-) create mode 100644 src/components/in-app-notification-previews/InAppNotificationPreviews.module.scss create mode 100644 src/components/in-app-notification-previews/InAppNotificationPreviews.tsx create mode 100644 src/components/in-app-notification-previews/index.tsx create mode 100644 src/components/in-app-notification-previews/useInAppNotificationPreviews.tsx diff --git a/src/App.tsx b/src/App.tsx index a8413335f..1b182f2bd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,6 +16,11 @@ import { Delay } from "./common/Delay"; const ConnectingStatusHeader = lazy( () => import("@/components/connecting-status-header/ConnectingStatusHeader") ); + +const InAppNotificationPreviews = lazy( + () => import("@/components/in-app-notification-previews") +); + export default function App() { const [, actions] = useTransContext(); const isAppPage = useMatch(() => "/app/*"); @@ -47,6 +52,7 @@ export default function App() { + ); diff --git a/src/chat-api/events/messageEvents.ts b/src/chat-api/events/messageEvents.ts index 8542be77e..a4396f7fb 100644 --- a/src/chat-api/events/messageEvents.ts +++ b/src/chat-api/events/messageEvents.ts @@ -13,6 +13,7 @@ import { createDesktopNotification } from "@/common/desktopNotification"; import useServerMembers from "../store/useServerMembers"; import { ROLE_PERMISSIONS } from "../Bitwise"; import useFriends from "../store/useFriends"; +import { pushMessageNotification } from "@/components/in-app-notification-previews/useInAppNotificationPreviews"; export function onMessageCreated(payload: { socketId: string; @@ -123,6 +124,7 @@ export function onMessageCreated(payload: { serverId: channel?.serverId, }); createDesktopNotification(payload.message); + pushMessageNotification(payload.message); } } diff --git a/src/common/Sound.ts b/src/common/Sound.ts index 6cd2f289d..f50c6ca38 100644 --- a/src/common/Sound.ts +++ b/src/common/Sound.ts @@ -64,40 +64,10 @@ export function playMessageNotification(opts?: MessageNotificationOpts) { if (notificationSoundMode === ServerNotificationSoundMode.MUTE) return; if (opts?.message) { - const mentionedMe = opts.message.mentions?.find( - (m) => m.id === account.user()?.id - ); - if (mentionedMe) { - return playSound(getCustomSound("MESSAGE_MENTION")); - } - - const quoteMention = opts.message.quotedMessages?.find( - (m) => m.createdBy?.id === userId - ); - - const replyMention = - opts.message.mentionReplies && - opts.message.replyMessages.find( - (m) => m.replyToMessage?.createdBy?.id === userId - ); - - if (quoteMention || replyMention) { + const mentioned = isMentioned(opts.message, opts.serverId); + if (mentioned) { return playSound(getCustomSound("MESSAGE_MENTION")); } - - const everyoneMentioned = opts.message.content?.includes("[@:e]"); - if (everyoneMentioned && opts.serverId) { - const member = serverMembers.get( - opts.serverId, - opts.message.createdBy.id - ); - const hasPerm = - member?.isServerCreator() || - member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE); - if (hasPerm) { - return playSound(getCustomSound("MESSAGE_MENTION")); - } - } } if (notificationSoundMode === ServerNotificationSoundMode.MENTIONS_ONLY) @@ -112,3 +82,40 @@ function getCustomSound(type: "MESSAGE" | "MESSAGE_MENTION") { }>(StorageKeys.NOTIFICATION_SOUNDS, {}); return storage[type]; } + +export function isMentioned(message: RawMessage, serverId?: string) { + const { account, serverMembers } = useStore(); + const userId = account.user()?.id; + + const mentionedMe = message.mentions?.find( + (m) => m.id === account.user()?.id + ); + if (mentionedMe) { + return true; + } + + const quoteMention = message.quotedMessages?.find( + (m) => m.createdBy?.id === userId + ); + + const replyMention = + message.mentionReplies && + message.replyMessages.find( + (m) => m.replyToMessage?.createdBy?.id === userId + ); + + if (quoteMention || replyMention) { + return true; + } + + const everyoneMentioned = message.content?.includes("[@:e]"); + if (everyoneMentioned && serverId) { + const member = serverMembers.get(serverId, message.createdBy.id); + const hasPerm = + member?.isServerCreator() || + member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE); + if (hasPerm) { + return true; + } + } +} diff --git a/src/common/experiments.tsx b/src/common/experiments.tsx index ef6e7fb7f..826605ce6 100644 --- a/src/common/experiments.tsx +++ b/src/common/experiments.tsx @@ -21,6 +21,11 @@ export const Experiments = [ name: "Quick Travel", description: "Press CTRL + SPACE to open Quick Travel.", }, + { + id: "IN_APP_NOTIFICATION_PREVIEWS", + name: "In-App Notification Previews", + description: "Show popup notifications in app.", + }, ] as const; export type ExperimentIds = (typeof Experiments)[number]["id"]; diff --git a/src/common/localStorage.ts b/src/common/localStorage.ts index 5aaf6101e..6de18cf05 100644 --- a/src/common/localStorage.ts +++ b/src/common/localStorage.ts @@ -7,6 +7,7 @@ export enum StorageKeys { APP_LANGUAGE = "appLanguage", FIRST_TIME = "firstTime", // After registering, this is set to true. ARE_NOTIFICATIONS_MUTED = "areNotificationsMuted", + IN_APP_NOTIFICATIONS_PREVIEW = "inAppNotificationsPreview", NOTIFICATION_VOLUME = "notificationVolume", ENABLE_DESKTOP_NOTIFICATION = "enableDesktopNotification", LAST_SELECTED_SERVER_CHANNELS = "lastSelectedServerChannels", diff --git a/src/components/in-app-notification-previews/InAppNotificationPreviews.module.scss b/src/components/in-app-notification-previews/InAppNotificationPreviews.module.scss new file mode 100644 index 000000000..999fd56ba --- /dev/null +++ b/src/components/in-app-notification-previews/InAppNotificationPreviews.module.scss @@ -0,0 +1,58 @@ +.backgroundContainer { + position: absolute; + z-index: 111; + top: 0; + right: 0; + left: 0; + display: flex; + justify-content: center; + pointer-events: none; +} +.container { + display: flex; + flex-direction: column; + width: 100%; + max-width: 500px; + height: 46px; + padding: 4px; + margin: 4px; + border: solid 1px rgba(255, 255, 255, 0.2); + border-radius: 8px; + background-color: var(--pane-color); + + user-select: none; + pointer-events: all; + gap: 4px; +} + +.infoContainer { + display: flex; + align-items: center; + flex: 1; +} +.progressBar { + flex-shrink: 0; + width: 100%; + height: 4px; + border-radius: 9999px; + background-color: rgba(255, 255, 255, 0.08); + .progress { + width: 100%; + height: 80%; + border-radius: 9999px; + } +} + +.title { + font-size: 12px; + font-weight: bold; +} +.body { + opacity: 0.8; + font-size: 12px; +} + +.infoContainer { + display: flex; + gap: 4px; +} diff --git a/src/components/in-app-notification-previews/InAppNotificationPreviews.tsx b/src/components/in-app-notification-previews/InAppNotificationPreviews.tsx new file mode 100644 index 000000000..ee39cf6d9 --- /dev/null +++ b/src/components/in-app-notification-previews/InAppNotificationPreviews.tsx @@ -0,0 +1,63 @@ +import { createEffect, createSignal, on, Show } from "solid-js"; +import Icon from "../ui/icon/Icon"; +import style from "./InAppNotificationPreviews.module.scss"; +import { useInAppNotificationPreviews } from "./useInAppNotificationPreviews"; +import { Markup } from "../Markup"; + +export default function InAppNotificationPreviews() { + const { notifications, removeNotification } = useInAppNotificationPreviews(); + const [progressEl, setProgressEl] = createSignal(); + + const notification = () => notifications()[0]; + + createEffect( + on([notification, progressEl], () => { + const progressElement = progressEl(); + if (!notification()) return; + if (!progressElement) return; + const anim = progressElement.animate( + [ + { composite: "replace", width: "100%" }, + { composite: "replace", width: "0%" }, + ], + { duration: 5000, fill: "forwards", endDelay: 300, delay: 300 } + ); + anim.onfinish = () => { + anim.cancel(); + removeNotification(notification()!); + }; + }) + ); + + return ( + +
+
+
+ +
+
{notification()?.title}
+
+ +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/in-app-notification-previews/index.tsx b/src/components/in-app-notification-previews/index.tsx new file mode 100644 index 000000000..918cf77c3 --- /dev/null +++ b/src/components/in-app-notification-previews/index.tsx @@ -0,0 +1 @@ +export { default } from "./InAppNotificationPreviews"; diff --git a/src/components/in-app-notification-previews/useInAppNotificationPreviews.tsx b/src/components/in-app-notification-previews/useInAppNotificationPreviews.tsx new file mode 100644 index 000000000..4e7c9f475 --- /dev/null +++ b/src/components/in-app-notification-previews/useInAppNotificationPreviews.tsx @@ -0,0 +1,101 @@ +import { ROLE_PERMISSIONS } from "@/chat-api/Bitwise"; +import { ServerNotificationPingMode } from "@/chat-api/RawData"; +import { Channel } from "@/chat-api/store/useChannels"; +import { Message } from "@/chat-api/store/useMessages"; +import useStore from "@/chat-api/store/useStore"; +import { isExperimentEnabled } from "@/common/experiments"; +import { + getStorageBoolean, + getStorageNumber, + getStorageObject, + getStorageString, + StorageKeys, +} from "@/common/localStorage"; +import { isMentioned } from "@/common/Sound"; +import { createStore } from "solid-js/store"; + +interface InAppPreviewNotification { + title: string; + body: string; + icon?: string; + color?: string; + avatar?: string; +} + +const [notifications, setNotifications] = createStore< + InAppPreviewNotification[] +>([]); + +const pushNotification = (notification: InAppPreviewNotification) => { + setNotifications([...notifications, notification]); +}; +const removeNotification = (notification: InAppPreviewNotification) => { + setNotifications(notifications.filter((n) => n !== notification)); +}; + +const buildMessageNotification = ( + message: Message, + channel: Channel, + mentioned: boolean +) => { + pushNotification({ + title: message.createdBy.username, + body: message.content || "Attachment", + color: mentioned ? "var(--alert-color)" : undefined, + }); +}; +const inAppNotificationPreviewsEnabled = isExperimentEnabled( + "IN_APP_NOTIFICATION_PREVIEWS" +); +export const pushMessageNotification = (message: Message) => { + if (!inAppNotificationPreviewsEnabled()) return; + if (message.silent) return; + let mode = getStorageObject( + StorageKeys.IN_APP_NOTIFICATIONS_PREVIEW, + '"INHERIT"' + ); + console.log(mode); + if (mode === "OFF") return; + + const { channels, account, serverMembers } = useStore(); + const channel = channels.get(message.channelId); + if (!channel) return; + const serverId = channel.serverId; + const channelId = channel.id; + const mentioned = !!isMentioned(message, serverId); + + if (mode === "ALL") { + return buildMessageNotification(message, channel, mentioned); + } + + if (mode === "INHERIT") { + if (!serverId) return buildMessageNotification(message, channel, mentioned); + const pingMode = account.getCombinedNotificationSettings( + serverId, + channelId + )?.notificationPingMode; + + if (pingMode === ServerNotificationPingMode.MUTE) return; + if (pingMode === ServerNotificationPingMode.MENTIONS_ONLY) { + mode = "MENTIONS_ONLY"; + } + } + + if (mode === "MENTIONS_ONLY") { + if (mentioned) { + return buildMessageNotification(message, channel, mentioned); + } + return; + } + return buildMessageNotification(message, channel, mentioned); +}; + +const obj = { + notifications: () => notifications, + pushNotification, + removeNotification, + pushMessageNotification, +}; +export const useInAppNotificationPreviews = () => { + return obj; +}; diff --git a/src/components/message-pane/message-log-area/MessageLogArea.tsx b/src/components/message-pane/message-log-area/MessageLogArea.tsx index 2829ed92c..8fde135d6 100644 --- a/src/components/message-pane/message-log-area/MessageLogArea.tsx +++ b/src/components/message-pane/message-log-area/MessageLogArea.tsx @@ -69,6 +69,7 @@ import Avatar from "@/components/ui/Avatar"; import { formatTimestamp } from "@/common/date"; import { CreateTicketModal } from "@/components/CreateTicketModal"; import { Skeleton } from "@/components/ui/skeleton/Skeleton"; +import { pushMessageNotification } from "@/components/in-app-notification-previews/useInAppNotificationPreviews"; const DeleteMessageModal = lazy( () => import("../message-delete-modal/MessageDeleteModal") @@ -266,6 +267,7 @@ export const MessageLogArea = (props: { serverId: channel().serverId, }); createDesktopNotification(payload.message); + pushMessageNotification(payload.message); } } }; diff --git a/src/components/settings/NotificationsSettings.tsx b/src/components/settings/NotificationsSettings.tsx index ad760392c..23d88814f 100644 --- a/src/components/settings/NotificationsSettings.tsx +++ b/src/components/settings/NotificationsSettings.tsx @@ -1,9 +1,16 @@ import { createEffect, createSignal, Show } from "solid-js"; import Text from "@/components/ui/Text"; -import { styled } from "solid-styled-components"; +import { css, styled } from "solid-styled-components"; import { FlexColumn, FlexRow } from "../ui/Flexbox"; import useStore from "@/chat-api/store/useStore"; -import { getStorageBoolean, getStorageNumber, setStorageBoolean, setStorageNumber, StorageKeys, useReactiveLocalStorage } from "@/common/localStorage"; +import { + getStorageBoolean, + getStorageNumber, + setStorageBoolean, + setStorageNumber, + StorageKeys, + useReactiveLocalStorage, +} from "@/common/localStorage"; import Checkbox from "../ui/Checkbox"; import Breadcrumb, { BreadcrumbItem } from "../ui/Breadcrumb"; import { t } from "i18next"; @@ -12,6 +19,9 @@ import Slider from "../ui/Slider"; import { playMessageNotification, playSound, Sounds } from "@/common/Sound"; import DropDown from "../ui/drop-down/DropDown"; import Button from "../ui/Button"; +import ItemContainer from "../ui/Item"; +import { RadioBox, RadioBoxItem } from "../ui/RadioBox"; +import { isExperimentEnabled, useExperiment } from "@/common/experiments"; const Container = styled("div")` display: flex; @@ -20,65 +30,79 @@ const Container = styled("div")` padding: 10px; `; - - export default function NotificationsSettings() { const { header } = useStore(); - createEffect(() => { header.updateHeader({ title: "Settings - Notifications", - iconName: "settings" + iconName: "settings", }); }); - + const inAppNotificationPreviewsEnabled = isExperimentEnabled( + "IN_APP_NOTIFICATION_PREVIEWS" + ); + isExperimentEnabled; return ( - + - + + + + + ); } - function DesktopNotification() { - - const [isEnabled, setEnabled] = createSignal(getStorageBoolean(StorageKeys.ENABLE_DESKTOP_NOTIFICATION, false)); + const [isEnabled, setEnabled] = createSignal( + getStorageBoolean(StorageKeys.ENABLE_DESKTOP_NOTIFICATION, false) + ); const onChange = async () => { setEnabled(!isEnabled()); setStorageBoolean(StorageKeys.ENABLE_DESKTOP_NOTIFICATION, isEnabled()); await Notification.requestPermission(); - isEnabled() && new Notification("It worked.", { body: "Desktop notifications enabled!", icon: "/assets/logo.png" }); + isEnabled() && + new Notification("It worked.", { + body: "Desktop notifications enabled!", + icon: "/assets/logo.png", + }); }; - return ( - + ); } - function NotificationSound() { - const [isMuted, setMuted] = createSignal(getStorageBoolean(StorageKeys.ARE_NOTIFICATIONS_MUTED, false)); + const [isMuted, setMuted] = createSignal( + getStorageBoolean(StorageKeys.ARE_NOTIFICATIONS_MUTED, false) + ); const onNotificationSoundChange = () => { setMuted(!isMuted()); setStorageBoolean(StorageKeys.ARE_NOTIFICATIONS_MUTED, isMuted()); !isMuted() && playMessageNotification({ force: true }); }; - const [volume, setVolume] = createSignal(getStorageNumber(StorageKeys.NOTIFICATION_VOLUME, 10)); + const [volume, setVolume] = createSignal( + getStorageNumber(StorageKeys.NOTIFICATION_VOLUME, 10) + ); const onVolumeChanged = () => { setStorageNumber(StorageKeys.NOTIFICATION_VOLUME, volume()); playMessageNotification({ force: true }); @@ -86,15 +110,31 @@ function NotificationSound() { return ( - + - +
- - {volume()} + + + {volume()} +
@@ -102,47 +142,123 @@ function NotificationSound() { ); } - function NotificationSoundSelection() { return ( - - + + - + ); } - -function NotificationSoundDropDown(props: {typeId: "MESSAGE" | "MESSAGE_MENTION"}) { - const [selectedSounds, setSelectedSounds] = useReactiveLocalStorage<{[key: string]: typeof Sounds[number] | undefined}>(StorageKeys.NOTIFICATION_SOUNDS, {}); +function NotificationSoundDropDown(props: { + typeId: "MESSAGE" | "MESSAGE_MENTION"; +}) { + const [selectedSounds, setSelectedSounds] = useReactiveLocalStorage<{ + [key: string]: (typeof Sounds)[number] | undefined; + }>(StorageKeys.NOTIFICATION_SOUNDS, {}); const selectedId = () => selectedSounds()[props.typeId] || "default"; - const capitalizeFirstLetter = (val: string) => { + const capitalizeFirstLetter = (val: string) => { return val.charAt(0).toUpperCase() + val.slice(1); }; - const testSound = (e: MouseEvent, sound: typeof Sounds[number]) => { + const testSound = (e: MouseEvent, sound: (typeof Sounds)[number]) => { e.stopPropagation(); playSound(sound); }; return ( - ({ + ({ id: sound, - onClick: () => setSelectedSounds({...selectedSounds(), [props.typeId]: sound}), - label: sound === "nerimity-mute" ? "Mute" : capitalizeFirstLetter(sound.replaceAll("-", " ")), - suffix:
-
- - })) - }/> + onClick: () => + setSelectedSounds({ ...selectedSounds(), [props.typeId]: sound }), + label: + sound === "nerimity-mute" + ? "Mute" + : capitalizeFirstLetter(sound.replaceAll("-", " ")), + suffix: ( + +
+
+
+ ), + }))} + /> ); } +const RadioBoxContainer = styled("div")` + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.4); + background: rgba(255, 255, 255, 0.05); + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + padding: 10px; + padding-left: 50px; +`; + +function InAppNotificationBlock() { + const [value, setValue] = useReactiveLocalStorage( + StorageKeys.IN_APP_NOTIFICATIONS_PREVIEW, + 2 + ); + const NotificationPingItems: RadioBoxItem[] = [ + { id: "OFF", label: "Off" }, + { id: "MENTIONS_ONLY", label: "Mentions Only" }, + { id: "INHERIT", label: "Inherit from Ping Settings" }, + { id: "ALL", label: "All" }, + ]; + + return ( +
+ + + + setValue(e.id)} + items={NotificationPingItems} + initialId={value()} + /> + +
+ ); +} From 7e07dc0cfc51d4839ea0f869b15d0df770760333 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Thu, 19 Sep 2024 13:47:02 +0100 Subject: [PATCH 0008/2090] fix default option --- src/components/settings/NotificationsSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/settings/NotificationsSettings.tsx b/src/components/settings/NotificationsSettings.tsx index 23d88814f..95f996316 100644 --- a/src/components/settings/NotificationsSettings.tsx +++ b/src/components/settings/NotificationsSettings.tsx @@ -231,7 +231,7 @@ const RadioBoxContainer = styled("div")` function InAppNotificationBlock() { const [value, setValue] = useReactiveLocalStorage( StorageKeys.IN_APP_NOTIFICATIONS_PREVIEW, - 2 + "INHERIT" ); const NotificationPingItems: RadioBoxItem[] = [ { id: "OFF", label: "Off" }, From 05ee21296379e77bc2318458e315a67550cff704 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Thu, 19 Sep 2024 19:28:07 +0100 Subject: [PATCH 0009/2090] update customize page slightly to show which roles an answer will give you. --- .../customize-pane/ServerCustomizePane.tsx | 205 ++++++++++++++---- .../servers/customize-pane/styles.module.scss | 64 ++++-- 2 files changed, 211 insertions(+), 58 deletions(-) diff --git a/src/components/servers/customize-pane/ServerCustomizePane.tsx b/src/components/servers/customize-pane/ServerCustomizePane.tsx index ea9831cf0..4f0d5c271 100644 --- a/src/components/servers/customize-pane/ServerCustomizePane.tsx +++ b/src/components/servers/customize-pane/ServerCustomizePane.tsx @@ -1,10 +1,27 @@ import styles from "./styles.module.scss"; import { useParams } from "solid-navigator"; -import { For, Setter, Show, batch, createEffect, createSignal, on, onMount } from "solid-js"; +import { + For, + Setter, + Show, + batch, + createEffect, + createSignal, + on, + onMount, +} from "solid-js"; import useStore from "@/chat-api/store/useStore"; -import { addAnswerToMember, getWelcomeQuestions, removeAnswerFromMember, updateWelcomeQuestion } from "@/chat-api/services/ServerService"; -import { RawServerWelcomeAnswer, RawServerWelcomeQuestion } from "@/chat-api/RawData"; +import { + addAnswerToMember, + getWelcomeQuestions, + removeAnswerFromMember, + updateWelcomeQuestion, +} from "@/chat-api/services/ServerService"; +import { + RawServerWelcomeAnswer, + RawServerWelcomeQuestion, +} from "@/chat-api/RawData"; import Checkbox from "@/components/ui/Checkbox"; import Icon from "@/components/ui/icon/Icon"; import Text from "@/components/ui/Text"; @@ -15,105 +32,157 @@ import { RadioBoxItem } from "@/components/ui/RadioBox"; import { SetStoreFunction, createStore, reconcile } from "solid-js/store"; export default function Pane() { - const params = useParams<{serverId: string}>(); + const params = useParams<{ serverId: string }>(); const { header } = useStore(); const [questions, setQuestions] = createStore([]); - - createEffect(on(() => params.serverId, async () => { - const questions = await getWelcomeQuestions(params.serverId!); - setQuestions(reconcile(questions)); - })); + createEffect( + on( + () => params.serverId, + async () => { + const questions = await getWelcomeQuestions(params.serverId!); + setQuestions(reconcile(questions)); + } + ) + ); onMount(() => { header.updateHeader({ title: "Customize", serverId: params.serverId!, - iconName: "tune" + iconName: "tune", }); }); return ( <> -
- +
+
- + ); } const WelcomeMessage = () => { - const params = useParams<{serverId: string}>(); + const params = useParams<{ serverId: string }>(); const store = useStore(); const server = () => store.servers.get(params.serverId!); return (
- {server()?.name} - Complete these questions: + + {server()?.name} + + + Complete these questions: +
); }; -const QuestionList = (props: {questions: RawServerWelcomeQuestion[], updateQuestions: SetStoreFunction}) => { +const QuestionList = (props: { + questions: RawServerWelcomeQuestion[]; + updateQuestions: SetStoreFunction; +}) => { return (
a.order - b.order)}> - {(question) => } + {(question) => ( + + )}
); }; -const QuestionItem = (props: {question: RawServerWelcomeQuestion; questions: RawServerWelcomeQuestion[], updateQuestions: SetStoreFunction}) => { +const QuestionItem = (props: { + question: RawServerWelcomeQuestion; + questions: RawServerWelcomeQuestion[]; + updateQuestions: SetStoreFunction; +}) => { return (
{props.question.title}
- + + +
); }; -const AnswerList = (props: {answers: RawServerWelcomeAnswer[], multiselect: boolean, questions: RawServerWelcomeQuestion[], updateQuestions: SetStoreFunction}) => { +const AnswerList = (props: { + answers: RawServerWelcomeAnswer[]; + multiselect: boolean; + questions: RawServerWelcomeQuestion[]; + updateQuestions: SetStoreFunction; +}) => { return (
a.order - b.order)}> - {(answer) => } + {(answer) => ( + + )}
); }; -const AnswerItem = (props: {answer: RawServerWelcomeAnswer, questions: RawServerWelcomeQuestion[], multiselect: boolean, updateQuestions: SetStoreFunction}) => { - const params = useParams<{serverId: string}>(); +const AnswerItem = (props: { + answer: RawServerWelcomeAnswer; + questions: RawServerWelcomeQuestion[]; + multiselect: boolean; + updateQuestions: SetStoreFunction; +}) => { + const params = useParams<{ serverId: string }>(); + const { serverRoles } = useStore(); const onChange = async (newVal: boolean) => { if (newVal) { await addAnswerToMember(params.serverId, props.answer.id); if (!props.multiselect) { - - const index = props.questions.findIndex(q => q.id === props.answer.questionId); + const index = props.questions.findIndex( + (q) => q.id === props.answer.questionId + ); if (index === -1) return; const answers = props.questions[index]?.answers!; - batch(() => { for (let i = 0; i < answers.length; i++) { const answer = answers[i]; - props.updateQuestions(index, "answers", i, "answered", answer?.id === props.answer.id); + props.updateQuestions( + index, + "answers", + i, + "answered", + answer?.id === props.answer.id + ); } }); } - } - else { + } else { await removeAnswerFromMember(params.serverId, props.answer.id); if (!props.multiselect) { - - const index = props.questions.findIndex(q => q.id === props.answer.questionId); + const index = props.questions.findIndex( + (q) => q.id === props.answer.questionId + ); if (index === -1) return; @@ -127,16 +196,59 @@ const AnswerItem = (props: {answer: RawServerWelcomeAnswer, questions: RawServer } } }; + + const roles = () => + props.answer.roleIds + .map((roleId) => serverRoles.get(params.serverId, roleId)!) + .sort((a, b) => a?.order - b?.order) + .filter((r) => r); return ( -
- onChange(!props.answer.answered)} selected={props.answer.answered} item={{label: props.answer.title, id: props.answer.id}} labelSize={14} class={styles.radioBox} /> - - +
+
+ + onChange(!props.answer.answered)} + selected={props.answer.answered} + item={{ label: props.answer.title, id: props.answer.id }} + labelSize={14} + class={styles.radioBox} + /> + + + + + + + +
+ +
+ + + {(role) => ( +
+
{role.name}
+
+ )} +
+
+
); }; -const UserCount = (props: {count: number}) => { +const UserCount = (props: { count: number }) => { return (
@@ -145,17 +257,28 @@ const UserCount = (props: {count: number}) => { ); }; - const ContinueFooter = () => { - const params = useParams<{serverId: string}>(); + const params = useParams<{ serverId: string }>(); const store = useStore(); const server = () => store.servers.get(params.serverId!); const defaultChannel = () => store.channels.get(server()?.defaultChannelId); return ( - -
); } - - - - - function PostsPane() { const LIMIT = 30; const [posts, setPosts] = createSignal([]); @@ -1022,23 +1032,22 @@ function PostsPane() { const [search, setSearch] = createSignal(""); let pageContainerEl: HTMLDivElement | undefined; - const [searchParams, setSearchParams] = useSearchParams<{"search-post-id": string}>(); - + const [searchParams, setSearchParams] = useSearchParams<{ + "search-post-id": string; + }>(); onMount(() => { if (searchParams["search-post-id"]) { setSearch(searchParams["search-post-id"]); - setSearchParams({ "search-post-id": undefined! }, {replace: true}); + setSearchParams({ "search-post-id": undefined! }, { replace: true }); const el = document.querySelector(".main-pane-container")!; setTimeout(() => { el.scrollTo(0, el.scrollHeight); }, 100); - } }); - const [showAll, setShowAll] = createSignal(false); createEffect( @@ -1109,7 +1118,7 @@ function PostsPane() { value={search()} /> - +
); -} \ No newline at end of file +} From 71bc50ca36e4a86e3f3049aada8549bf23897592 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Tue, 24 Sep 2024 11:17:48 +0100 Subject: [PATCH 0027/2090] fix hard coded strings --- src/components/profile-pane/ProfilePane.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/profile-pane/ProfilePane.tsx b/src/components/profile-pane/ProfilePane.tsx index 8747d041e..065532e74 100644 --- a/src/components/profile-pane/ProfilePane.tsx +++ b/src/components/profile-pane/ProfilePane.tsx @@ -460,7 +460,11 @@ const ActionButtons = (props: { @@ -493,7 +497,9 @@ function ProfileContextMenu(props: Omit) { const items: ContextMenuItem[] = [ { id: "message", - label: isMe() ? "Saved Notes" : "Message", + label: isMe() + ? t("inbox.drawer.savedNotesButton") + : t("profile.messageButton"), icon: isMe() ? "note_alt" : "mail", onClick: onMessageClicked, }, From 500f13e182bd8c64e911ebde164a537e40c26174 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Tue, 24 Sep 2024 11:34:49 +0100 Subject: [PATCH 0028/2090] fix bugs --- src/chat-api/services/ModerationService.ts | 1 + src/components/moderation-pane/ModerationPane.tsx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/chat-api/services/ModerationService.ts b/src/chat-api/services/ModerationService.ts index 8389d2644..bfda611d6 100644 --- a/src/chat-api/services/ModerationService.ts +++ b/src/chat-api/services/ModerationService.ts @@ -187,6 +187,7 @@ export interface AuditLog { username?: string; ipAddress?: string; + count?: number; reason?: string; expireAt?: number; } diff --git a/src/components/moderation-pane/ModerationPane.tsx b/src/components/moderation-pane/ModerationPane.tsx index a44bbddd7..563718a61 100644 --- a/src/components/moderation-pane/ModerationPane.tsx +++ b/src/components/moderation-pane/ModerationPane.tsx @@ -874,7 +874,9 @@ function AuditLogItem(props: { auditLog: AuditLog }) { - IP Banned for 7 days + + {props.auditLog.count || 1}IP(s) Banned for 7 days + Date: Tue, 24 Sep 2024 11:36:58 +0100 Subject: [PATCH 0029/2090] fix bug --- src/components/moderation-pane/ModerationPane.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/moderation-pane/ModerationPane.tsx b/src/components/moderation-pane/ModerationPane.tsx index 563718a61..66f67455f 100644 --- a/src/components/moderation-pane/ModerationPane.tsx +++ b/src/components/moderation-pane/ModerationPane.tsx @@ -875,7 +875,7 @@ function AuditLogItem(props: { auditLog: AuditLog }) { - {props.auditLog.count || 1}IP(s) Banned for 7 days + {props.auditLog.count || 1} IP(s) Banned for 7 days Date: Tue, 24 Sep 2024 14:15:27 +0300 Subject: [PATCH 0030/2090] Update languages.ts Changed Belarusian --- src/locales/languages.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locales/languages.ts b/src/locales/languages.ts index e13e209fe..0d4f5ff63 100644 --- a/src/locales/languages.ts +++ b/src/locales/languages.ts @@ -26,9 +26,9 @@ export const languages = { contributors: ["https://github.com/mooocksadev"], }, "be-by": { - name: "Belarusian", - emoji: "🇧🇾", - contributors: ["https://github.com/1enify"], + name: "Belarusian (Traditional)", + emoji: "🚩", + contributors: ["https://github.com/Dzi-Mieha"], }, "pt-br": { name: "Brazilian Portuguese", From caf42e1afe91004d862a8abde7590a4323204bfe Mon Sep 17 00:00:00 2001 From: lenify <135825033+1enify@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:53:44 +0300 Subject: [PATCH 0031/2090] Update languages.ts changed the flag back(my latest messages on neri's official server) --- src/locales/languages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/languages.ts b/src/locales/languages.ts index 0d4f5ff63..de66e7daa 100644 --- a/src/locales/languages.ts +++ b/src/locales/languages.ts @@ -27,7 +27,7 @@ export const languages = { }, "be-by": { name: "Belarusian (Traditional)", - emoji: "🚩", + emoji: "🇧🇾", contributors: ["https://github.com/Dzi-Mieha"], }, "pt-br": { From a62f257719bae11850ff802c479a650534471e70 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Tue, 24 Sep 2024 15:25:36 +0100 Subject: [PATCH 0032/2090] janky but its fixed --- .../message-pane/message-item/MessageItem.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/message-pane/message-item/MessageItem.tsx b/src/components/message-pane/message-item/MessageItem.tsx index 44b31b72a..61aec62eb 100644 --- a/src/components/message-pane/message-item/MessageItem.tsx +++ b/src/components/message-pane/message-item/MessageItem.tsx @@ -1244,6 +1244,14 @@ function HTMLEmbedItem(props: { items: HtmlEmbedItem[] | string[] }) { return attributes; }; + const replaceEscaped = (str: string) => { + return str + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll(""", '"') + .replaceAll("'", "'"); + }; return ( {(item) => ( @@ -1264,7 +1272,9 @@ function HTMLEmbedItem(props: { items: HtmlEmbedItem[] | string[] }) { } > - + <> @@ -1276,7 +1286,7 @@ function HTMLEmbedItem(props: { items: HtmlEmbedItem[] | string[] }) { } > - + <> From 94319f3559b4debf3bb3d2399fb96d4f8df1c5e9 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Wed, 25 Sep 2024 12:53:21 +0100 Subject: [PATCH 0033/2090] remove scope --- src/components/message-pane/message-item/MessageItem.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/message-pane/message-item/MessageItem.tsx b/src/components/message-pane/message-item/MessageItem.tsx index 61aec62eb..1968524da 100644 --- a/src/components/message-pane/message-item/MessageItem.tsx +++ b/src/components/message-pane/message-item/MessageItem.tsx @@ -1192,9 +1192,10 @@ function HTMLEmbed(props: { message: RawMessage }) { } /> + {/* @scope (.htmlEmbed${id}) { */} - -
+ + +
+ ); } @@ -1854,3 +1856,20 @@ const MessageReplyItem = (props: { ); }; + +/** + * A declarative shadow root component + * + * Hooks into SolidJS' Portal's `useShadow` prop + * to handle shadow DOM and the component lifecycle + */ +const ShadowRoot: ParentComponent = (props) => { + let div: HTMLDivElement; + return ( +
+ + {props.children} + +
+ ); +}; From d8fec50f4ac81d8a08242e21685edd757bfa2ac0 Mon Sep 17 00:00:00 2001 From: Ruby <50844675+Shine0064@users.noreply.github.com> Date: Fri, 24 Jan 2025 06:42:01 +0100 Subject: [PATCH 0263/2090] Fix formatting in README.md Fixing formatting inconsistencies within the README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ddd88c6e5..61914329e 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,17 @@ Chat App made using SolidJS. - ## Repos - Nerimity Web - Frontend (You Are Here) - [Nerimity Server - Backend](https://github.com/Supertigerr/chat-server) ## Setup - * Fork the repo * duplicate and rename `example.env` to `.env` * Run `pnpm i` and `pnpm run dev` * Go to http://local.nerimity.com:3000 ## Features Checklist: - ### Planned Features: - [ ] Explore Themes From 2cfb298f4a3b4d83e28020d3ee46878ed6969272 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Fri, 24 Jan 2025 12:52:38 +0000 Subject: [PATCH 0264/2090] Add view transition to image embed --- src/common/transitionViewIfSupported.ts | 7 ++++ src/components/ui/ImageEmbed.tsx | 50 +++++++++++++++++-------- src/components/ui/ImagePreviewModal.tsx | 5 ++- 3 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 src/common/transitionViewIfSupported.ts diff --git a/src/common/transitionViewIfSupported.ts b/src/common/transitionViewIfSupported.ts new file mode 100644 index 000000000..b1edba054 --- /dev/null +++ b/src/common/transitionViewIfSupported.ts @@ -0,0 +1,7 @@ +export const transitionViewIfSupported = (updateCb: () => void) => { + if (document.startViewTransition) { + document.startViewTransition(updateCb); + } else { + updateCb(); + } +}; \ No newline at end of file diff --git a/src/components/ui/ImageEmbed.tsx b/src/components/ui/ImageEmbed.tsx index 9cc390ec2..74460fd8a 100644 --- a/src/components/ui/ImageEmbed.tsx +++ b/src/components/ui/ImageEmbed.tsx @@ -7,6 +7,7 @@ import { RawAttachment } from "@/chat-api/RawData"; import { createSignal, onCleanup, onMount } from "solid-js"; import env from "@/common/env"; import { ImagePreviewModal } from "./ImagePreviewModal"; +import { transitionViewIfSupported } from "@/common/transitionViewIfSupported"; const ImageEmbedContainer = styled(FlexRow)` user-select: none; @@ -44,6 +45,7 @@ interface ImageEmbedProps { export function ImageEmbed(props: ImageEmbedProps) { const { paneWidth, height, hasFocus } = useWindowProperties(); const { createPortal } = useCustomPortal(); + const [previewModalOpened, setPreviewModalOpened] = createSignal(false); const isGif = () => props.attachment.path?.endsWith(".gif"); const url = (ignoreFocus?: boolean) => { @@ -68,25 +70,43 @@ export function ImageEmbed(props: ImageEmbedProps) { const maxHeight = props.maxHeight ? clamp((props.customHeight || height()) / 2, props.maxHeight) : (props.customHeight || height()) / 2; - return clampImageSize( - props.attachment.width!, - props.attachment.height!, - maxWidth, - maxHeight - ); + + return { + ...clampImageSize( + props.attachment.width!, + props.attachment.height!, + maxWidth, + maxHeight + ), + ...(previewModalOpened() + ? { "view-transition-name": "embed-image" } + : {}), + }; }; const onClicked = () => { if (props.ignoreClick) return; - createPortal((close) => ( - - )); + setPreviewModalOpened(true); + transitionViewIfSupported(() => { + setPreviewModalOpened(false); + createPortal((close) => ( + { + transitionViewIfSupported(() => { + close(); + setPreviewModalOpened(true); + setTimeout(() => { + setPreviewModalOpened(false); + }, 100); + }); + }} + url={url(true)} + origUrl={props.attachment.origSrc} + width={props.attachment.width} + height={props.attachment.height} + /> + )); + }); }; return ( diff --git a/src/components/ui/ImagePreviewModal.tsx b/src/components/ui/ImagePreviewModal.tsx index d2fcaac15..bc68a4f57 100644 --- a/src/components/ui/ImagePreviewModal.tsx +++ b/src/components/ui/ImagePreviewModal.tsx @@ -36,6 +36,7 @@ const ImagePreviewContainer = styled(FlexRow)` img { max-width: 100%; max-height: 100%; + view-transition-name: embed-image; } `; const ImagePreview = styled(FlexRow)` @@ -66,8 +67,8 @@ export function ImagePreviewModal(props: { }) { let imageRef: HTMLImageElement | undefined; let zoomistContainerRef: HTMLImageElement | undefined; - let location = useLocation(); - let navigate = useNavigate(); + const location = useLocation(); + const navigate = useNavigate(); const { createPortal } = useCustomPortal(); createEffect( From f5f8f18d800b27659f9026383cdda19ea26ae3e9 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Fri, 24 Jan 2025 13:24:37 +0000 Subject: [PATCH 0265/2090] Mod pane: preview badges --- src/components/moderation-pane/UserPage.tsx | 55 +++++++++++++++---- .../ui/settings-block/SettingsBlock.tsx | 14 ++++- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/components/moderation-pane/UserPage.tsx b/src/components/moderation-pane/UserPage.tsx index 7dcbb8dbb..b06ec560f 100644 --- a/src/components/moderation-pane/UserPage.tsx +++ b/src/components/moderation-pane/UserPage.tsx @@ -1,4 +1,10 @@ -import { USER_BADGES, addBit, hasBit, removeBit } from "@/chat-api/Bitwise"; +import { + Bitwise, + USER_BADGES, + addBit, + hasBit, + removeBit, +} from "@/chat-api/Bitwise"; import { ModerationUser, getUser, @@ -30,6 +36,7 @@ import { RawServer, RawUser } from "@/chat-api/RawData"; import { AuditLogPane, Server, User } from "./ModerationPane"; import EditUserSuspensionModal from "./EditUserSuspensionModal"; import WarnUserModal from "./WarnUserModal"; +import { UserDetails } from "@/chat-api/services/UserService"; const UserPageContainer = styled(FlexColumn)` height: 100%; @@ -258,16 +265,12 @@ export default function UserPage() { {(badge) => ( - - onBadgeUpdate(checked, badge.bit)} - /> - + )} @@ -330,6 +333,36 @@ export default function UserPage() { ); } +const BadgeItem = (props: { + badge: Bitwise; + user: RawUser; + badges: number; + onBadgeUpdate: (checked: boolean, badgeBit: number) => void; +}) => { + const [hovered, setHovered] = createSignal(false); + return ( + setHovered(true)} + onMouseLeave={() => setHovered(false)} + class={BadgeItemStyles} + label={props.badge.name} + description={props.badge.description} + icon={ + + } + > + props.onBadgeUpdate(checked, props.badge.bit)} + /> + + ); +}; + const UsersWithSameIPAddressContainer = styled(FlexColumn)` background: rgba(255, 255, 255, 0.05); margin-bottom: 10px; diff --git a/src/components/ui/settings-block/SettingsBlock.tsx b/src/components/ui/settings-block/SettingsBlock.tsx index ab5f80d32..897799806 100644 --- a/src/components/ui/settings-block/SettingsBlock.tsx +++ b/src/components/ui/settings-block/SettingsBlock.tsx @@ -8,7 +8,7 @@ import { CustomLink } from "../CustomLink"; interface BlockProps { label: string; - icon?: string; + icon?: string | JSXElement; iconSrc?: string; description?: string | JSXElement; children?: JSX.Element | undefined; @@ -21,12 +21,16 @@ interface BlockProps { href?: string; hrefBlank?: boolean; historyState?: any; + onMouseOver?: () => void; + onMouseLeave?: () => void; } export default function SettingsBlock(props: BlockProps) { const child = children(() => props.children); return ( } + fallback={ + !props.icon || typeof props.icon === "string" ? ( + + ) : ( + props.icon + ) + } > From d18933269219a1e9dd7ffb77e1e2b0e9c6ef3552 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Fri, 24 Jan 2025 15:37:12 +0000 Subject: [PATCH 0266/2090] fix bug --- src/components/message-pane/message-item/MessageItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/message-pane/message-item/MessageItem.tsx b/src/components/message-pane/message-item/MessageItem.tsx index e3edd2d7b..f936a7678 100644 --- a/src/components/message-pane/message-item/MessageItem.tsx +++ b/src/components/message-pane/message-item/MessageItem.tsx @@ -1364,8 +1364,8 @@ const NormalEmbed = (props: { message: RawMessage }) => { }; const replaceImageUrl = (val: string, hasFocus: boolean) => { - const regex = /url\((.*?)\)/gm; - const regex2 = /url\((.*?)\)/m; + const regex = /url\((.*?)\)/gim; + const regex2 = /url\((.*?)\)/im; return val.replaceAll(regex, (r) => { let url = regex2.exec(r)?.[1]; From 0db8b9a2fe5eeb125f76bac9d1bee22423be14e4 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Fri, 24 Jan 2025 21:31:45 +0000 Subject: [PATCH 0267/2090] Add copy link button to electron titlebar --- src/components/ElectronTitleBar.tsx | 72 +++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/src/components/ElectronTitleBar.tsx b/src/components/ElectronTitleBar.tsx index a58f68712..a0cb6acaf 100644 --- a/src/components/ElectronTitleBar.tsx +++ b/src/components/ElectronTitleBar.tsx @@ -5,6 +5,7 @@ import Button from "./ui/Button"; import { electronWindowAPI } from "@/common/Electron"; import Icon from "./ui/icon/Icon"; import { classNames } from "@/common/classNames"; +import { createSignal } from "solid-js"; const BarContainer = styled(FlexRow)` height: 35px; @@ -26,7 +27,7 @@ const windowControlButtonStyles = css` width: 30px; height: 20px; padding: 5px; - border-radius: 4px; + border-radius: 8px; background-color: transparent; border: none; color: white; @@ -35,32 +36,37 @@ const windowControlButtonStyles = css` const minimize = css` &:hover { - background-color: rgba(255,255,255,0.1); + background-color: rgba(255, 255, 255, 0.1); color: var(--warn-color); } `; +const copyLink = css` + &:hover { + background-color: rgba(255, 255, 255, 0.1); + color: var(--primary-color); + } +`; const full = css` &:hover { - background-color: rgba(255,255,255,0.1); + background-color: rgba(255, 255, 255, 0.1); color: var(--success-color); } `; const close = css` &:hover { - background-color: rgba(255,255,255,0.1); + background-color: rgba(255, 255, 255, 0.1); color: var(--alert-color); } `; - const IconImage = styled("img")` height: 20px; border-radius: 50%; margin-left: 10px; pointer-events: none; - background-color: #353535; + background-color: rgb(38, 38, 38); `; const TitleText = styled(Text)` @@ -74,10 +80,56 @@ export function ElectronTitleBar() { Nerimity - - - + + + + ); -} \ No newline at end of file +} + +const CopyLinkButton = () => { + const [clicked, setClicked] = createSignal(false); + const copyLinkClick = () => { + setClicked(true); + setTimeout(() => setClicked(false), 1000); + navigator.clipboard.writeText(window.location.href); + }; + return ( + + ); +}; From 5e08619bab65a6f9183d9859ecc8dea284299fd2 Mon Sep 17 00:00:00 2001 From: Supertiger Date: Fri, 24 Jan 2025 22:18:34 +0000 Subject: [PATCH 0268/2090] Hide drawers on desktop --- .../main-pane-header/MainPaneHeader.tsx | 38 +++++++------- .../main-pane-header/styles.module.scss | 12 ++--- src/components/ui/drawer/Drawer.tsx | 50 ++++++++++++++++--- 3 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/components/main-pane-header/MainPaneHeader.tsx b/src/components/main-pane-header/MainPaneHeader.tsx index b7a61c93e..ed6915217 100644 --- a/src/components/main-pane-header/MainPaneHeader.tsx +++ b/src/components/main-pane-header/MainPaneHeader.tsx @@ -55,8 +55,8 @@ export default function MainPaneHeader() { tickets, friends, } = useStore(); - const { toggleLeftDrawer, toggleRightDrawer, hasRightDrawer, currentPage } = - useDrawer(); + const { hasRightDrawer, ...drawer } = useDrawer(); + const { isMobileWidth } = useWindowProperties(); const [hovered, setHovered] = createSignal(false); @@ -64,6 +64,14 @@ export default function MainPaneHeader() { const user = () => users.get(header.details().userId!); const channel = () => channels.get(header.details().channelId!); + const toggleLeftDrawer = () => { + if (isMobileWidth()) drawer.toggleLeftDrawer(); + else drawer.toggleHideLeftDrawer(); + }; + const toggleRightDrawer = () => { + if (isMobileWidth()) drawer.toggleRightDrawer(); + else drawer.toggleHideRightDrawer(); + }; const details = () => { let subName = null; @@ -133,20 +141,16 @@ export default function MainPaneHeader() { conditionalClass(isMobileWidth(), styles.isMobile) )} > - -
- -
- {notificationCount()} -
-
-
-
+
+ +
{notificationCount()}
+
+
{header.details().iconName && ( - +