The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord

improve discord markdown & palceholder

Changed files
+144 -149
app
(home)
dashboard
[guildId]
profile
spotify
components
public
+9 -9
app/(home)/page.tsx
··· 10 10 import DiscordAppBadge from "@/components/discord/app-badge"; 11 11 import DiscordChannel from "@/components/discord/channel"; 12 12 import DiscordChannelCategory from "@/components/discord/channel-category"; 13 - import Highlight from "@/components/discord/markdown"; 13 + import { DiscordMarkdown } from "@/components/discord/markdown"; 14 14 import DiscordMessage from "@/components/discord/message"; 15 15 import DiscordMessageEmbed from "@/components/discord/message-embed"; 16 16 import DiscordUser from "@/components/discord/user"; ··· 299 299 <div className="bg-[#313338] h-0.5 w-full sm:w-0.5 sm:h-32 md:h-0.5 md:w-full lg:w-0.5 lg:h-32 rounded-full ml-2" /> 300 300 301 301 <DiscordMessage {...messageProps("tts voice")}> 302 - <Highlight mode={"DARK"} text="Now talking..." /> 302 + <DiscordMarkdown mode={"DARK"} text="Now talking..." /> 303 303 </DiscordMessage> 304 304 </div> 305 305 ··· 366 366 style={{ backgroundColor: "rgb(43, 45, 49)" }} 367 367 > 368 368 <DiscordMessage {...messageProps()}> 369 - <Highlight mode={"DARK"} text="Hey **@everyone**, Linus Tech Tips just posted a new video!\n[youtube.com/watch?v=74Lj5cHseI8]()" /> 369 + <DiscordMarkdown mode={"DARK"} text="Hey **@everyone**, Linus Tech Tips just posted a new video!\n[youtube.com/watch?v=74Lj5cHseI8](https://youtube.com/watch?v=74Lj5cHseI8)" /> 370 370 <DiscordMessageEmbed 371 371 mode="DARK" 372 372 title="Your PC Can Look Like THIS Now!" ··· 441 441 style={{ backgroundColor: "rgb(43, 45, 49)" }} 442 442 > 443 443 <DiscordMessage {...messageProps("image")}> 444 - <Highlight mode={"DARK"} text="query: **femboy**" /> 444 + <DiscordMarkdown mode={"DARK"} text="query: **femboy**" /> 445 445 <Image 446 446 alt="" 447 447 className="rounded-md shadow-md w-64 md:w-56 lg:w-72 md:w-unset max-w-md mt-2" ··· 627 627 <span className="text-blue-500 hover:underline cursor-pointer">#lounge</span> 628 628 </div> 629 629 630 - <Highlight mode={"DARK"} text="**Replied to @drijon**" /> 631 - <Highlight mode={"DARK"} text="As if someone creates a discord account being like: OH I NEED TO KNOW THE GAS PRICES. THERE IS A NICE WAY FOR IT. MEE6 PREMIUM!" /> 630 + <DiscordMarkdown mode={"DARK"} text="**Replied to @drijon**" /> 631 + <DiscordMarkdown mode={"DARK"} text="As if someone creates a discord account being like: OH I NEED TO KNOW THE GAS PRICES. THERE IS A NICE WAY FOR IT. MEE6 PREMIUM!" /> 632 632 </DiscordMessageEmbed> 633 633 </DiscordMessage> 634 634 </div> ··· 676 676 style={{ backgroundColor: "rgb(43, 45, 49)" }} 677 677 > 678 678 <DiscordMessage {...messageProps()}> 679 - <Highlight mode={"DARK"} text="Welcome @mwlica to **Someone's** 👋" /> 679 + <DiscordMarkdown mode={"DARK"} text="Welcome @mwlica to **Someone's** 👋" /> 680 680 <Image 681 681 alt="example welcome card" 682 682 src={WelcomePic} ··· 795 795 bot: false 796 796 }} 797 797 > 798 - <Highlight mode={"DARK"} text="wm - howto" /> 798 + <DiscordMarkdown mode={"DARK"} text="wm - howto" /> 799 799 </DiscordMessage> 800 800 801 801 <DiscordMessage ··· 810 810 mode={"DARK"} 811 811 color={0xbc7ed4} 812 812 > 813 - <Highlight mode={"DARK"} text="To create a custom command, go to [your server's dashboard](/dashboard?to=custom-commands), click on `Create`, fill in the response **content**, **embed title**, **embed description**, **embed color**, **embed images**, command **permissions** and more. When you're done you can start using the command 🎉" /> 813 + <DiscordMarkdown mode={"DARK"} text="To create a custom command, go to [your server's dashboard](/dashboard?to=custom-commands), click on `Create`, fill in the response **content**, **embed title**, **embed description**, **embed color**, **embed images**, command **permissions** and more. When you're done you can start using the command 🎉" /> 814 814 </DiscordMessageEmbed> 815 815 </DiscordMessage> 816 816 </div>
+2 -2
app/dashboard/[guildId]/starboard/page.tsx
··· 8 8 import { useQuery } from "react-query"; 9 9 10 10 import { guildStore } from "@/common/guilds"; 11 - import Highlight from "@/components/discord/markdown"; 11 + import { DiscordMarkdown } from "@/components/discord/markdown"; 12 12 import DiscordMessage from "@/components/discord/message"; 13 13 import DiscordMessageEmbed from "@/components/discord/message-embed"; 14 14 import MultiSelectMenu from "@/components/inputs/multi-select-menu"; ··· 308 308 bot: true 309 309 }} 310 310 > 311 - <Highlight 311 + <DiscordMarkdown 312 312 mode={"DARK"} 313 313 text={""} 314 314 />
+8 -7
app/dashboard/[guildId]/tts.component.tsx
··· 1 + import { Accordion, AccordionItem } from "@nextui-org/react"; 2 + import Image from "next/image"; 3 + import Link from "next/link"; 4 + import { useParams } from "next/navigation"; 5 + import { useCookies } from "next-client-cookies"; 6 + 1 7 import { guildStore } from "@/common/guilds"; 2 8 import NumberInput from "@/components/inputs/number-input"; 3 9 import SelectMenu from "@/components/inputs/select-menu"; 4 10 import Switch from "@/components/inputs/switch"; 5 - import { Accordion, AccordionItem } from "@nextui-org/react"; 6 - import { useCookies } from "next-client-cookies"; 7 - import Image from "next/image"; 8 - import Link from "next/link"; 9 - import { useParams } from "next/navigation"; 10 11 11 12 export function TTSSettings() { 12 13 const guild = guildStore((g) => g); ··· 63 64 64 65 <Faq /> 65 66 </div> 66 - ) 67 + ); 67 68 } 68 69 69 70 function Faq() { ··· 111 112 </Link> 112 113 </AccordionItem> 113 114 </Accordion> 114 - ) 115 + ); 115 116 }
+5 -5
app/profile/spotify/page.tsx
··· 6 6 7 7 import { userStore } from "@/common/user"; 8 8 import Box from "@/components/box"; 9 - import Highlight from "@/components/discord/markdown"; 9 + import { DiscordMarkdown } from "@/components/discord/markdown"; 10 10 import DiscordMessage from "@/components/discord/message"; 11 11 import { HomeButton, ScreenMessage, SupportButton } from "@/components/screen-message"; 12 12 import { cacheOptions, getData } from "@/lib/api"; ··· 101 101 }} 102 102 > 103 103 104 - <Highlight mode={"DARK"} text={`wm play [https://open.spotify.com/track/${data.playing?.id || "4cOdK2wGLETKBW3PvgPWqT"}](#)`} /> 104 + <DiscordMarkdown mode={"DARK"} text={`wm play [https://open.spotify.com/track/${data.playing?.id || "4cOdK2wGLETKBW3PvgPWqT"}](#)`} /> 105 105 106 106 </DiscordMessage> 107 107 <DiscordMessage ··· 115 115 116 116 <div className="flex items-center gap-1"> 117 117 <Image src="https://cdn.discordapp.com/emojis/845043307351900183.gif?size=44&quality=lossless" height={18} width={18} alt="" /> 118 - <Highlight mode={"DARK"} text={`@${user.username} now playing [${data.playing?.name || "Never Gonna Give You Up"}](#) for **${data.playing?.duration || "3 minutes 33 seconds"}**`} /> 118 + <DiscordMarkdown mode={"DARK"} text={`@${user.username} now playing [${data.playing?.name || "Never Gonna Give You Up"}](#) for **${data.playing?.duration || "3 minutes 33 seconds"}**`} /> 119 119 </div> 120 120 121 121 <div className="flex flex-row gap-1.5 h-8 mt-3"> ··· 141 141 }} 142 142 > 143 143 144 - <Highlight mode={"DARK"} text="wm" /> 144 + <DiscordMarkdown mode={"DARK"} text="wm" /> 145 145 146 146 </DiscordMessage> 147 147 <DiscordMessage ··· 155 155 156 156 <div className="flex items-center gap-1"> 157 157 <Image src="https://cdn.discordapp.com/emojis/845043307351900183.gif?size=44&quality=lossless" height={18} width={18} alt="" /> 158 - <Highlight mode={"DARK"} text={`@${user.username} is playing [${data.playing?.name || "Never Gonna Give You Up"}](#) by ${data.playing?.artists || "[Rick Astley]()"}`} /> 158 + <DiscordMarkdown mode={"DARK"} text={`@${user.username} is playing [${data.playing?.name || "Never Gonna Give You Up"}](#) by ${data.playing?.artists || "[Rick Astley]()"}`} /> 159 159 </div> 160 160 161 161 <div className="flex gap-1.5 h-8 mt-3">
+39
components/discord/markdown.css
··· 1 + .discord-md a { 2 + @apply text-blurple hover:underline 3 + } 4 + 5 + .discord-md code { 6 + @apply bg-neutral-900 border-[1px] border-neutral-600 p-1 text-xs rounded 7 + } 8 + 9 + .discord-md-light code { 10 + @apply !bg-neutral-300 !border-neutral-400 11 + } 12 + 13 + .discord-md .d-mention { 14 + @apply bg-blurple/30 hover:bg-blurple/50 py-0.5 px-1 rounded-md dark:text-neutral-100 text-neutral-900 font-light duration-200 cursor-pointer 15 + } 16 + 17 + .discord-md .d-emoji { 18 + @apply size-5 inline 19 + } 20 + 21 + .discord-md .d-spoiler { 22 + @apply bg-neutral-500/75 px-0.5 text-sm rounded 23 + } 24 + 25 + .discord-md h1 { 26 + @apply text-3xl font-semibold text-white 27 + } 28 + 29 + .discord-md h2 { 30 + @apply text-2xl font-semibold text-white 31 + } 32 + 33 + .discord-md h3 { 34 + @apply text-xl font-semibold text-white 35 + } 36 + 37 + .discord-md strong { 38 + @apply font-semibold 39 + }
+16 -112
components/discord/markdown.tsx
··· 1 - "use client"; 1 + import "./markdown.css"; 2 2 3 - import React from "react"; 4 - import { renderToString } from "react-dom/server"; 5 - import ReactMarkdown from "react-markdown"; 6 - import rehypeRaw from "rehype-raw"; 3 + import md from "@odiffey/discord-markdown"; 7 4 8 5 import cn from "@/utils/cn"; 9 6 10 - import Channel from "../markdown/channel"; 11 - import Emoji from "../markdown/emoji"; 12 - import User from "../markdown/user"; 13 - 14 - interface Props { 15 - text: string; 16 - mode: "DARK" | "LIGHT"; 17 - discord?: boolean; 18 - } 19 - 20 - export default function Highlight({ 7 + export function DiscordMarkdown({ 21 8 text, 22 9 mode, 23 - discord = true 24 - }: Props) { 25 - 26 - function parseDiscordMarkdown(content: string) { 27 - return content 28 - .replace(/<(?!(?:[@#]|a:|:))/g, "&lt;") 29 - .replaceAll("\\n", "\n\n") 30 - .replace(/__(.*?)__/g, "<u>$1</u>") 31 - .replace(/\{(\w*?)\.(\w*?)\}|{ping}/g, (match) => { 32 - return renderToString( 33 - <span 34 - className={cn( 35 - mode === "DARK" ? "bg-wamellow text-neutral-200" : "bg-wamellow-100 text-neutral-800", 36 - "border-1 border-violet-400 px-[3px] rounded-md font-light" 37 - )} 38 - > 39 - {match.slice(1, -1)} 40 - </span> 41 - ); 42 - }) 43 - .replace(/<a?:\w{2,32}:\d{15,21}>/g, (match) => { 44 - const emojiId = match.match(/\d{15,21}/)?.[0]!; 45 - 46 - return renderToString(<Emoji emojiId={emojiId} />); 47 - }) 48 - .replace(/<(@[!&]?)\d{15,21}>/g, (match) => { 49 - return renderToString(<User username={match.includes("&") ? "some-role" : "some-user"} />); 50 - }) 51 - .replace(/<(#!?)\d{15,21}>/g, () => { 52 - return renderToString(<Channel name="some-channel" />); 53 - }); 54 - } 55 - 56 - if (!discord) return ( 57 - <ReactMarkdown 58 - // @ts-expect-error they broke types 59 - rehypePlugins={[rehypeRaw]} 60 - allowedElements={["span", "p"]} 61 - > 62 - {parseDiscordMarkdown(text 63 - .replaceAll("*", "\\*") 64 - .replaceAll("_", "\\_") 65 - .replaceAll("~", "\\~") 66 - .replaceAll("`", "\\`") 67 - )} 68 - </ReactMarkdown> 69 - ); 10 + embed = true 11 + }: { 12 + text: string; 13 + mode: "DARK" | "LIGHT"; 14 + embed?: boolean; 15 + }) { 16 + const sanitizedHtml = text 17 + .replaceAll("\\n", "\n") 18 + .trim(); 70 19 71 20 return ( 72 - <ReactMarkdown 73 - className="break-words" 74 - // @ts-expect-error inline does exist 75 - rehypePlugins={[rehypeRaw]} 76 - components={{ 77 - h1: (props) => <div className="text-3xl font-semibold" {...props} />, 78 - h2: (props) => <div className="text-2xl font-semibold" {...props} />, 79 - h3: (props) => <div className="text-xl font-semibold" {...props} />, 80 - strong: (props) => <span className="font-semibold" {...props} />, 81 - i: (props) => <span className="italic" {...props} />, 82 - a: (props) => <a className="text-blue-600 hover:underline underline-blue-500" {...props} />, 83 - del: (props) => <span className="line-through" {...props} />, 84 - ins: (props) => <span className="underline" {...props} />, 85 - li: (props) => ( 86 - <div> 87 - <span className="mr-1">•</span> 88 - <span {...props} /> 89 - </div> 90 - ), 91 - code: ({ inline, children, ...props }) => { 92 - if (!inline) return ( 93 - <div 94 - className={cn( 95 - mode === "DARK" ? "bg-neutral-900" : "bg-neutral-200", 96 - "px-4 py-3 text-sm rounded-md min-w-full max-w-full my-2 break-all" 97 - )} 98 - > 99 - {children} 100 - </div> 101 - ); 102 - 103 - return ( 104 - <code 105 - {...props} 106 - className={cn( 107 - mode === "DARK" ? "bg-neutral-900 text-neutral-100" : "bg-neutral-200 text-neutral-900", 108 - "p-1 text-sm rounded" 109 - )} 110 - > 111 - {children} 112 - </code> 113 - ); 114 - }, 115 - p: (props) => <p className="mb-4" {...props} /> 116 - }} 117 - > 118 - {parseDiscordMarkdown(text)} 119 - </ReactMarkdown> 21 + <div 22 + className={cn("discord-md", mode === "LIGHT" && "discord-md-light")} 23 + dangerouslySetInnerHTML={{ __html: md.toHTML(sanitizedHtml, { embed }) }} 24 + /> 120 25 ); 121 - 122 26 }
+20 -8
components/discord/message-embed.tsx
··· 2 2 3 3 import cn from "@/utils/cn"; 4 4 5 - import Highlight from "./markdown"; 5 + import { DiscordMarkdown } from "./markdown"; 6 6 7 7 interface Props { 8 8 children: React.ReactNode; ··· 62 62 > 63 63 {/* eslint-disable-next-line @next/next/no-img-element */} 64 64 {author.icon_url && <img src={author.icon_url} alt="" className="rounded-full h-6 w-6" />} 65 - <Highlight 65 + <DiscordMarkdown 66 66 mode={mode} 67 67 text={author.text} 68 - discord={false} 68 + embed={true} 69 69 /> 70 70 </div> 71 71 } ··· 76 76 "font-semibold text-lg mb-2" 77 77 )} 78 78 > 79 - <Highlight 79 + <DiscordMarkdown 80 80 mode={mode} 81 81 text={title} 82 - discord={false} 82 + embed={true} 83 83 /> 84 84 </div> 85 85 } ··· 89 89 </div> 90 90 91 91 {/* eslint-disable-next-line @next/next/no-img-element */} 92 - {thumbnail && <img src={thumbnail} alt="" className="ml-auto h-20 w-20 rounded-md" />} 92 + {thumbnail && <img src={replaceTemplatesToUrl(thumbnail)} alt="" className="ml-auto h-20 w-20 rounded-md" />} 93 93 </div> 94 94 95 95 {/* eslint-disable-next-line @next/next/no-img-element */} 96 - {image && <img src={image} alt="" className="ml-auto rounded-md h-full w-full mt-4" />} 96 + {image && <img src={replaceTemplatesToUrl(image)} alt="" className="ml-auto rounded-md h-full w-full mt-4" />} 97 97 98 98 {footer?.text && 99 99 <div className="flex gap-1 items-center mt-3"> 100 100 {/* eslint-disable-next-line @next/next/no-img-element */} 101 101 {footer.icon_url && <img src={footer.icon_url} alt="" className="rounded-full h-5 w-5" />} 102 102 <span className="text-xs"> 103 - <Highlight mode={mode} text={footer.text} discord={false} /> 103 + <DiscordMarkdown 104 + mode={mode} 105 + text={footer.text} 106 + embed={true} 107 + /> 104 108 </span> 105 109 </div> 106 110 } 107 111 108 112 </div> 109 113 ); 114 + } 115 + 116 + function replaceTemplatesToUrl(input: string) { 117 + if (/^{(user|guild|creator)\.(icon|avatar)}$/.test(input)) return "https://cdn.discordapp.com/embed/avatars/0.png"; 118 + if (/^{video\.thumbnail}/.test(input)) return "/_next/image?url=/notifications-thumbnail-placeholder.webp&w=384&q=75"; 119 + 120 + if (!input.startsWith("http")) return; 121 + return input; 110 122 }
+3 -3
components/embed-creator.tsx
··· 7 7 import { GuildEmbed } from "@/typings"; 8 8 import cn from "@/utils/cn"; 9 9 10 - import Highlight from "./discord/markdown"; 10 + import { DiscordMarkdown } from "./discord/markdown"; 11 11 import DiscordMessage from "./discord/message"; 12 12 import DiscordMessageEmbed from "./discord/message-embed"; 13 13 import DumbColorInput from "./inputs/dumb-color-input"; ··· 238 238 bot: true 239 239 }} 240 240 > 241 - <Highlight 241 + <DiscordMarkdown 242 242 mode={mode} 243 243 text={content || ""} 244 244 /> ··· 251 251 image={JSON.parse(embed).image} 252 252 footer={JSON.parse(embedfooter)} 253 253 > 254 - {JSON.parse(embed).description && <Highlight mode={mode} text={JSON.parse(embed).description} />} 254 + {JSON.parse(embed).description && <DiscordMarkdown mode={mode} text={JSON.parse(embed).description} />} 255 255 {showMessageAttachmentComponentInEmbed && messageAttachmentComponent} 256 256 </DiscordMessageEmbed> 257 257
+4 -3
components/markdown/index.tsx
··· 40 40 return content 41 41 .replace(/__(.*?)__/g, "<u>$1</u>") 42 42 .replace(/<a?:\w{2,32}:\d{15,21}>/g, (match) => { 43 - const emojiId = match.match(/\d{15,21}/)?.[0]!; 43 + const emojiId = match.match(/\d{15,21}/)?.[0] as string; 44 44 45 45 return renderToString(<Emoji emojiId={emojiId} />); 46 46 }) ··· 57 57 return renderToString(<Channel name="some-channel" />); 58 58 }) 59 59 .replace(/<t:\d{1,10}:[Rf]?>/g, (match) => { 60 - const timestamp = match.match(/\d{1,10}/)?.[0]!; 60 + const timestamp = match.match(/\d{1,10}/)?.[0] as string; 61 61 const format = match.match(/:\w*?>/)?.[0] || "f"; 62 62 63 63 return renderToString( ··· 182 182 }, 183 183 184 184 ol: ({ ordered, ...props }) => <ol className="list-decimal list-inside space-y-1 marker:text-neutral-300/40 my-1" {...props} />, 185 - ul: ({ ordered, ...props }) => <ul className="list-disc list-inside space-y-1 marker:text-neutral-300/40 my-1" {...props} /> 185 + ul: ({ ordered, ...props }) => <ul className="list-disc list-inside space-y-1 marker:text-neutral-300/40 my-1" {...props} />, 186 + p: (props) => <span {...props} /> 186 187 187 188 }} 188 189 >
+1
package.json
··· 12 12 "@discordjs/collection": "^2.1.0", 13 13 "@discordjs/rest": "^2.2.0", 14 14 "@nextui-org/react": "^2.4.6", 15 + "@odiffey/discord-markdown": "^3.1.2", 15 16 "autoprefixer": "^10.4.19", 16 17 "clsx": "^2.1.1", 17 18 "discord-api-types": "^0.37.93",
+37
pnpm-lock.yaml
··· 17 17 '@nextui-org/react': 18 18 specifier: ^2.4.6 19 19 version: 2.4.6(@types/react@18.3.3)(framer-motion@11.3.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.7) 20 + '@odiffey/discord-markdown': 21 + specifier: ^3.1.2 22 + version: 3.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 20 23 autoprefixer: 21 24 specifier: ^10.4.19 22 25 version: 10.4.19(postcss@8.4.40) ··· 336 339 337 340 '@jridgewell/trace-mapping@0.3.25': 338 341 resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 342 + 343 + '@khanacademy/perseus-core@1.5.0': 344 + resolution: {integrity: sha512-QR9tGBr8nAUFuARSbzgzL6ZkyjDur0Cz9tgQREjC0L+Sug5aj0DHuK86lhosZAbtgAzitX3KVRAsOMbKqU2fYg==} 345 + 346 + '@khanacademy/simple-markdown@0.12.1': 347 + resolution: {integrity: sha512-GnrK+mxULyO58pWjPQSU1bVUd892teNIzf7stdBB9mucFDXk2TiplPD9BJDUZ10uGfliyDVKlvwV3BIjHPhL5g==} 348 + peerDependencies: 349 + react: 16.14.0 350 + react-dom: 16.14.0 339 351 340 352 '@next/env@14.2.5': 341 353 resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} ··· 894 906 895 907 '@octokit/types@13.5.0': 896 908 resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} 909 + 910 + '@odiffey/discord-markdown@3.1.2': 911 + resolution: {integrity: sha512-V+lVkoMhiNVEyLJ4ezmvigHU91SwiACc13jcBDosfkUbcLK82YXG+L6Dkzuaw7EY34Gh4HGO6+n8ZtwEF1WZJQ==} 897 912 898 913 '@pkgjs/parseargs@0.11.0': 899 914 resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} ··· 2480 2495 2481 2496 hastscript@8.0.0: 2482 2497 resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} 2498 + 2499 + highlight.js@11.10.0: 2500 + resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} 2501 + engines: {node: '>=12.0.0'} 2483 2502 2484 2503 html-void-elements@3.0.0: 2485 2504 resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} ··· 4090 4109 '@jridgewell/resolve-uri': 3.1.2 4091 4110 '@jridgewell/sourcemap-codec': 1.5.0 4092 4111 4112 + '@khanacademy/perseus-core@1.5.0': {} 4113 + 4114 + '@khanacademy/simple-markdown@0.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 4115 + dependencies: 4116 + '@khanacademy/perseus-core': 1.5.0 4117 + react: 18.3.1 4118 + react-dom: 18.3.1(react@18.3.1) 4119 + 4093 4120 '@next/env@14.2.5': {} 4094 4121 4095 4122 '@next/eslint-plugin-next@14.2.5': ··· 5101 5128 '@octokit/types@13.5.0': 5102 5129 dependencies: 5103 5130 '@octokit/openapi-types': 22.2.0 5131 + 5132 + '@odiffey/discord-markdown@3.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 5133 + dependencies: 5134 + '@khanacademy/simple-markdown': 0.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 5135 + highlight.js: 11.10.0 5136 + transitivePeerDependencies: 5137 + - react 5138 + - react-dom 5104 5139 5105 5140 '@pkgjs/parseargs@0.11.0': 5106 5141 optional: true ··· 7314 7349 hast-util-parse-selector: 4.0.0 7315 7350 property-information: 6.5.0 7316 7351 space-separated-tokens: 2.0.2 7352 + 7353 + highlight.js@11.10.0: {} 7317 7354 7318 7355 html-void-elements@3.0.0: {} 7319 7356
public/notifications-thumbnail-placeholder.webp

This is a binary file and will not be displayed.