From a1879586ffbda70b8ee3e6ffd3a59b0353dcae65 Mon Sep 17 00:00:00 2001 From: Tuur Martens Date: Thu, 18 Apr 2024 14:15:46 +0200 Subject: [PATCH 01/11] fix: video embeds' proxy_url was not checked for null (#52) --- src/Content/Embed/EmbedVideo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Content/Embed/EmbedVideo.tsx b/src/Content/Embed/EmbedVideo.tsx index 071183c..f75b74e 100644 --- a/src/Content/Embed/EmbedVideo.tsx +++ b/src/Content/Embed/EmbedVideo.tsx @@ -57,11 +57,11 @@ interface EmbedVideoProps extends Required> { thumbnail?: APIEmbedThumbnail["url"]; url: APIEmbedVideo["url"] | undefined; - proxyUrl: APIEmbedVideo["proxy_url"] | undefined; + proxyUrl: APIEmbedVideo["proxy_url"] | undefined | null; } function EmbedVideo(props: EmbedVideoProps) { - if (props.proxyUrl !== undefined) + if (props.proxyUrl) return ( Date: Fri, 26 Apr 2024 18:52:46 +0200 Subject: [PATCH 02/11] v2.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3084e46..cf36254 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@widgetbot/message-renderer", - "version": "v2.2.0", + "version": "v2.3.1", "description": "", "module": "dist/message-renderer.mjs", "files": [ From f70147484c68f10a798c41d96569d1f052a237d5 Mon Sep 17 00:00:00 2001 From: JohnyTheCarrot Date: Wed, 15 May 2024 09:19:59 +0200 Subject: [PATCH 03/11] fix: fix reaction style inaccuracy --- src/Message/Reactions/style.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Message/Reactions/style.ts b/src/Message/Reactions/style.ts index d12d704..caffc4b 100644 --- a/src/Message/Reactions/style.ts +++ b/src/Message/Reactions/style.ts @@ -39,7 +39,7 @@ export const Reaction = styled.withConfig({ display: "flex", flexDirection: "row", alignItems: "center", - padding: `${theme.space.small} ${theme.space.medium}`, + padding: `${theme.space.xs} ${theme.space.medium}`, borderRadius: 8, cursor: "not-allowed", backgroundColor: theme.colors.backgroundSecondary, From 1f0a83f7e08f7e0c4ed8438bbff075c047178b31 Mon Sep 17 00:00:00 2001 From: tylergeorges Date: Fri, 27 Sep 2024 10:04:50 -0400 Subject: [PATCH 04/11] feat: add MessageEditor component --- src/Message/style/message.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Message/style/message.ts b/src/Message/style/message.ts index 53ca466..a409bac 100644 --- a/src/Message/style/message.ts +++ b/src/Message/style/message.ts @@ -96,6 +96,26 @@ export const MessageHeaderBase = styled.withConfig({ flexWrap: "wrap", }); +export const MessageEditor = styled.withConfig({ + displayName: "message-editor", + componentId: commonComponentId, +})("input", { + paddingTop: theme.space.xl, + paddingBottom: theme.space.xl, + paddingLeft: theme.space.xxl, + paddingRight: theme.space.large, + backgroundColor: "rgb(56, 58, 64)", + // backgroundColor: theme.colors.primaryOpacity10, + outline: "none", + borderRadius: 8, + border: "none", + color: theme.colors.primaryOpacity80, + fontWeight: 400, + lineHeight: "1.375rem", + width: "100%", + whiteSpace: "pre-wrap", +}); + export const AutomodHeaderText = styled.withConfig({ displayName: "automod-header-text", componentId: commonComponentId, From bc1cf125f84c4c101504a88b27e5849486703b5e Mon Sep 17 00:00:00 2001 From: tylergeorges Date: Tue, 22 Oct 2024 14:20:32 -0400 Subject: [PATCH 05/11] feat: handle EditMessageInput submit --- src/Message/style/message.ts | 5 +- src/Message/variants/EditMessageInput.tsx | 207 ++++++++++++++++++++++ src/Message/variants/NormalMessage.tsx | 35 ++-- src/core/ConfigContext.ts | 20 ++- src/stories/Normal.stories.tsx | 56 ++++++ src/stories/Wrapper.tsx | 59 +++--- 6 files changed, 330 insertions(+), 52 deletions(-) create mode 100644 src/Message/variants/EditMessageInput.tsx diff --git a/src/Message/style/message.ts b/src/Message/style/message.ts index a409bac..1815631 100644 --- a/src/Message/style/message.ts +++ b/src/Message/style/message.ts @@ -3,8 +3,8 @@ import { styled, theme, } from "../../Stitches/stitches.config"; -import { Link } from "../../markdown/render/elements"; import SvgFromUrl from "../../SvgFromUrl"; +import { Link } from "../../markdown/render/elements"; export const SmallTimestamp = styled.withConfig({ displayName: "small-timestamp", @@ -104,7 +104,7 @@ export const MessageEditor = styled.withConfig({ paddingBottom: theme.space.xl, paddingLeft: theme.space.xxl, paddingRight: theme.space.large, - backgroundColor: "rgb(56, 58, 64)", + backgroundColor: theme.colors.primaryOpacity10, // backgroundColor: theme.colors.primaryOpacity10, outline: "none", borderRadius: 8, @@ -113,7 +113,6 @@ export const MessageEditor = styled.withConfig({ fontWeight: 400, lineHeight: "1.375rem", width: "100%", - whiteSpace: "pre-wrap", }); export const AutomodHeaderText = styled.withConfig({ diff --git a/src/Message/variants/EditMessageInput.tsx b/src/Message/variants/EditMessageInput.tsx new file mode 100644 index 0000000..048dd0c --- /dev/null +++ b/src/Message/variants/EditMessageInput.tsx @@ -0,0 +1,207 @@ +import type { + APIMessageInteraction, + APIRole, + APIUser, + Snowflake, +} from "discord-api-types/v10"; +import { MessageType } from "discord-api-types/v10"; +import Moment from "moment"; +import React, { memo, useMemo, useRef } from "react"; +import ChatTag from "../../ChatTag"; +import Content from "../../Content"; +import Tooltip from "../../Tooltip"; +import { useConfig } from "../../core/ConfigContext"; +import type { ChatMessage } from "../../types"; +import type { GetAvatarOptions } from "../../utils/getAvatar"; +import getAvatar from "../../utils/getAvatar"; +import getDisplayName from "../../utils/getDisplayName"; +import LargeTimestamp from "../LargeTimestamp"; +import MessageAuthor from "../MessageAuthor"; +import * as Styles from "../style/message"; + +interface ReplyInfoProps { + channelId: Snowflake; + referencedMessage: ChatMessage["referenced_message"]; + mentioned?: boolean; + interaction: APIMessageInteraction | undefined; + isContextMenuInteraction?: boolean; +} + +function getMiniAvatarUrl(user: APIUser) { + const getAvatarSettings: GetAvatarOptions = { + size: 16, + }; + + return getAvatar(user, getAvatarSettings); +} + +const FLAG_CROSSPOST = 1 << 1; + +const ReplyInfo = memo((props: ReplyInfoProps) => { + const user = props.interaction + ? props.interaction.user + : props.referencedMessage?.author; + + const { resolveRole, resolveChannel, resolveMember, avatarUrlOverride } = + useConfig(); + const miniUserName = useMemo(() => { + if (!props.interaction && !props.referencedMessage) return null; + + if (user === undefined) return null; + + if (!resolveChannel) return getDisplayName(user); + + const channel = resolveChannel(props.channelId); + if ( + !channel || + !("guild_id" in channel) || + !channel.guild_id || + !props.referencedMessage + ) + return getDisplayName(user); + + const guildMember = resolveMember( + props.referencedMessage.author, + channel.guild_id + ); + + if (!guildMember) return getDisplayName(user); + + return guildMember.nick ?? getDisplayName(guildMember.user as APIUser); + }, [props.referencedMessage, props.interaction, resolveChannel]); + + const miniAvatarUrl = useMemo( + () => + user === undefined + ? null + : avatarUrlOverride?.(user) ?? getMiniAvatarUrl(user), + [props.referencedMessage, props.interaction] + ); + + const miniUserNameColorHex = useMemo(() => { + if (!props.referencedMessage) return undefined; + + const channel = resolveChannel(props.referencedMessage.channel_id); + if (!channel || !("guild_id" in channel) || !channel.guild_id) + return undefined; + + const guildMember = resolveMember( + props.referencedMessage.author, + channel.guild_id + ); + + if (!guildMember) return undefined; + + const [role] = guildMember.roles + .map((id) => resolveRole(id)) + .filter( + (role): role is APIRole => + role !== null && role !== undefined && role.color !== 0 + ) + .sort((a, b) => b.position - a.position); + + const color = role?.color; + if (!color) return undefined; + + return color > 0 ? `#${color.toString(16).padStart(6, "0")}` : undefined; + }, [resolveRole]); + + const unknownReply = !props.referencedMessage && !props.interaction; + + return ( + + + {unknownReply ? ( + <> + + + Original message was deleted or is unknown. + + + ) : ( + + {miniAvatarUrl && ( + + )} + {props.referencedMessage && ( + + )} + + {props.mentioned && "@"} + {miniUserName} + + + )} + {props.referencedMessage ? ( + + ) : ( + props.interaction && ( + + used{" "} + + {!props.isContextMenuInteraction ? "/" : ""} + {props.interaction.name} + + + ) + )} + + ); +}); + +ReplyInfo.displayName = "ReplyInfo"; + +// type Message = Omit & Partial; + +interface EditMessageInputProps { + // isFirstMessage?: boolean; + message: ChatMessage; + // isHovered?: boolean; + // noThreadButton?: boolean; + // isEditing?:boolean; + // isContextMenuInteraction?: boolean; + // hideTimestamp?: boolean; + // overrides?: { + // userMentioned?: boolean; + // }; +} + +function EditMessageInput(props: EditMessageInputProps) { + const { handleMessageEditSubmit } = useConfig(); + + const submitMessageCallback = (content: string) => { + if (!handleMessageEditSubmit || !content) return; + + handleMessageEditSubmit({ + ...props.message, + content: content, + edited_timestamp: new Date().getMilliseconds().toString(), + }); + }; + + return ( + { + if (e.key === "Enter") { + submitMessageCallback(e.target.value); + } + }} + defaultValue={props.message.content} + /> + ); +} + +export default EditMessageInput; diff --git a/src/Message/variants/NormalMessage.tsx b/src/Message/variants/NormalMessage.tsx index d169d2a..ec5fcf1 100644 --- a/src/Message/variants/NormalMessage.tsx +++ b/src/Message/variants/NormalMessage.tsx @@ -1,13 +1,3 @@ -import React, { memo, useMemo } from "react"; -import MessageAuthor from "../MessageAuthor"; -import Content from "../../Content"; -import Moment from "moment"; -import Tooltip from "../../Tooltip"; -import type { GetAvatarOptions } from "../../utils/getAvatar"; -import getAvatar from "../../utils/getAvatar"; -import LargeTimestamp from "../LargeTimestamp"; -import ChatTag from "../../ChatTag"; -import * as Styles from "../style/message"; import type { APIMessageInteraction, APIRole, @@ -15,9 +5,19 @@ import type { Snowflake, } from "discord-api-types/v10"; import { MessageType } from "discord-api-types/v10"; +import Moment from "moment"; +import React, { memo, useMemo } from "react"; +import ChatTag from "../../ChatTag"; +import Content from "../../Content"; +import Tooltip from "../../Tooltip"; import { useConfig } from "../../core/ConfigContext"; -import getDisplayName from "../../utils/getDisplayName"; import type { ChatMessage } from "../../types"; +import type { GetAvatarOptions } from "../../utils/getAvatar"; +import getAvatar from "../../utils/getAvatar"; +import getDisplayName from "../../utils/getDisplayName"; +import LargeTimestamp from "../LargeTimestamp"; +import MessageAuthor from "../MessageAuthor"; +import * as Styles from "../style/message"; interface ReplyInfoProps { channelId: Snowflake; @@ -170,6 +170,7 @@ interface MessageProps { message: ChatMessage; isHovered?: boolean; noThreadButton?: boolean; + isEditing?: boolean; isContextMenuInteraction?: boolean; hideTimestamp?: boolean; overrides?: { @@ -181,7 +182,9 @@ function NormalMessage(props: MessageProps) { const shouldShowReply = props.message.type === MessageType.Reply || Boolean(props.message.interaction); - const { currentUser, resolveChannel } = useConfig(); + + const { currentUser, resolveChannel, EditMessageComponent } = useConfig(); + const channel = resolveChannel(props.message.channel_id); const guildId = channel !== null && "guild_id" in channel ? channel.guild_id : null; @@ -220,6 +223,7 @@ function NormalMessage(props: MessageProps) { isContextMenuInteraction={props.isContextMenuInteraction} /> )} + )} - + ) : null} + {/* + /> */} ); diff --git a/src/core/ConfigContext.ts b/src/core/ConfigContext.ts index c0f0eb8..cc29b34 100644 --- a/src/core/ConfigContext.ts +++ b/src/core/ConfigContext.ts @@ -1,5 +1,3 @@ -import type { ReactElement } from "react"; -import { createContext, useContext } from "react"; import type { APIChannel, APIEmbedImage, @@ -9,11 +7,14 @@ import type { APIUser, Snowflake, } from "discord-api-types/v10"; -import type { SvgConfig } from "./svgs"; -import type { Tag } from "../ChatTag/style"; import type { APIAttachment } from "discord-api-types/v10"; -import type { UserAvatar } from "../utils/getAvatar"; +import type { ReactElement } from "react"; +import { createContext, useContext } from "react"; +import EditMessageInput from "src/Message/variants/EditMessageInput"; +import type { Tag } from "../ChatTag/style"; import type { ChatMessage } from "../types"; +import type { UserAvatar } from "../utils/getAvatar"; +import type { SvgConfig } from "./svgs"; export type PartialSvgConfig = Partial; @@ -29,9 +30,9 @@ export interface ChatBadgeProps { } export enum MessageTypeResponse { - InAppError, - ConsoleError, - None, + InAppError = 0, + ConsoleError = 1, + None = 2, } export type Config = { @@ -62,6 +63,8 @@ export type Config = { attachmentImageOnClick?(image: APIAttachment): void; embedImageOnClick?(image: APIEmbedImage): void; externalLinkOpenRequested?(url: string): void; + handleMessageEditSubmit?(message: ChatMessage): void; + EditMessageComponent?:( (props: { message: ChatMessage }) => JSX.Element); }; export const ConfigContext = createContext>({ @@ -76,6 +79,7 @@ export const ConfigContext = createContext>({ still: "", animated: "", }, + EditMessageComponent: EditMessageInput, }); export function useConfig() { diff --git a/src/stories/Normal.stories.tsx b/src/stories/Normal.stories.tsx index a39a6c5..1d6d535 100644 --- a/src/stories/Normal.stories.tsx +++ b/src/stories/Normal.stories.tsx @@ -1006,6 +1006,62 @@ VideoAttachment.args = { ], }; +export const Editing: StoryFn = Template.bind({}); +Editing.args = { + messages: [ + { + id: "1101275906716213339", + type: 0, + content: + "Small update: We needed to roll this back ~~for 24 hours~~ to patch some security issues. It'll be back real soon. Update: we don't want to re-roll it out on a friday afternoon, so thisll be back next week.", + channel_id: "697138785317814292", + author: { + id: "933123872641921044", + username: "therealjethro", + global_name: "Jeff", + avatar: "e4d8c186d8900eed2ace6aed5cefe1c0", + discriminator: "0", + public_flags: 4604871, + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-04-27T22:37:16.878000+00:00", + edited_timestamp: "2023-04-28T21:00:43.827000+00:00", + flags: 0, + components: [], + message_reference: { + channel_id: "697138785317814292", + guild_id: "613425648685547541", + message_id: "1101188115344920607", + }, + reactions: [ + { + emoji: { + id: null, + name: "👍", + }, + count: 234, + me: false, + }, + { + emoji: { + id: "1085363933579329656", + name: "App_Broom", + }, + count: 185, + me: false, + }, + ], + + }, + ], +}; + export const Reply: StoryFn = Template.bind({}); Reply.args = { messages: [ diff --git a/src/stories/Wrapper.tsx b/src/stories/Wrapper.tsx index d788a5f..12cca7f 100644 --- a/src/stories/Wrapper.tsx +++ b/src/stories/Wrapper.tsx @@ -1,42 +1,42 @@ -import { MessageRendererProvider } from "../index"; import React from "react"; +import { MessageRendererProvider } from "../index"; -// SVGs -import SvgFileAudio from "../assets/storybookOnlyAssets/file-audio.svg"; -import SvgSketch from "../assets/storybookOnlyAssets/file-sketch.svg"; -import SvgFileArchive from "../assets/storybookOnlyAssets/file-archive.svg"; -import SvgFileUnknown from "../assets/storybookOnlyAssets/file-unknown.svg"; +import SvgAcrobat from "../assets/storybookOnlyAssets/file-acrobat.svg"; import SvgAe from "../assets/storybookOnlyAssets/file-ae.svg"; import SvgAi from "../assets/storybookOnlyAssets/file-ai.svg"; -import SvgAcrobat from "../assets/storybookOnlyAssets/file-acrobat.svg"; +import SvgFileArchive from "../assets/storybookOnlyAssets/file-archive.svg"; +// SVGs +import SvgFileAudio from "../assets/storybookOnlyAssets/file-audio.svg"; import SvgCode from "../assets/storybookOnlyAssets/file-code.svg"; import SvgDocument from "../assets/storybookOnlyAssets/file-document.svg"; +import SvgSketch from "../assets/storybookOnlyAssets/file-sketch.svg"; import SvgSpreadsheet from "../assets/storybookOnlyAssets/file-spreadsheet.svg"; +import SvgFileUnknown from "../assets/storybookOnlyAssets/file-unknown.svg"; import SvgWebCode from "../assets/storybookOnlyAssets/file-webcode.svg"; import SvgIconAdd from "../assets/storybookOnlyAssets/icon-add.svg"; -import SvgIconRemove from "../assets/storybookOnlyAssets/icon-remove.svg"; -import SvgWarning from "../assets/storybookOnlyAssets/icon-warning.svg"; -import SvgIconDownload from "../assets/storybookOnlyAssets/icon-download.svg"; -import SvgIconCheckmark from "../assets/storybookOnlyAssets/icon-checkmark.svg"; -import SvgIconCross from "../assets/storybookOnlyAssets/icon-cross.svg"; -import SvgIconPin from "../assets/storybookOnlyAssets/icon-pin.svg"; -import SvgIconPencil from "../assets/storybookOnlyAssets/icon-pencil.svg"; +import SvgIconAttachment from "../assets/storybookOnlyAssets/icon-attachment.svg"; import SvgIconBoost from "../assets/storybookOnlyAssets/icon-boost.svg"; -import SvgIconThreadCreated from "../assets/storybookOnlyAssets/icon-thread-created.svg"; -import SvgIconId from "../assets/storybookOnlyAssets/icon-id.svg"; -import SvgIconSticker from "../assets/storybookOnlyAssets/icon-sticker.svg"; +import SvgIconCheckmark from "../assets/storybookOnlyAssets/icon-checkmark.svg"; import SvgIconCommand from "../assets/storybookOnlyAssets/icon-command.svg"; -import SvgIconAttachment from "../assets/storybookOnlyAssets/icon-attachment.svg"; +import SvgIconCross from "../assets/storybookOnlyAssets/icon-cross.svg"; import SvgIconDanger from "../assets/storybookOnlyAssets/icon-danger.svg"; -import SvgIconPause from "../assets/storybookOnlyAssets/icon-pause.svg"; +import SvgIconDownload from "../assets/storybookOnlyAssets/icon-download.svg"; import SvgIconFullscreen from "../assets/storybookOnlyAssets/icon-fullscreen.svg"; +import SvgIconId from "../assets/storybookOnlyAssets/icon-id.svg"; +import SvgIconLinkExternal from "../assets/storybookOnlyAssets/icon-link-external.svg"; +import SvgIconPause from "../assets/storybookOnlyAssets/icon-pause.svg"; +import SvgIconPencil from "../assets/storybookOnlyAssets/icon-pencil.svg"; +import SvgIconPin from "../assets/storybookOnlyAssets/icon-pin.svg"; import SvgIconPlay from "../assets/storybookOnlyAssets/icon-play.svg"; -import SvgIconTextChannel from "../assets/storybookOnlyAssets/icon-text-channel.svg"; -import SvgIconVoiceChannel from "../assets/storybookOnlyAssets/icon-voice-channel.svg"; +import SvgIconRemove from "../assets/storybookOnlyAssets/icon-remove.svg"; import SvgIconStageChannel from "../assets/storybookOnlyAssets/icon-stage-channel.svg"; -import SvgIconLinkExternal from "../assets/storybookOnlyAssets/icon-link-external.svg"; +import SvgIconSticker from "../assets/storybookOnlyAssets/icon-sticker.svg"; +import SvgIconTextChannel from "../assets/storybookOnlyAssets/icon-text-channel.svg"; +import SvgIconThreadCreated from "../assets/storybookOnlyAssets/icon-thread-created.svg"; import SvgIconUnknownReply from "../assets/storybookOnlyAssets/icon-unknown-reply.svg"; +import SvgIconVoiceChannel from "../assets/storybookOnlyAssets/icon-voice-channel.svg"; +import SvgWarning from "../assets/storybookOnlyAssets/icon-warning.svg"; import ggSansNormal400 from "../assets/storybookOnlyAssets/gg-sans-normal-400.woff2"; import ggSansNormal500 from "../assets/storybookOnlyAssets/gg-sans-normal-500.woff2"; @@ -50,10 +50,10 @@ import ggSansItalic600 from "../assets/storybookOnlyAssets/gg-sans-italic-600.wo import ggSansItalic700 from "../assets/storybookOnlyAssets/gg-sans-italic-700.woff2"; import ggSansItalic800 from "../assets/storybookOnlyAssets/gg-sans-italic-800.woff2"; -import automodAvatarStill from "../assets/storybookOnlyAssets/automod-avatar.png"; import automodAvatarAnimated from "../assets/storybookOnlyAssets/automod-avatar.gif"; +import automodAvatarStill from "../assets/storybookOnlyAssets/automod-avatar.png"; -import SvgMiscDiscordImageFailure from "../assets/storybookOnlyAssets/misc-discord-image-failure.svg"; +import type { Decorator } from "@storybook/react"; import type { APIChannel, APIGuild, @@ -63,16 +63,17 @@ import type { Snowflake, } from "discord-api-types/v10"; import { ChannelType, GuildNSFWLevel } from "discord-api-types/v10"; +import EditMessageInput from "../Message/variants/EditMessageInput"; import { globalCss, prefix, styled, theme } from "../Stitches/stitches.config"; -import getDisplayName from "../utils/getDisplayName"; +import SvgMiscDiscordImageFailure from "../assets/storybookOnlyAssets/misc-discord-image-failure.svg"; import type { ChatBadgeProps, MessageButtonListOption, } from "../core/ConfigContext"; import { MessageTypeResponse } from "../core/ConfigContext"; -import { testTextChannel, testVoiceChannel } from "./commonTestData"; -import type { Decorator } from "@storybook/react"; import type { ChatMessage } from "../types"; +import getDisplayName from "../utils/getDisplayName"; +import { testTextChannel, testVoiceChannel } from "./commonTestData"; const svgUrls = { FileAudio: SvgFileAudio, @@ -382,12 +383,16 @@ const Wrapper: Decorator = (Story) => { still: automodAvatarStill, animated: automodAvatarAnimated, }} + EditMessageComponent={EditMessageInput} messageButtons={getButtons} resolveRole={resolveRole} resolveChannel={resolveChannel} resolveMember={resolveMember} resolveGuild={resolveGuild} resolveUser={resolveUser} + handleMessageEditSubmit={(message) => { + alert(`Edited message: ${message.id}`); + }} currentUser={() => resolveUser("132819036282159104")} seeThreadOnClick={(messageId, thread) => alert(`See Thread "${thread.name}" clicked on message ${messageId}`) From c0fb0e21367865fef4b205ace6c39420312443ea Mon Sep 17 00:00:00 2001 From: tylergeorges Date: Tue, 29 Oct 2024 13:21:15 -0400 Subject: [PATCH 06/11] feat: add editingMessageId prop to MessageRendererProvider --- src/Message/variants/EditMessageInput.tsx | 183 ++-------------------- src/Message/variants/NormalMessage.tsx | 23 +-- src/core/ConfigContext.ts | 5 +- src/stories/Normal.stories.tsx | 16 +- src/stories/Wrapper.tsx | 7 +- 5 files changed, 40 insertions(+), 194 deletions(-) diff --git a/src/Message/variants/EditMessageInput.tsx b/src/Message/variants/EditMessageInput.tsx index 048dd0c..2372929 100644 --- a/src/Message/variants/EditMessageInput.tsx +++ b/src/Message/variants/EditMessageInput.tsx @@ -1,169 +1,8 @@ -import type { - APIMessageInteraction, - APIRole, - APIUser, - Snowflake, -} from "discord-api-types/v10"; -import { MessageType } from "discord-api-types/v10"; -import Moment from "moment"; -import React, { memo, useMemo, useRef } from "react"; -import ChatTag from "../../ChatTag"; -import Content from "../../Content"; -import Tooltip from "../../Tooltip"; +import React from "react"; import { useConfig } from "../../core/ConfigContext"; import type { ChatMessage } from "../../types"; -import type { GetAvatarOptions } from "../../utils/getAvatar"; -import getAvatar from "../../utils/getAvatar"; -import getDisplayName from "../../utils/getDisplayName"; -import LargeTimestamp from "../LargeTimestamp"; -import MessageAuthor from "../MessageAuthor"; -import * as Styles from "../style/message"; - -interface ReplyInfoProps { - channelId: Snowflake; - referencedMessage: ChatMessage["referenced_message"]; - mentioned?: boolean; - interaction: APIMessageInteraction | undefined; - isContextMenuInteraction?: boolean; -} - -function getMiniAvatarUrl(user: APIUser) { - const getAvatarSettings: GetAvatarOptions = { - size: 16, - }; - - return getAvatar(user, getAvatarSettings); -} - -const FLAG_CROSSPOST = 1 << 1; - -const ReplyInfo = memo((props: ReplyInfoProps) => { - const user = props.interaction - ? props.interaction.user - : props.referencedMessage?.author; - - const { resolveRole, resolveChannel, resolveMember, avatarUrlOverride } = - useConfig(); - const miniUserName = useMemo(() => { - if (!props.interaction && !props.referencedMessage) return null; - - if (user === undefined) return null; - - if (!resolveChannel) return getDisplayName(user); - - const channel = resolveChannel(props.channelId); - if ( - !channel || - !("guild_id" in channel) || - !channel.guild_id || - !props.referencedMessage - ) - return getDisplayName(user); - - const guildMember = resolveMember( - props.referencedMessage.author, - channel.guild_id - ); - - if (!guildMember) return getDisplayName(user); - - return guildMember.nick ?? getDisplayName(guildMember.user as APIUser); - }, [props.referencedMessage, props.interaction, resolveChannel]); - - const miniAvatarUrl = useMemo( - () => - user === undefined - ? null - : avatarUrlOverride?.(user) ?? getMiniAvatarUrl(user), - [props.referencedMessage, props.interaction] - ); - - const miniUserNameColorHex = useMemo(() => { - if (!props.referencedMessage) return undefined; - - const channel = resolveChannel(props.referencedMessage.channel_id); - if (!channel || !("guild_id" in channel) || !channel.guild_id) - return undefined; - - const guildMember = resolveMember( - props.referencedMessage.author, - channel.guild_id - ); - - if (!guildMember) return undefined; - - const [role] = guildMember.roles - .map((id) => resolveRole(id)) - .filter( - (role): role is APIRole => - role !== null && role !== undefined && role.color !== 0 - ) - .sort((a, b) => b.position - a.position); - - const color = role?.color; - if (!color) return undefined; - - return color > 0 ? `#${color.toString(16).padStart(6, "0")}` : undefined; - }, [resolveRole]); - const unknownReply = !props.referencedMessage && !props.interaction; - - return ( - - - {unknownReply ? ( - <> - - - Original message was deleted or is unknown. - - - ) : ( - - {miniAvatarUrl && ( - - )} - {props.referencedMessage && ( - - )} - - {props.mentioned && "@"} - {miniUserName} - - - )} - {props.referencedMessage ? ( - - ) : ( - props.interaction && ( - - used{" "} - - {!props.isContextMenuInteraction ? "/" : ""} - {props.interaction.name} - - - ) - )} - - ); -}); - -ReplyInfo.displayName = "ReplyInfo"; - -// type Message = Omit & Partial; +import * as Styles from "../style/message"; interface EditMessageInputProps { // isFirstMessage?: boolean; @@ -181,7 +20,7 @@ interface EditMessageInputProps { function EditMessageInput(props: EditMessageInputProps) { const { handleMessageEditSubmit } = useConfig(); - const submitMessageCallback = (content: string) => { + function submitMessageCallback(content: string) { if (!handleMessageEditSubmit || !content) return; handleMessageEditSubmit({ @@ -189,16 +28,20 @@ function EditMessageInput(props: EditMessageInputProps) { content: content, edited_timestamp: new Date().getMilliseconds().toString(), }); - }; + } + + function onKeyDown(e: React.KeyboardEvent) { + const target = e.target as HTMLInputElement; + + if (e.key === "Enter") { + submitMessageCallback(target.value); + } + } return ( { - if (e.key === "Enter") { - submitMessageCallback(e.target.value); - } - }} + onKeyDown={onKeyDown} defaultValue={props.message.content} /> ); diff --git a/src/Message/variants/NormalMessage.tsx b/src/Message/variants/NormalMessage.tsx index ec5fcf1..a4b5ac2 100644 --- a/src/Message/variants/NormalMessage.tsx +++ b/src/Message/variants/NormalMessage.tsx @@ -163,14 +163,11 @@ const ReplyInfo = memo((props: ReplyInfoProps) => { ReplyInfo.displayName = "ReplyInfo"; -// type Message = Omit & Partial; - interface MessageProps { isFirstMessage?: boolean; message: ChatMessage; isHovered?: boolean; noThreadButton?: boolean; - isEditing?: boolean; isContextMenuInteraction?: boolean; hideTimestamp?: boolean; overrides?: { @@ -183,7 +180,12 @@ function NormalMessage(props: MessageProps) { props.message.type === MessageType.Reply || Boolean(props.message.interaction); - const { currentUser, resolveChannel, EditMessageComponent } = useConfig(); + const { + currentUser, + resolveChannel, + editingMessageId, + EditMessageComponent, + } = useConfig(); const channel = resolveChannel(props.message.channel_id); const guildId = @@ -235,13 +237,14 @@ function NormalMessage(props: MessageProps) { )} - {EditMessageComponent ? ( + {editingMessageId === props.message.id && EditMessageComponent ? ( - ) : null} - {/* */} + ) : ( + + )} ); diff --git a/src/core/ConfigContext.ts b/src/core/ConfigContext.ts index cc29b34..fdfcb92 100644 --- a/src/core/ConfigContext.ts +++ b/src/core/ConfigContext.ts @@ -10,7 +10,6 @@ import type { import type { APIAttachment } from "discord-api-types/v10"; import type { ReactElement } from "react"; import { createContext, useContext } from "react"; -import EditMessageInput from "src/Message/variants/EditMessageInput"; import type { Tag } from "../ChatTag/style"; import type { ChatMessage } from "../types"; import type { UserAvatar } from "../utils/getAvatar"; @@ -51,6 +50,7 @@ export type Config = { avatarUrlOverride?(user: APIUser): UserAvatar | null; themeOverrideClassName?: string; unknownMessageTypeResponse?: MessageTypeResponse; + editingMessageId?: string; // Click handlers currentUser(): APIUser | null; @@ -64,7 +64,7 @@ export type Config = { embedImageOnClick?(image: APIEmbedImage): void; externalLinkOpenRequested?(url: string): void; handleMessageEditSubmit?(message: ChatMessage): void; - EditMessageComponent?:( (props: { message: ChatMessage }) => JSX.Element); + EditMessageComponent?: (props: { message: ChatMessage }) => JSX.Element; }; export const ConfigContext = createContext>({ @@ -79,7 +79,6 @@ export const ConfigContext = createContext>({ still: "", animated: "", }, - EditMessageComponent: EditMessageInput, }); export function useConfig() { diff --git a/src/stories/Normal.stories.tsx b/src/stories/Normal.stories.tsx index 1d6d535..42b9fe6 100644 --- a/src/stories/Normal.stories.tsx +++ b/src/stories/Normal.stories.tsx @@ -1008,20 +1008,21 @@ VideoAttachment.args = { export const Editing: StoryFn = Template.bind({}); Editing.args = { + showButtons: true, messages: [ { - id: "1101275906716213339", + id: "1101275906716213331", type: 0, content: "Small update: We needed to roll this back ~~for 24 hours~~ to patch some security issues. It'll be back real soon. Update: we don't want to re-roll it out on a friday afternoon, so thisll be back next week.", channel_id: "697138785317814292", author: { - id: "933123872641921044", - username: "therealjethro", - global_name: "Jeff", - avatar: "e4d8c186d8900eed2ace6aed5cefe1c0", - discriminator: "0", - public_flags: 4604871, + id: "132819036282159104", + username: "mrquine", + global_name: "Mr. Quine", + avatar: "3a30ffeeeb354950804d77ded94162d3", + discriminator: "0001", + public_flags: 4457220, }, attachments: [], embeds: [], @@ -1057,7 +1058,6 @@ Editing.args = { me: false, }, ], - }, ], }; diff --git a/src/stories/Wrapper.tsx b/src/stories/Wrapper.tsx index 12cca7f..29ccddc 100644 --- a/src/stories/Wrapper.tsx +++ b/src/stories/Wrapper.tsx @@ -384,15 +384,16 @@ const Wrapper: Decorator = (Story) => { animated: automodAvatarAnimated, }} EditMessageComponent={EditMessageInput} + handleMessageEditSubmit={(message) => { + alert(`Edited message: ${message.id}`); + }} + editingMessageId="1101275906716213331" messageButtons={getButtons} resolveRole={resolveRole} resolveChannel={resolveChannel} resolveMember={resolveMember} resolveGuild={resolveGuild} resolveUser={resolveUser} - handleMessageEditSubmit={(message) => { - alert(`Edited message: ${message.id}`); - }} currentUser={() => resolveUser("132819036282159104")} seeThreadOnClick={(messageId, thread) => alert(`See Thread "${thread.name}" clicked on message ${messageId}`) From 172eb17bd2e9ce9eefdd9afbdfe68a6c693a275e Mon Sep 17 00:00:00 2001 From: Mason Rogers <26467584+mason-rogers@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:58:55 +0000 Subject: [PATCH 07/11] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cf36254..3084e46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@widgetbot/message-renderer", - "version": "v2.3.1", + "version": "v2.2.0", "description": "", "module": "dist/message-renderer.mjs", "files": [ From 015e982b3fe9b5a712f18dfe7918e7b6026b0d70 Mon Sep 17 00:00:00 2001 From: tylergeorges Date: Mon, 11 Nov 2024 10:26:16 -0500 Subject: [PATCH 08/11] refactor: remove commented style --- src/Message/style/message.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Message/style/message.ts b/src/Message/style/message.ts index 1815631..56fbc3b 100644 --- a/src/Message/style/message.ts +++ b/src/Message/style/message.ts @@ -105,7 +105,6 @@ export const MessageEditor = styled.withConfig({ paddingLeft: theme.space.xxl, paddingRight: theme.space.large, backgroundColor: theme.colors.primaryOpacity10, - // backgroundColor: theme.colors.primaryOpacity10, outline: "none", borderRadius: 8, border: "none", From b1f3d43f9f83f0ed60941f1a5a59196bf7afabff Mon Sep 17 00:00:00 2001 From: tylergeorges Date: Mon, 11 Nov 2024 10:26:48 -0500 Subject: [PATCH 09/11] refactor: remove commented EditMessageInputProps --- src/Message/variants/EditMessageInput.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Message/variants/EditMessageInput.tsx b/src/Message/variants/EditMessageInput.tsx index 2372929..534952d 100644 --- a/src/Message/variants/EditMessageInput.tsx +++ b/src/Message/variants/EditMessageInput.tsx @@ -5,16 +5,7 @@ import type { ChatMessage } from "../../types"; import * as Styles from "../style/message"; interface EditMessageInputProps { - // isFirstMessage?: boolean; message: ChatMessage; - // isHovered?: boolean; - // noThreadButton?: boolean; - // isEditing?:boolean; - // isContextMenuInteraction?: boolean; - // hideTimestamp?: boolean; - // overrides?: { - // userMentioned?: boolean; - // }; } function EditMessageInput(props: EditMessageInputProps) { From 758ba97568368a56aac5d8f1c30411348823c551 Mon Sep 17 00:00:00 2001 From: tylergeorges Date: Mon, 11 Nov 2024 10:29:00 -0500 Subject: [PATCH 10/11] refactor: rename handleMessageEditSubmit --- src/Message/variants/EditMessageInput.tsx | 6 +++--- src/core/ConfigContext.ts | 2 +- src/stories/Wrapper.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Message/variants/EditMessageInput.tsx b/src/Message/variants/EditMessageInput.tsx index 534952d..bb567d9 100644 --- a/src/Message/variants/EditMessageInput.tsx +++ b/src/Message/variants/EditMessageInput.tsx @@ -9,12 +9,12 @@ interface EditMessageInputProps { } function EditMessageInput(props: EditMessageInputProps) { - const { handleMessageEditSubmit } = useConfig(); + const { messageEditOnSubmit } = useConfig(); function submitMessageCallback(content: string) { - if (!handleMessageEditSubmit || !content) return; + if (!messageEditOnSubmit || !content) return; - handleMessageEditSubmit({ + messageEditOnSubmit({ ...props.message, content: content, edited_timestamp: new Date().getMilliseconds().toString(), diff --git a/src/core/ConfigContext.ts b/src/core/ConfigContext.ts index fdfcb92..c4b028f 100644 --- a/src/core/ConfigContext.ts +++ b/src/core/ConfigContext.ts @@ -63,7 +63,7 @@ export type Config = { attachmentImageOnClick?(image: APIAttachment): void; embedImageOnClick?(image: APIEmbedImage): void; externalLinkOpenRequested?(url: string): void; - handleMessageEditSubmit?(message: ChatMessage): void; + messageEditOnSubmit?(message: ChatMessage): void; EditMessageComponent?: (props: { message: ChatMessage }) => JSX.Element; }; diff --git a/src/stories/Wrapper.tsx b/src/stories/Wrapper.tsx index 29ccddc..fbeda3f 100644 --- a/src/stories/Wrapper.tsx +++ b/src/stories/Wrapper.tsx @@ -384,7 +384,7 @@ const Wrapper: Decorator = (Story) => { animated: automodAvatarAnimated, }} EditMessageComponent={EditMessageInput} - handleMessageEditSubmit={(message) => { + messageEditOnSubmit={(message) => { alert(`Edited message: ${message.id}`); }} editingMessageId="1101275906716213331" From 244e90de36a05e177cbdbbc1287a767d649aedbe Mon Sep 17 00:00:00 2001 From: tylergeorges Date: Mon, 11 Nov 2024 10:29:37 -0500 Subject: [PATCH 11/11] refactor: remove braces around autoCorrect prop --- src/Message/variants/EditMessageInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Message/variants/EditMessageInput.tsx b/src/Message/variants/EditMessageInput.tsx index bb567d9..91beb94 100644 --- a/src/Message/variants/EditMessageInput.tsx +++ b/src/Message/variants/EditMessageInput.tsx @@ -31,7 +31,7 @@ function EditMessageInput(props: EditMessageInputProps) { return (