Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@widgetbot/message-renderer",
"version": "v2.4.1",
"version": "v3.0.0",
"description": "",
"module": "dist/message-renderer.mjs",
"files": [
Expand Down Expand Up @@ -32,7 +32,7 @@
"@stitches/react": "^1.3.1-1",
"autobind-decorator": "^2.4.0",
"color": "^4.2.3",
"discord-api-types": "^0.37.52",
"discord-api-types": "^0.38.49",
"filesize": "^10.0.6",
"highlight.js": "^11.7.0",
"i18next": "^23.4.4",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/Message/Components/ActionRow.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type {
APIActionRowComponent,
APIMessageActionRowComponent,
APIComponentInMessageActionRow,
} from "discord-api-types/v10";
import * as Styles from "./style";
import React from "react";
import Component from "./Component";
import type { ChatMessage } from "../../types";

interface ActionRowProps {
actionRow: APIActionRowComponent<APIMessageActionRowComponent>;
actionRow: APIActionRowComponent<APIComponentInMessageActionRow>;
message: ChatMessage;
}

Expand Down
1 change: 1 addition & 0 deletions src/Message/Components/ButtonComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const buttonStyleMap: Record<
[ButtonStyle.Primary]: "primary",
[ButtonStyle.Secondary]: "secondary",
[ButtonStyle.Link]: "secondary",
[ButtonStyle.Premium]: "secondary",
[ButtonStyle.Danger]: "danger",
[ButtonStyle.Success]: "success",
};
Expand Down
28 changes: 26 additions & 2 deletions src/Message/Components/Component.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
import type { APIMessageActionRowComponent } from "discord-api-types/v10";
import type { APIMessageComponent } from "discord-api-types/v10";
import { ComponentType } from "discord-api-types/v10";
import ActionRow from "./ActionRow";
import ButtonComponent from "./ButtonComponent";
import ContainerComponent from "./ContainerComponent";
import FileComponent from "./FileComponent";
import MediaGalleryComponent from "./MediaGalleryComponent";
import React from "react";
import SectionComponent from "./SectionComponent";
import SeparatorComponent from "./SeparatorComponent";
import TextDisplayComponent from "./TextDisplayComponent";
import ThumbnailComponent from "./ThumbnailComponent";
import type { ChatMessage } from "../../types";

interface ComponentProps {
component: APIMessageActionRowComponent;
component: APIMessageComponent;
message: ChatMessage;
}

function Component({ component, message }: ComponentProps) {
switch (component.type) {
case ComponentType.ActionRow:
return <ActionRow actionRow={component} message={message} />;
case ComponentType.Button:
// @ts-expect-error TS2322 Type error not applicable (tl;dr: button emoji is not optional)
return <ButtonComponent button={component} message={message} />;
case ComponentType.Section:
return <SectionComponent section={component} message={message} />;
case ComponentType.TextDisplay:
return <TextDisplayComponent textDisplay={component} message={message} />;
case ComponentType.Thumbnail:
return <ThumbnailComponent thumbnail={component} />;
case ComponentType.MediaGallery:
return <MediaGalleryComponent mediaGallery={component} />;
case ComponentType.File:
return <FileComponent file={component} />;
case ComponentType.Separator:
return <SeparatorComponent separator={component} />;
case ComponentType.Container:
return <ContainerComponent container={component} message={message} />;
default:
return null;
}
Expand Down
36 changes: 36 additions & 0 deletions src/Message/Components/ContainerComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { APIContainerComponent } from "discord-api-types/v10";
import Component from "./Component";
import React from "react";
import Spoiler from "./Spoiler";
import * as Styles from "./style";
import { colorIntToRgba } from "../../utils/colorIntToCss";
import type { ChatMessage } from "../../types";

interface ContainerComponentProps {
container: APIContainerComponent;
message: ChatMessage;
}

function ContainerComponent({ container, message }: ContainerComponentProps) {
const accentColor =
container.accent_color === null ? undefined : container.accent_color;

const content = (
<Styles.Container
hasAccent={accentColor !== undefined}
style={
accentColor === undefined
? undefined
: { borderLeftColor: colorIntToRgba(accentColor) }
}
>
{container.components.map((component, index) => (
<Component component={component} message={message} key={index} />
))}
</Styles.Container>
);

return container.spoiler ? <Spoiler>{content}</Spoiler> : content;
}

export default ContainerComponent;
50 changes: 50 additions & 0 deletions src/Message/Components/FileComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { APIFileComponent } from "discord-api-types/v10";
import { filesize } from "filesize";
import React from "react";
import Spoiler from "./Spoiler";
import SvgFromUrl from "../../SvgFromUrl";
import * as Styles from "./style";

interface FileComponentProps {
file: APIFileComponent;
}

function getFileName(file: APIFileComponent): string {
if (file.name) return file.name;

const path = file.file.url.split("?")[0];
return path.split("/").pop() || file.file.url;
}

function FileComponent({ file }: FileComponentProps) {
const card = (
<Styles.FileContainer>
<SvgFromUrl svg="FileUnknown" width={30} height={40} />
<Styles.FileMetadata>
<Styles.FileName
rel="noreferrer noopener"
target="_blank"
href={file.file.url}
>
{getFileName(file)}
</Styles.FileName>
{file.size !== undefined && (
<Styles.FileSize>
{filesize(file.size, { base: 2 }) as string}
</Styles.FileSize>
)}
</Styles.FileMetadata>
<Styles.FileDownload
rel="noreferrer noopener"
target="_blank"
href={file.file.url}
>
<SvgFromUrl svg="IconDownload" width={24} height={24} />
</Styles.FileDownload>
</Styles.FileContainer>
);

return file.spoiler ? <Spoiler>{card}</Spoiler> : card;
}

export default FileComponent;
29 changes: 29 additions & 0 deletions src/Message/Components/MediaGalleryComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { APIMediaGalleryComponent } from "discord-api-types/v10";
import MediaItem from "./MediaItem";
import React from "react";
import Spoiler from "./Spoiler";
import * as Styles from "./style";

interface MediaGalleryComponentProps {
mediaGallery: APIMediaGalleryComponent;
}

function MediaGalleryComponent({ mediaGallery }: MediaGalleryComponentProps) {
return (
<Styles.MediaGallery>
{mediaGallery.items.map((item, index) => {
const media = (
<MediaItem media={item.media} description={item.description} />
);

return (
<Styles.MediaGalleryItem key={index}>
{item.spoiler ? <Spoiler>{media}</Spoiler> : media}
</Styles.MediaGalleryItem>
);
})}
</Styles.MediaGallery>
);
}

export default MediaGalleryComponent;
48 changes: 48 additions & 0 deletions src/Message/Components/MediaItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { APIUnfurledMediaItem } from "discord-api-types/v10";
import useSize from "../../Content/Attachment/useSize";
import React from "react";
import * as Styles from "./style";
import { t } from "i18next";

interface MediaItemProps {
media: APIUnfurledMediaItem;
description?: string | null;
}

function MediaItem({ media, description }: MediaItemProps) {
const hasDimensions =
typeof media.width === "number" && typeof media.height === "number";
const { width, height } = useSize(
media.width ?? 0,
media.height ?? 0,
!hasDimensions
);

if (media.content_type?.startsWith("video/")) {
return (
<Styles.MediaVideo
src={media.url}
width={width}
height={height}
controls
/>
);
}

return (
<Styles.MediaImage
src={media.url}
alt={description ?? undefined}
width={width}
height={height}
draggable={false}
placeholder={
<Styles.LazyImagePlaceholder style={{ width, height }}>
{t("loading")}
</Styles.LazyImagePlaceholder>
}
/>
);
}

export default MediaItem;
27 changes: 27 additions & 0 deletions src/Message/Components/SectionComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { APISectionComponent } from "discord-api-types/v10";
import Component from "./Component";
import React from "react";
import * as Styles from "./style";
import type { ChatMessage } from "../../types";

interface SectionComponentProps {
section: APISectionComponent;
message: ChatMessage;
}

function SectionComponent({ section, message }: SectionComponentProps) {
return (
<Styles.Section>
<Styles.SectionContent>
{section.components.map((component, index) => (
<Component component={component} message={message} key={index} />
))}
</Styles.SectionContent>
<Styles.SectionAccessory>
<Component component={section.accessory} message={message} />
</Styles.SectionAccessory>
</Styles.Section>
);
}

export default SectionComponent;
21 changes: 21 additions & 0 deletions src/Message/Components/SeparatorComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { APISeparatorComponent } from "discord-api-types/v10";
import { SeparatorSpacingSize } from "discord-api-types/v10";
import React from "react";
import * as Styles from "./style";

interface SeparatorComponentProps {
separator: APISeparatorComponent;
}

function SeparatorComponent({ separator }: SeparatorComponentProps) {
return (
<Styles.Separator
spacing={
separator.spacing === SeparatorSpacingSize.Large ? "large" : "small"
}
divider={separator.divider ?? true}
/>
);
}

export default SeparatorComponent;
18 changes: 18 additions & 0 deletions src/Message/Components/Spoiler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { useState } from "react";
import * as Styles from "./style";

interface SpoilerProps {
children: React.ReactNode;
}

function Spoiler({ children }: SpoilerProps) {
const [shown, setShown] = useState(false);

return (
<Styles.Spoiler data-show={shown} onClick={() => setShown(true)}>
{children}
</Styles.Spoiler>
);
}

export default Spoiler;
23 changes: 23 additions & 0 deletions src/Message/Components/TextDisplayComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { APITextDisplayComponent } from "discord-api-types/v10";
import { Markdown } from "../../markdown/render";
import React from "react";
import * as Styles from "./style";
import type { ChatMessage } from "../../types";

interface TextDisplayComponentProps {
textDisplay: APITextDisplayComponent;
message: ChatMessage;
}

function TextDisplayComponent({
textDisplay,
message,
}: TextDisplayComponentProps) {
return (
<Styles.TextDisplay>
<Markdown mentions={message.mentions}>{textDisplay.content}</Markdown>
</Styles.TextDisplay>
);
}

export default TextDisplayComponent;
Loading