Merge pull request 'feat: cards' (#44) from cards into main

Reviewed-on: Kieran/stream#44
This commit is contained in:
Kieran 2023-07-26 20:53:26 +00:00
commit ba40302b5a
33 changed files with 1873 additions and 82 deletions

View File

@ -34,6 +34,7 @@
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-intersection-observer": "^9.5.1",
"react-markdown": "^8.0.7",
"react-router-dom": "^6.13.0",
"react-tag-input-component": "^2.0.2",
"semantic-sdp": "^3.26.2",

View File

@ -54,5 +54,8 @@
<path d="M17.7084 10.1607C18.1683 13.3466 14.8705 14.0207 12.9733 13.9618C12.8515 13.958 12.7366 14.0173 12.6647 14.1157C12.4684 14.384 12.1547 14.7309 11.9125 14.7309C11.6405 14.7309 11.3957 15.254 11.284 15.5795C11.2723 15.6137 11.3059 15.6452 11.3403 15.634C14.345 14.6584 15.5241 14.3238 16.032 14.4178C16.4421 14.4937 17.209 15.8665 17.5413 16.5434C16.7155 16.5909 16.4402 15.8507 16.2503 15.7178C16.0985 15.6116 16.0415 16.0974 16.032 16.3536C15.8517 16.2587 15.6239 16.1259 15.6049 15.7178C15.5859 15.3098 15.3771 15.4142 15.2157 15.4332C15.0544 15.4521 12.5769 16.2493 12.2067 16.3536C11.8366 16.458 11.4094 16.6004 11.0582 16.8471C10.4697 17.1318 10.09 16.9325 9.98561 16.4485C9.90208 16.0614 10.4444 14.8701 10.726 14.3229C10.3779 14.4526 9.65529 14.7158 9.54898 14.7309C9.44588 14.7457 8.13815 15.7552 7.43879 16.3038C7.398 16.3358 7.37174 16.3827 7.36236 16.4336C7.25047 17.0416 6.89335 17.2118 6.27423 17.5303C5.77602 17.7867 4.036 20.4606 3.14127 21.9041C3.0794 22.0039 2.9886 22.0806 2.8911 22.1461C2.32279 22.5276 1.74399 23.4985 1.50923 23.9737C1.17511 23.0095 1.61048 22.1802 1.86993 21.886C1.75602 21.7873 1.49341 21.8449 1.37634 21.886C1.69907 20.7757 2.82862 20.7757 2.79066 20.7757C2.99948 20.5954 5.44842 17.0938 5.50538 16.9325C5.56187 16.7725 5.46892 16.0242 6.69975 15.6139C6.7193 15.6073 6.73868 15.5984 6.75601 15.5873C7.71493 14.971 8.43427 13.9774 8.67571 13.5542C7.39547 13.4662 5.92943 12.7525 5.16289 12.294C4.99765 12.1952 4.8224 12.1092 4.63108 12.0875C3.58154 11.9687 2.53067 12.6401 2.10723 13.0228C1.93258 12.7799 2.12938 12.0739 2.24961 11.7513C1.82437 11.6905 1.19916 12.308 0.939711 12.6243C0.658747 12.184 0.904907 11.397 1.06311 11.0585C0.501179 11.0737 0.120232 11.3306 0 11.4571C0.465109 7.99343 4.02275 9.00076 4.06259 9.04675C3.87275 8.84937 3.88857 8.59126 3.92021 8.48688C6.0749 8.54381 7.08105 8.18321 7.71702 7.81313C12.7288 5.01374 14.8882 6.73133 15.6856 7.1631C16.4829 7.59487 17.9304 7.77042 18.9318 7.37187C20.1278 6.83097 19.9478 5.43673 19.7054 4.90461C19.4397 4.32101 17.9399 3.51438 17.4084 2.49428C16.8768 1.47418 17.34 0.233672 17.9558 0.0607684C18.5425 -0.103972 18.9615 0.0876835 19.2831 0.378128C19.4974 0.571763 20.0994 0.710259 20.3509 0.800409C20.6024 0.890558 21.0201 1.00918 20.9964 1.08035C20.9726 1.15152 20.5699 1.14202 20.5075 1.14202C20.3794 1.14202 20.2275 1.161 20.3794 1.23217C20.5575 1.30439 20.8263 1.40936 20.955 1.47846C20.9717 1.48744 20.9683 1.51084 20.95 1.51577C20.0765 1.75085 19.2966 1.26578 18.7183 1.82526C18.1298 2.39463 19.3827 2.83114 20.0282 3.51438C20.6736 4.19762 21.3381 5.01372 20.8065 6.87365C20.395 8.31355 18.6703 9.53781 17.7795 10.0167C17.7282 10.0442 17.7001 10.1031 17.7084 10.1607Z" fill="currentColor"/>
</g>
</symbol>
<symbol id="plus" viewBox="0 0 24 24" fill="none">
<path d="M12 5V19M5 12H19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -2,4 +2,8 @@ import { EventKind } from "@snort/system";
export const LIVE_STREAM = 30_311 as EventKind;
export const LIVE_STREAM_CHAT = 1_311 as EventKind;
export const EMOJI_PACK = 30_030 as EventKind;
export const USER_EMOJIS = 10_030 as EventKind;
export const GOAL = 9041 as EventKind;
export const USER_CARDS = 17_777 as EventKind;
export const CARD = 37_777 as EventKind;

19
src/element/Address.tsx Normal file
View File

@ -0,0 +1,19 @@
import { type NostrLink } from "@snort/system";
import { useEvent } from "hooks/event";
import { EMOJI_PACK } from "const";
import { EmojiPack } from "element/emoji-pack";
interface AddressProps {
link: NostrLink;
}
export function Address({ link }: AddressProps) {
const event = useEvent(link);
if (event?.kind === EMOJI_PACK) {
return <EmojiPack ev={event} />;
}
return null;
}

33
src/element/Event.tsx Normal file
View File

@ -0,0 +1,33 @@
import "./event.css";
import { type NostrLink, EventKind } from "@snort/system";
import { useEvent } from "hooks/event";
import { GOAL } from "const";
import { Goal } from "element/goal";
import { Note } from "element/note";
interface EventProps {
link: NostrLink;
}
export function Event({ link }: EventProps) {
const event = useEvent(link);
if (event && event.kind === GOAL) {
return (
<div className="event-container">
<Goal ev={event} />
</div>
);
}
if (event && event.kind === EventKind.TextNote) {
return (
<div className="event-container">
<Note ev={event} />
</div>
);
}
return <code>{link.id}</code>;
}

0
src/element/address.css Normal file
View File

View File

@ -58,7 +58,7 @@ export function ChatMessage({
const login = useLogin();
const profile = useUserProfile(
System,
inView?.isIntersecting ? ev.pubkey : undefined
inView?.isIntersecting ? ev.pubkey : undefined,
);
const zapTarget = profile?.lud16 ?? profile?.lud06;
const zaps = useMemo(() => {
@ -178,16 +178,16 @@ export function ChatMessage({
style={
isTablet
? {
display: showZapDialog || isHovering ? "flex" : "none",
}
display: showZapDialog || isHovering ? "flex" : "none",
}
: {
position: "fixed",
top: topOffset ? topOffset - 12 : 0,
left: leftOffset ? leftOffset - 32 : 0,
opacity: showZapDialog || isHovering ? 1 : 0,
pointerEvents:
showZapDialog || isHovering ? "auto" : "none",
}
position: "fixed",
top: topOffset ? topOffset - 12 : 0,
left: leftOffset ? leftOffset - 32 : 0,
opacity: showZapDialog || isHovering ? 1 : 0,
pointerEvents:
showZapDialog || isHovering ? "auto" : "none",
}
}
>
{zapTarget && (

View File

@ -0,0 +1,32 @@
.emoji-pack-title {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.emoji-pack-title a {
font-size: 14px;
}
.emoji-pack-emojis {
margin-top: 12px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 4px;
}
.emoji-definition {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.emoji-name {
font-size: 10px;
}
.emoji-pack h4 {
margin: 0;
}

View File

@ -0,0 +1,29 @@
import "./emoji-pack.css";
import { type NostrEvent } from "@snort/system";
import { Mention } from "element/mention";
import { findTag } from "utils";
export function EmojiPack({ ev }: { ev: NostrEvent }) {
const name = findTag(ev, "d");
const emoji = ev.tags.filter((e) => e.at(0) === "emoji");
return (
<div className="emoji-pack">
<div className="emoji-pack-title">
<h4>{name}</h4>
<Mention pubkey={ev.pubkey} />
</div>
<div className="emoji-pack-emojis">
{emoji.map((e) => {
const [, name, image] = e;
return (
<div className="emoji-definition">
<img alt={name} className="emoji" src={image} />
<span className="emoji-name">{name}</span>
</div>
);
})}
</div>
</div>
);
}

8
src/element/event.css Normal file
View File

@ -0,0 +1,8 @@
.event-container .goal {
font-size: 14px;
}
.event-container .goal .amount {
top: -8px;
font-size: 10px;
}

View File

@ -0,0 +1,7 @@
export function ExternalLink({ children, href }) {
return (
<a href={href} rel="noopener noreferrer" target="_blank">
{children}
</a>
)
}

View File

@ -0,0 +1,27 @@
.file-uploader-container {
display: flex;
justify-content: space-between;
}
.file-uploader input[type="file"] {
display: none;
}
.file-uploader {
align-self: flex-start;
background: white;
color: black;
max-width: 100px;
border-radius: 10px;
padding: 6px 12px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.image-preview {
width: 82px;
height: 60px;
border-radius: 10px;
}

View File

@ -0,0 +1,75 @@
import "./file-uploader.css";
import { VoidApi } from "@void-cat/api";
import { useState } from "react";
const voidCatHost = "https://void.cat";
const fileExtensionRegex = /\.([\w]{1,7})$/i;
const voidCatApi = new VoidApi(voidCatHost);
type UploadResult = {
url?: string;
error?: string;
};
async function voidCatUpload(file: File | Blob): Promise<UploadResult> {
const uploader = voidCatApi.getUploader(file);
const rsp = await uploader.upload({
"V-Strip-Metadata": "true",
});
if (rsp.ok) {
let ext = file.name.match(fileExtensionRegex);
if (rsp.file?.metadata?.mimeType === "image/webp") {
ext = ["", "webp"];
}
const resultUrl =
rsp.file?.metadata?.url ??
`${voidCatHost}/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}`;
const ret = {
url: resultUrl,
} as UploadResult;
return ret;
} else {
return {
error: rsp.errorMessage,
};
}
}
export function FileUploader({ onFileUpload }) {
const [img, setImg] = useState();
const [isUploading, setIsUploading] = useState(false);
async function onFileChange(ev) {
const file = ev.target.files[0];
if (file) {
try {
setIsUploading(true);
const upload = await voidCatUpload(file);
if (upload.url) {
setImg(upload.url);
onFileUpload(upload.url);
}
if (upload.error) {
console.error(upload.error);
}
} catch (error) {
console.error(error);
} finally {
setIsUploading(false);
}
}
}
return (
<div className="file-uploader-container">
<label className="file-uploader">
<input type="file" onChange={onFileChange} />
{isUploading ? "Uploading..." : "Add File"}
</label>
{img && <img className="image-preview" src={img} />}
</div>
);
}

View File

@ -13,8 +13,8 @@ export function LoggedInFollowButton({
}) {
const login = useLogin();
const following = useFollows(loggedIn, true);
const { tags, relays } = following ? following : { tags: [], relays: {} }
const follows = tags.filter((t) => t.at(0) === "p")
const { tags, relays } = following ? following : { tags: [], relays: {} };
const follows = tags.filter((t) => t.at(0) === "p");
const isFollowing = follows.find((t) => t.at(1) === pubkey);
async function unfollow() {
@ -23,7 +23,7 @@ export function LoggedInFollowButton({
const ev = await pub.generic((eb) => {
eb.kind(EventKind.ContactList).content(JSON.stringify(relays));
for (const t of tags) {
const isFollow = t.at(0) === "p" && t.at(1) === pubkey
const isFollow = t.at(0) === "p" && t.at(1) === pubkey;
if (!isFollow) {
eb.tag(t);
}

View File

@ -2,19 +2,23 @@ import "./goal.css";
import { useMemo } from "react";
import * as Progress from "@radix-ui/react-progress";
import Confetti from "react-confetti";
import { ParsedZap, NostrEvent } from "@snort/system";
import { Icon } from "./icon";
import { type NostrEvent } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { findTag } from "utils";
import { formatSats } from "number";
import usePreviousValue from "hooks/usePreviousValue";
import { SendZapsDialog } from "element/send-zap";
import { useZaps } from "hooks/goals";
import { getName } from "element/profile";
import { System } from "index";
import { Icon } from "./icon";
export function Goal({
ev,
zaps,
}: {
ev: NostrEvent;
zaps: ParsedZap[];
}) {
export function Goal({ ev }: { ev: NostrEvent }) {
const profile = useUserProfile(System, ev.pubkey);
const zapTarget = profile?.lud16 ?? profile?.lud06;
const zaps = useZaps(ev, true);
const goalAmount = useMemo(() => {
const amount = findTag(ev, "amount");
return amount ? Number(amount) / 1000 : null;
@ -34,8 +38,8 @@ export function Goal({
const isFinished = progress >= 100;
const previousValue = usePreviousValue(isFinished);
return (
<div className="goal">
const goalContent = (
<div className="goal" style={{ cursor: zapTarget ? "pointer" : "auto" }}>
{ev.content.length > 0 && <p>{ev.content}</p>}
<div className={`progress-container ${isFinished ? "finished" : ""}`}>
<Progress.Root className="progress-root" value={progress}>
@ -61,4 +65,16 @@ export function Goal({
)}
</div>
);
return zapTarget ? (
<SendZapsDialog
lnurl={zapTarget}
pubkey={ev.pubkey}
eTag={ev?.id}
targetName={getName(ev.pubkey, profile)}
button={goalContent}
/>
) : (
goalContent
);
}

View File

@ -1,25 +1,63 @@
import { NostrLink } from "./nostr-link";
const FileExtensionRegex = /\.([\w]+)$/i;
interface HyperTextProps {
link: string;
}
export function HyperText({ link }: HyperTextProps) {
export function HyperText({ link, children }: HyperTextProps) {
try {
const url = new URL(link);
if (url.protocol === "nostr:" || url.protocol === "web+nostr:") {
const extension =
FileExtensionRegex.test(url.pathname.toLowerCase()) && RegExp.$1;
if (extension) {
switch (extension) {
case "gif":
case "jpg":
case "jpeg":
case "png":
case "bmp":
case "webp": {
return (
<img
src={url.toString()}
alt={url.toString()}
objectFit="contain"
/>
);
}
case "wav":
case "mp3":
case "ogg": {
return <audio key={url.toString()} src={url.toString()} controls />;
}
case "mp4":
case "mov":
case "mkv":
case "avi":
case "m4v":
case "webm": {
return <video key={url.toString()} src={url.toString()} controls />;
}
default:
return <a href={url.toString()}>{children || url.toString()}</a>;
}
} else if (url.protocol === "nostr:" || url.protocol === "web+nostr:") {
return <NostrLink link={link} />;
} else {
<a href={link} target="_blank" rel="noreferrer">
{link}
{children}
</a>;
}
} catch {
} catch (error) {
console.error(error);
// Ignore the error.
}
return (
<a href={link} target="_blank" rel="noreferrer">
{link}
{children}
</a>
);
}

View File

@ -309,12 +309,6 @@
line-height: 22px;
}
.message-reaction .emoji {
width: 15px;
height: 15px;
margin-bottom: -2px;
}
.zap-pill-amount {
text-transform: lowercase;
color: #FFF;
@ -347,4 +341,4 @@
.write-emoji-button:hover {
color: white;
}
}

View File

@ -88,20 +88,9 @@ export function LiveChat({
const zaps = feed.zaps
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
.filter((z) => z && z.valid);
const goalZaps = feed.zaps
.filter((ev) =>
goal
? ev.created_at > goal.created_at &&
ev.tags.some((t) => t[0] === "e" && t[1] === goal.id)
: false
)
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
.filter((z) => z && z.valid);
const events = useMemo(() => {
return [...feed.messages, ...feed.zaps].sort(
(a, b) => b.created_at - a.created_at
(a, b) => b.created_at - a.created_at,
);
}, [feed.messages, feed.zaps]);
const streamer = getHost(ev);
@ -112,7 +101,7 @@ export function LiveChat({
findTag(ev, "d") ?? "",
undefined,
ev.kind,
ev.pubkey
ev.pubkey,
);
}
}, [ev]);
@ -125,7 +114,13 @@ export function LiveChat({
<Icon
name="link"
size={32}
onClick={() => window.open(`/chat/${naddr}?chat=true`, "_blank", "popup,width=400,height=800")}
onClick={() =>
window.open(
`/chat/${naddr}?chat=true`,
"_blank",
"popup,width=400,height=800",
)
}
/>
</div>
)}
@ -135,7 +130,7 @@ export function LiveChat({
<div className="top-zappers-container">
<TopZappers zaps={zaps} />
</div>
{goal && <Goal ev={goal} zaps={goalZaps} />}
{goal && <Goal ev={goal} />}
{login?.pubkey === streamer && <NewGoalDialog link={link} />}
</div>
)}
@ -155,7 +150,7 @@ export function LiveChat({
}
case EventKind.ZapReceipt: {
const zap = zaps.find(
(b) => b.id === a.id && b.receiver === streamer
(b) => b.id === a.id && b.receiver === streamer,
);
if (zap) {
return <ChatZap zap={zap} key={a.id} />;
@ -179,7 +174,7 @@ export function LiveChat({
);
}
const BIG_ZAP_THRESHOLD = 100_000;
const BIG_ZAP_THRESHOLD = 50_000;
function ChatZap({ zap }: { zap: ParsedZap }) {
if (!zap.valid) {
@ -202,7 +197,7 @@ function ChatZap({ zap }: { zap: ParsedZap }) {
<span className="zap-amount">{formatSats(zap.amount)}</span>
sats
</div>
{zap.content && (
{zap.content && (
<div className="zap-content">
<Text content={zap.content} tags={[]} />
</div>

24
src/element/markdown.css Normal file
View File

@ -0,0 +1,24 @@
.markdown a {
color: var(--text-link);
}
.markdown > ul, .markdown > ol {
margin: 0;
padding: 0 12px;
font-size: 18px;
font-weight: 400;
line-height: 29px;
}
.markdown > p {
font-size: 18px;
font-style: normal;
overflow-wrap: break-word;
font-weight: 400;
line-height: 29px; /* 161.111% */
}
.markdown > img {
max-height: 230px;
width: 100%;
}

216
src/element/markdown.tsx Normal file
View File

@ -0,0 +1,216 @@
import "./markdown.css";
import { parseNostrLink } from "@snort/system";
import type { ReactNode } from "react";
import { useMemo } from "react";
import ReactMarkdown from "react-markdown";
import { Address } from "element/Address";
import { Event } from "element/Event";
import { Mention } from "element/mention";
import { Emoji } from "element/emoji";
import { HyperText } from "element/hypertext";
const MentionRegex = /(#\[\d+\])/gi;
const NostrPrefixRegex = /^nostr:/;
const EmojiRegex = /:([\w-]+):/g;
function extractEmoji(fragments: Fragment[], tags: string[][]) {
return fragments
.map((f) => {
if (typeof f === "string") {
return f.split(EmojiRegex).map((i) => {
const t = tags.find((a) => a[0] === "emoji" && a[1] === i);
if (t) {
return <Emoji name={t[1]} url={t[2]} />;
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractMentions(fragments, tags) {
return fragments
.map((f) => {
if (typeof f === "string") {
return f.split(MentionRegex).map((match) => {
const matchTag = match.match(/#\[(\d+)\]/);
if (matchTag && matchTag.length === 2) {
const idx = parseInt(matchTag[1]);
const ref = tags?.find((a, i) => i === idx);
if (ref) {
switch (ref[0]) {
case "p": {
return <Mention key={ref[1]} pubkey={ref[1]} />;
}
case "a": {
return <Address link={parseNostrLink(ref[1])} />;
}
default:
// todo: e and t mentions
return ref[1];
}
}
return null;
} else {
return match;
}
});
}
return f;
})
.flat();
}
function extractNprofiles(fragments) {
return fragments
.map((f) => {
if (typeof f === "string") {
return f.split(/(nostr:nprofile1[a-z0-9]+)/g).map((i) => {
if (i.startsWith("nostr:nprofile1")) {
try {
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
return <Mention key={link.id} pubkey={link.id} />;
} catch (error) {
return i;
}
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractNpubs(fragments) {
return fragments
.map((f) => {
if (typeof f === "string") {
return f.split(/(nostr:npub1[a-z0-9]+)/g).map((i) => {
if (i.startsWith("nostr:npub1")) {
try {
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
return <Mention key={link.id} pubkey={link.id} />;
} catch (error) {
return i;
}
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractNevents(fragments) {
return fragments
.map((f) => {
if (typeof f === "string") {
return f.split(/(nostr:nevent1[a-z0-9]+)/g).map((i) => {
if (i.startsWith("nostr:nevent1")) {
try {
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
return <Event link={link} />;
} catch (error) {
return i;
}
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractNaddrs(fragments) {
return fragments
.map((f) => {
if (typeof f === "string") {
return f.split(/(nostr:naddr1[a-z0-9]+)/g).map((i) => {
if (i.startsWith("nostr:naddr1")) {
try {
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
return <Address key={i} link={link} />;
} catch (error) {
console.error(error);
return i;
}
} else {
return i;
}
});
}
return f;
})
.flat();
}
function extractNoteIds(fragments) {
return fragments
.map((f) => {
if (typeof f === "string") {
return f.split(/(nostr:note1[a-z0-9]+)/g).map((i) => {
if (i.startsWith("nostr:note1")) {
try {
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
return <Event link={link} />;
} catch (error) {
return i;
}
} else {
return i;
}
});
}
return f;
})
.flat();
}
function transformText(ps, tags) {
let fragments = extractMentions(ps, tags);
fragments = extractNprofiles(fragments);
fragments = extractNevents(fragments);
fragments = extractNaddrs(fragments);
fragments = extractNoteIds(fragments);
fragments = extractNpubs(fragments);
fragments = extractEmoji(fragments, tags);
return fragments;
}
interface MarkdownProps {
children: ReactNode;
tags?: string[];
}
export function Markdown({ children, tags = [] }: MarkdownProps) {
const components = useMemo(() => {
return {
li: ({ children, ...props }) => {
return children && <li {...props}>{transformText(children, tags)}</li>;
},
td: ({ children }) =>
children && <td>{transformText(children, tags)}</td>,
p: ({ children }) => children && <p>{transformText(children, tags)}</p>,
a: (props) => {
return <HyperText link={props.href}>{props.children}</HyperText>;
},
};
}, [tags]);
return (
<div className="markdown">
<ReactMarkdown children={children} components={components} />
</div>
);
}

22
src/element/note.css Normal file
View File

@ -0,0 +1,22 @@
.note {
padding: 12px;
border: 1px solid var(--border);
border-radius: 10px;
}
.note .note-header .profile {
font-size: 14px;
}
.note .note-avatar {
width: 18px;
height: 18px;
}
.note .note-content {
margin-left: 30px;
}
.note .note-content .markdown > p {
font-size: 14px;
}

18
src/element/note.tsx Normal file
View File

@ -0,0 +1,18 @@
import "./note.css";
import { type NostrEvent } from "@snort/system";
import { Markdown } from "element/markdown";
import { Profile } from "element/profile";
export function Note({ ev }: { ev: NostrEvent }) {
return (
<div className="note">
<div className="note-header">
<Profile avatarClassname="note-avatar" pubkey={ev.pubkey} />
</div>
<div className="note-content">
<Markdown tags={ev.tags}>{ev.content}</Markdown>
</div>
</div>
);
}

View File

@ -0,0 +1,128 @@
.stream-cards {
display: none;
}
@media (min-width: 1020px) {
.stream-cards {
display: flex;
align-items: flex-start;
gap: 24px;
margin-top: 12px;
flex-wrap: wrap;
}
}
.card-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.editor-buttons {
display: flex;
flex-direction: column;
gap: 12px;
}
.stream-card {
display: flex;
align-self: flex-start;
flex-direction: column;
padding: 20px 24px;
gap: 16px;
border-radius: 24px;
background: #111;
width: 210px;
}
.stream-card .card-title {
margin: 0;
font-size: 22px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
@media (min-width: 1900px) {
.stream-card {
width: 342px;
}
}
.add-card {
align-items: center;
justify-content: center;
}
.add-card .add-icon {
color: #797979;
cursor: pointer;
width: 24px;
height: 24px;
}
.new-card {
display: flex;
flex-direction: column;
gap: 12px;
}
.new-card h3 {
margin: 0;
margin-bottom: 12px;
}
.new-card input[type="text"] {
background: #262626;
padding: 8px 16px;
border-radius: 16px;
width: unset;
margin-bottom: 8px;
font-size: 16px;
font-weight: 500;
line-height: 20px;
}
.new-card textarea {
width: unset;
background: #262626;
padding: 8px 16px;
border-radius: 16px;
margin-bottom: 8px;
}
.form-control {
display: flex;
flex-direction: column;
}
.form-control label {
margin-bottom: 8px;
}
.new-card-buttons {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
}
.help-text {
color: var(--text-muted);
font-size: 14px;
margin-left: 6px;
}
.help-text a {
color: var(--text-link);
}
.add-button {
height: 50px;
}
.delete-button {
background: transparent;
color: var(--text-danger);
}

View File

@ -0,0 +1,305 @@
import "./stream-cards.css";
import { useState } from "react";
import * as Dialog from "@radix-ui/react-dialog";
import type { NostrEvent } from "@snort/system";
import { useLogin } from "hooks/login";
import { useCards } from "hooks/cards";
import { CARD, USER_CARDS } from "const";
import { toTag } from "utils";
import { System } from "index";
import { findTag } from "utils";
import { Icon } from "./icon";
import { ExternalLink } from "./external-link";
import { FileUploader } from "./file-uploader";
import { Markdown } from "./markdown";
interface CardType {
identifier?: string;
title?: string;
image?: string;
link?: string;
content: string;
}
interface CardProps {
canEdit?: boolean;
ev: NostrEvent;
cards: NostrEvent[];
}
function Card({ canEdit, ev, cards }: CardProps) {
const identifier = findTag(ev, "d");
const title = findTag(ev, "title") || findTag(ev, "subject");
const image = findTag(ev, "image");
const link = findTag(ev, "r");
const evCard = { title, image, link, content: ev.content, identifier };
const card = (
<>
<div className="stream-card">
{title && <h1 className="card-title">{title}</h1>}
{image && <img src={image} alt={title} />}
<Markdown children={ev.content} />
</div>
</>
);
const editor = canEdit && (
<div className="editor-buttons">
<EditCard card={evCard} />
<DeleteCard card={ev} cards={cards} />
</div>
);
return link && !canEdit ? (
<div className="card-container">
<ExternalLink href={link}>{card}</ExternalLink>
{editor}
</div>
) : (
<div className="card-container">
{card}
{editor}
</div>
);
}
interface CardDialogProps {
header?: string;
cta?: string;
card?: CardType;
onSave(ev: CardType): void;
onCancel(): void;
}
function CardDialog({ header, cta, card, onSave, onCancel }: CardDialogProps) {
const [title, setTitle] = useState(card?.title ?? "");
const [image, setImage] = useState(card?.image ?? "");
const [content, setContent] = useState(card?.content ?? "");
const [link, setLink] = useState(card?.link ?? "");
return (
<div className="new-card">
<h3>{header || "Add card"}</h3>
<div className="form-control">
<label for="card-title">Title</label>
<input
id="card-title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="e.g. about me"
/>
</div>
<div className="form-control">
<label for="card-image">Image</label>
<FileUploader onFileUpload={setImage} />
</div>
<div className="form-control">
<label for="card-image-link">Link</label>
<input
id="card-image-link"
type="text"
placeholder="https://"
value={link}
onChange={(e) => setLink(e.target.value)}
/>
</div>
<div className="form-control">
<label for="card-content">Content</label>
<textarea
placeholder="Start typing..."
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<span className="help-text">
Supports{" "}
<ExternalLink href="https://www.markdownguide.org/cheat-sheet">
Markdown
</ExternalLink>
</span>
</div>
<div className="new-card-buttons">
<button
className="btn btn-primary add-button"
onClick={() => onSave({ title, image, content, link })}
>
{cta || "Add Card"}
</button>
<button className="btn delete-button" onClick={onCancel}>
Cancel
</button>
</div>
</div>
);
}
interface EditCardProps {
card: CardType;
}
function EditCard({ card }: EditCardProps) {
const login = useLogin();
const [isOpen, setIsOpen] = useState(false);
async function editCard({ title, image, link, content }) {
const pub = login?.publisher();
if (pub) {
const ev = await pub.generic((eb) => {
eb.kind(CARD).content(content).tag(["d", card.identifier]);
if (title?.length > 0) {
eb.tag(["title", title]);
}
if (image?.length > 0) {
eb.tag(["image", image]);
}
if (link?.lenght > 0) {
eb.tag(["r", link]);
}
return eb;
});
console.debug(ev);
System.BroadcastEvent(ev);
setIsOpen(false);
}
}
function onCancel() {
setIsOpen(false);
}
return (
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Trigger asChild>
<button className="btn btn-primary">Edit</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="dialog-overlay" />
<Dialog.Content className="dialog-content">
<CardDialog
header="Edit card"
cta="Save Card"
card={card}
onSave={editCard}
onCancel={onCancel}
/>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
interface DeleteCardProps {
card: NostrEvent;
cards: NostrEvent[];
}
function DeleteCard({ card, cards }: DeleteCardProps) {
const login = useLogin();
const tags = cards.map(toTag);
async function deleteCard() {
const pub = login?.publisher();
if (pub) {
const userCardsEv = await pub.generic((eb) => {
eb.kind(USER_CARDS).content("");
for (const tag of tags) {
if (tag.at(1) !== toTag(card).at(1)) {
eb.tag(tag);
}
}
return eb;
});
console.log(userCardsEv);
System.BroadcastEvent(userCardsEv);
}
}
return (
<button className="btn delete-button" onClick={deleteCard}>
Delete
</button>
);
}
interface AddCardProps {
cards: NostrEvent[];
}
function AddCard({ cards }: AddCardProps) {
const login = useLogin();
const tags = cards.map(toTag);
const [isOpen, setIsOpen] = useState(false);
async function createCard({ title, image, link, content }) {
const pub = login?.publisher();
if (pub) {
const ev = await pub.generic((eb) => {
const d = String(Date.now());
eb.kind(CARD).content(content).tag(["d", d]);
if (title?.length > 0) {
eb.tag(["title", title]);
}
if (image?.length > 0) {
eb.tag(["image", image]);
}
if (link?.length > 0) {
eb.tag(["r", link]);
}
return eb;
});
const userCardsEv = await pub.generic((eb) => {
eb.kind(USER_CARDS).content("");
for (const tag of tags) {
eb.tag(tag);
}
eb.tag(toTag(ev));
return eb;
});
console.debug(ev);
console.debug(userCardsEv);
System.BroadcastEvent(ev);
System.BroadcastEvent(userCardsEv);
setIsOpen(false);
}
}
function onCancel() {
setIsOpen(false);
}
return (
<div className="stream-card add-card">
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Trigger asChild>
<Icon name="plus" className="add-icon" />
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="dialog-overlay" />
<Dialog.Content className="dialog-content">
<CardDialog onSave={createCard} onCancel={onCancel} />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</div>
);
}
export function StreamCards({ host }) {
const login = useLogin();
const canEdit = login?.pubkey === host;
const cards = useCards(host, canEdit);
return (
<div className="stream-cards">
{cards.map((ev) => (
<Card canEdit={canEdit} cards={cards} key={ev.id} ev={ev} />
))}
{canEdit && <AddCard cards={cards} />}
</div>
);
}

86
src/hooks/cards.ts Normal file
View File

@ -0,0 +1,86 @@
import { useMemo } from "react";
import {
ReplaceableNoteStore,
NoteCollection,
RequestBuilder,
} from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { USER_CARDS, CARD } from "const";
import { findTag } from "utils";
import { System } from "index";
export function useCards(pubkey: string, leaveOpen = false) {
const sub = useMemo(() => {
const b = new RequestBuilder(`user-cards:${pubkey.slice(0, 12)}`);
b.withOptions({
leaveOpen,
})
.withFilter()
.authors([pubkey])
.kinds([USER_CARDS]);
return b;
}, [pubkey, leaveOpen]);
const { data: userCards } = useRequestBuilder<ReplaceableNoteStore>(
System,
ReplaceableNoteStore,
sub,
);
const related = useMemo(() => {
// filtering to only show CARD kinds for now, but in the future we could link and render anything
if (userCards) {
return userCards.tags.filter(
(t) => t.at(0) === "a" && t.at(1)?.startsWith(`${CARD}:`),
);
}
return [];
}, [userCards]);
const subRelated = useMemo(() => {
if (!pubkey) return null;
const splitted = related.map((t) => t.at(1)!.split(":"));
const authors = splitted
.map((s) => s.at(1))
.filter((s) => s)
.map((s) => s as string);
const identifiers = splitted
.map((s) => s.at(2))
.filter((s) => s)
.map((s) => s as string);
const rb = new RequestBuilder(`cards:${pubkey}`);
rb.withOptions({ leaveOpen })
.withFilter()
.kinds([CARD])
.authors(authors)
.tag("d", identifiers);
return rb;
}, [pubkey, related]);
const { data } = useRequestBuilder<NoteCollection>(
System,
NoteCollection,
subRelated,
);
const cards = useMemo(() => {
return related
.map((t) => {
const [k, pubkey, identifier] = t.at(1).split(":");
const kind = Number(k);
return data.find(
(e) =>
e.kind === kind &&
e.pubkey === pubkey &&
findTag(e, "d") === identifier,
);
})
.filter((e) => e);
}, [related, data]);
return cards;
}

View File

@ -1,6 +1,5 @@
import {
RequestBuilder,
EventKind,
ReplaceableNoteStore,
NoteCollection,
NostrEvent,
@ -9,6 +8,7 @@ import { useRequestBuilder } from "@snort/system-react";
import { System } from "index";
import { useMemo } from "react";
import { findTag } from "utils";
import { EMOJI_PACK, USER_EMOJIS } from "const";
import type { EmojiTag } from "../element/emoji";
import uniqBy from "lodash.uniqby";
@ -49,9 +49,7 @@ export default function useEmoji(pubkey?: string) {
if (!pubkey) return null;
const rb = new RequestBuilder(`emoji:${pubkey}`);
rb.withFilter()
.authors([pubkey])
.kinds([10030 as EventKind]);
rb.withFilter().authors([pubkey]).kinds([USER_EMOJIS]);
return rb;
}, [pubkey]);
@ -59,13 +57,13 @@ export default function useEmoji(pubkey?: string) {
const { data: userEmoji } = useRequestBuilder<ReplaceableNoteStore>(
System,
ReplaceableNoteStore,
sub
sub,
);
const related = useMemo(() => {
if (userEmoji) {
return userEmoji.tags.filter(
(t) => t.at(0) === "a" && t.at(1)?.startsWith(`30030:`)
(t) => t.at(0) === "a" && t.at(1)?.startsWith(`${EMOJI_PACK}:`),
);
}
return [];
@ -85,14 +83,9 @@ export default function useEmoji(pubkey?: string) {
const rb = new RequestBuilder(`emoji-related:${pubkey}`);
rb.withFilter()
.kinds([30030 as EventKind])
.authors(authors)
.tag("d", identifiers);
rb.withFilter().kinds([EMOJI_PACK]).authors(authors).tag("d", identifiers);
rb.withFilter()
.kinds([30030 as EventKind])
.authors([pubkey]);
rb.withFilter().kinds([EMOJI_PACK]).authors([pubkey]);
return rb;
}, [pubkey, related]);
@ -100,7 +93,7 @@ export default function useEmoji(pubkey?: string) {
const { data: relatedData } = useRequestBuilder<NoteCollection>(
System,
NoteCollection,
subRelated
subRelated,
);
const emojiPacks = useMemo(() => {

43
src/hooks/event.ts Normal file
View File

@ -0,0 +1,43 @@
import { useMemo } from "react";
import {
NostrPrefix,
ReplaceableNoteStore,
RequestBuilder,
type NostrLink,
} from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { System } from "index";
export function useEvent(link: NostrLink) {
const sub = useMemo(() => {
const b = new RequestBuilder(`event:${link.id.slice(0, 12)}`);
if (link.type === NostrPrefix.Address) {
const f = b.withFilter().tag("d", [link.id]);
if (link.author) {
f.authors([link.author]);
}
if (link.kind) {
f.kinds([link.kind]);
}
} else {
const f = b.withFilter().ids([link.id]);
if (link.relays) {
link.relays.slice(0, 2).forEach((r) => f.relay(r));
}
if (link.author) {
f.authors([link.author]);
}
}
return b;
}, [link]);
const { data } = useRequestBuilder<ReplaceableNoteStore>(
System,
ReplaceableNoteStore,
sub,
);
return data;
}

View File

@ -1,13 +1,37 @@
import { useMemo } from "react";
import {
EventKind,
NostrEvent,
RequestBuilder,
NoteCollection,
ReplaceableNoteStore,
NostrLink,
parseZap,
} from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { GOAL } from "const";
import { System } from "index";
export function useZaps(goal: NostrEvent, leaveOpen = false) {
const sub = useMemo(() => {
const b = new RequestBuilder(`goal-zaps:${goal.id.slice(0, 12)}`);
b.withOptions({ leaveOpen });
b.withFilter()
.kinds([EventKind.ZapReceipt])
.tag("e", [goal.id])
.since(goal.created_at);
return b;
}, [goal, leaveOpen]);
const { data } = useRequestBuilder<NoteCollection>(
System,
NoteCollection,
sub,
);
return data?.map((ev) => parseZap(ev, System.ProfileLoader.Cache)).filter((z) => z && z.valid) ?? [];
}
export function useZapGoal(host: string, link: NostrLink, leaveOpen = false) {
const sub = useMemo(() => {
const b = new RequestBuilder(`goals:${host.slice(0, 12)}`);
@ -22,7 +46,7 @@ export function useZapGoal(host: string, link: NostrLink, leaveOpen = false) {
const { data } = useRequestBuilder<ReplaceableNoteStore>(
System,
ReplaceableNoteStore,
sub
sub,
);
return data;

View File

@ -12,6 +12,10 @@ body {
--gap-m: 24px;
--gap-s: 16px;
--header-height: 48px;
--text-muted: #797979;
--text-link: #F838D9;
--text-danger: #FF563F;
--border: #333;
}
@media(max-width: 1020px) {
@ -267,3 +271,9 @@ div.paper {
.szh-menu__item--hover {
background-color: unset;
}
.emoji {
width: 15px;
height: 15px;
margin-bottom: -2px;
}

View File

@ -62,7 +62,7 @@ export function ProfilePage() {
}, [streams]);
const futureStreams = useMemo(() => {
return streams.filter(
(ev) => findTag(ev, "status") === StreamState.Planned
(ev) => findTag(ev, "status") === StreamState.Planned,
);
}, [streams]);
const isLive = Boolean(liveEvent);
@ -75,7 +75,7 @@ export function ProfilePage() {
d,
undefined,
liveEvent.kind,
liveEvent.pubkey
liveEvent.pubkey,
);
navigate(`/${naddr}`);
}
@ -114,7 +114,7 @@ export function ProfilePage() {
liveEvent
? `${liveEvent.kind}:${liveEvent.pubkey}:${findTag(
liveEvent,
"d"
"d",
)}`
: undefined
}
@ -171,7 +171,7 @@ export function ProfilePage() {
<span className="timestamp">
Streamed on{" "}
{moment(Number(ev.created_at) * 1000).format(
"MMM DD, YYYY"
"MMM DD, YYYY",
)}
</span>
</div>
@ -186,7 +186,7 @@ export function ProfilePage() {
<span className="timestamp">
Scheduled for{" "}
{moment(Number(ev.created_at) * 1000).format(
"MMM DD, YYYY h:mm:ss a"
"MMM DD, YYYY h:mm:ss a",
)}
</span>
</div>

View File

@ -18,6 +18,7 @@ import { useUserProfile } from "@snort/system-react";
import { NewStreamDialog } from "element/new-stream";
import { Tags } from "element/tags";
import { StatePill } from "element/state-pill";
import { StreamCards } from "element/stream-cards";
import { formatSats } from "number";
import { StreamTimer } from "element/stream-time";
import { ShareMenu } from "element/share-menu";
@ -135,6 +136,7 @@ export function StreamPage() {
<div className="video-content">
<LiveVideoPlayer stream={stream} poster={image} status={status} />
<ProfileInfo ev={ev} goal={goal} />
<StreamCards host={host} />
</div>
<LiveChat link={link} ev={ev} goal={goal} />
</div>

View File

@ -2,6 +2,20 @@ import { NostrEvent, NostrPrefix, encodeTLV } from "@snort/system";
import * as utils from "@noble/curves/abstract/utils";
import { bech32 } from "@scure/base";
export function toTag(e: NostrEvent): string[] {
if (e.kind && e.kind >= 30000 && e.kind <= 40000) {
const dTag = findTag(e, "d");
return ["a", `${e.kind}:${e.pubkey}:${dTag}`];
}
if (e.kind === 0 || e.kind === 3) {
return ["p", e.pubkey];
}
return ["e", e.id];
}
export function findTag(e: NostrEvent | undefined, tag: string) {
const maybeTag = e?.tags.find((evTag) => {
return evTag[0] === tag;
@ -48,17 +62,21 @@ export function eventLink(ev: NostrEvent) {
d,
undefined,
ev.kind,
ev.pubkey
ev.pubkey,
);
return `/${naddr}`;
}
export function getHost(ev?: NostrEvent) {
return ev?.tags.find(a => a[0] === "p" && a[3] === "host")?.[1] ?? ev?.pubkey ?? "";
return (
ev?.tags.find((a) => a[0] === "p" && a[3] === "host")?.[1] ??
ev?.pubkey ??
""
);
}
export async function openFile(): Promise<File | undefined> {
return new Promise(resolve => {
return new Promise((resolve) => {
const elm = document.createElement("input");
elm.type = "file";
elm.onchange = (e: Event) => {
@ -71,4 +89,4 @@ export async function openFile(): Promise<File | undefined> {
};
elm.click();
});
}
}

629
yarn.lock
View File

@ -2775,6 +2775,15 @@ __metadata:
languageName: node
linkType: hard
"@types/debug@npm:^4.0.0":
version: 4.1.8
resolution: "@types/debug@npm:4.1.8"
dependencies:
"@types/ms": "*"
checksum: a9a9bb40a199e9724aa944e139a7659173a9b274798ea7efbc277cb084bc37d32fc4c00877c3496fac4fed70a23243d284adb75c00b5fdabb38a22154d18e5df
languageName: node
linkType: hard
"@types/eslint-scope@npm:^3.7.3":
version: 3.7.4
resolution: "@types/eslint-scope@npm:3.7.4"
@ -2833,6 +2842,15 @@ __metadata:
languageName: node
linkType: hard
"@types/hast@npm:^2.0.0":
version: 2.3.5
resolution: "@types/hast@npm:2.3.5"
dependencies:
"@types/unist": ^2
checksum: e435e9fbf6afc616ade377d2246a632fb75f4064be4bfd619b67a1ba0d9935d75968a18fbdb66535dfb5e77ef81f4b9b56fd8f35c1cffa34b48ddb0287fec91e
languageName: node
linkType: hard
"@types/html-minifier-terser@npm:^6.0.0":
version: 6.1.0
resolution: "@types/html-minifier-terser@npm:6.1.0"
@ -2914,6 +2932,15 @@ __metadata:
languageName: node
linkType: hard
"@types/mdast@npm:^3.0.0":
version: 3.0.12
resolution: "@types/mdast@npm:3.0.12"
dependencies:
"@types/unist": ^2
checksum: 83adb8679b9d139f69f63554d120af921e9f1289e9903a2c99e0554a327c8524a6c0beccdc0721e4fdbccc606e81964fecb0d390d53df0f74360938e22f1a469
languageName: node
linkType: hard
"@types/mime@npm:*":
version: 3.0.1
resolution: "@types/mime@npm:3.0.1"
@ -2928,6 +2955,13 @@ __metadata:
languageName: node
linkType: hard
"@types/ms@npm:*":
version: 0.7.31
resolution: "@types/ms@npm:0.7.31"
checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da
languageName: node
linkType: hard
"@types/node@npm:*":
version: 20.3.1
resolution: "@types/node@npm:20.3.1"
@ -2949,7 +2983,7 @@ __metadata:
languageName: node
linkType: hard
"@types/prop-types@npm:*":
"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0":
version: 15.7.5
resolution: "@types/prop-types@npm:15.7.5"
checksum: 5b43b8b15415e1f298243165f1d44390403bb2bd42e662bca3b5b5633fdd39c938e91b7fce3a9483699db0f7a715d08cef220c121f723a634972fdf596aec980
@ -3110,6 +3144,13 @@ __metadata:
languageName: node
linkType: hard
"@types/unist@npm:^2, @types/unist@npm:^2.0.0":
version: 2.0.7
resolution: "@types/unist@npm:2.0.7"
checksum: b97a219554e83431f19a93ff113306bf0512909292815e8f32964e47d041c505af1aaa2a381c23e137c4c0b962fad58d4ce9c5c3256642921a466be43c1fc715
languageName: node
linkType: hard
"@types/webscopeio__react-textarea-autocomplete@npm:^4.7.2":
version: 4.7.2
resolution: "@types/webscopeio__react-textarea-autocomplete@npm:4.7.2"
@ -3912,6 +3953,13 @@ __metadata:
languageName: node
linkType: hard
"bail@npm:^2.0.0":
version: 2.0.2
resolution: "bail@npm:2.0.2"
checksum: aab4e8ccdc8d762bf3fdfce8e706601695620c0c2eda256dd85088dc0be3cfd7ff126f6e99c2bee1f24f5d418414aacf09d7f9702f16d6963df2fa488cda8824
languageName: node
linkType: hard
"balanced-match@npm:^1.0.0":
version: 1.0.2
resolution: "balanced-match@npm:1.0.2"
@ -4156,6 +4204,13 @@ __metadata:
languageName: node
linkType: hard
"character-entities@npm:^2.0.0":
version: 2.0.2
resolution: "character-entities@npm:2.0.2"
checksum: cf1643814023697f725e47328fcec17923b8f1799102a8a79c1514e894815651794a2bffd84bb1b3a4b124b050154e4529ed6e81f7c8068a734aecf07a6d3def
languageName: node
linkType: hard
"chokidar@npm:^3.5.3":
version: 3.5.3
resolution: "chokidar@npm:3.5.3"
@ -4278,6 +4333,13 @@ __metadata:
languageName: node
linkType: hard
"comma-separated-tokens@npm:^2.0.0":
version: 2.0.3
resolution: "comma-separated-tokens@npm:2.0.3"
checksum: e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d
languageName: node
linkType: hard
"commander@npm:^10.0.1":
version: 10.0.1
resolution: "commander@npm:10.0.1"
@ -4669,7 +4731,7 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4":
"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4":
version: 4.3.4
resolution: "debug@npm:4.3.4"
dependencies:
@ -4681,6 +4743,15 @@ __metadata:
languageName: node
linkType: hard
"decode-named-character-reference@npm:^1.0.0":
version: 1.0.2
resolution: "decode-named-character-reference@npm:1.0.2"
dependencies:
character-entities: ^2.0.0
checksum: f4c71d3b93105f20076052f9cb1523a22a9c796b8296cd35eef1ca54239c78d182c136a848b83ff8da2071e3ae2b1d300bf29d00650a6d6e675438cc31b11d78
languageName: node
linkType: hard
"deep-equal@npm:^2.0.5":
version: 2.2.1
resolution: "deep-equal@npm:2.2.1"
@ -4768,7 +4839,7 @@ __metadata:
languageName: node
linkType: hard
"dequal@npm:^2.0.3":
"dequal@npm:^2.0.0, dequal@npm:^2.0.3":
version: 2.0.3
resolution: "dequal@npm:2.0.3"
checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90
@ -4810,6 +4881,13 @@ __metadata:
languageName: node
linkType: hard
"diff@npm:^5.0.0":
version: 5.1.0
resolution: "diff@npm:5.1.0"
checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90
languageName: node
linkType: hard
"dir-glob@npm:^3.0.1":
version: 3.0.1
resolution: "dir-glob@npm:3.0.1"
@ -5464,6 +5542,13 @@ __metadata:
languageName: node
linkType: hard
"extend@npm:^3.0.0":
version: 3.0.2
resolution: "extend@npm:3.0.2"
checksum: a50a8309ca65ea5d426382ff09f33586527882cf532931cb08ca786ea3146c0553310bda688710ff61d7668eba9f96b923fe1420cdf56a2c3eaf30fcab87b515
languageName: node
linkType: hard
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
version: 3.1.3
resolution: "fast-deep-equal@npm:3.1.3"
@ -6025,6 +6110,13 @@ __metadata:
languageName: node
linkType: hard
"hast-util-whitespace@npm:^2.0.0":
version: 2.0.1
resolution: "hast-util-whitespace@npm:2.0.1"
checksum: 431be6b2f35472f951615540d7a53f69f39461e5e080c0190268bdeb2be9ab9b1dddfd1f467dd26c1de7e7952df67beb1307b6ee940baf78b24a71b5e0663868
languageName: node
linkType: hard
"he@npm:^1.2.0":
version: 1.2.0
resolution: "he@npm:1.2.0"
@ -6324,6 +6416,13 @@ __metadata:
languageName: node
linkType: hard
"inline-style-parser@npm:0.1.1":
version: 0.1.1
resolution: "inline-style-parser@npm:0.1.1"
checksum: 5d545056a3e1f2bf864c928a886a0e1656a3517127d36917b973de581bd54adc91b4bf1febcb0da054f204b4934763f1a4e09308b4d55002327cf1d48ac5d966
languageName: node
linkType: hard
"internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.5":
version: 1.0.5
resolution: "internal-slot@npm:1.0.5"
@ -6421,6 +6520,13 @@ __metadata:
languageName: node
linkType: hard
"is-buffer@npm:^2.0.0":
version: 2.0.5
resolution: "is-buffer@npm:2.0.5"
checksum: 764c9ad8b523a9f5a32af29bdf772b08eb48c04d2ad0a7240916ac2688c983bf5f8504bf25b35e66240edeb9d9085461f9b5dae1f3d2861c6b06a65fe983de42
languageName: node
linkType: hard
"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7":
version: 1.2.7
resolution: "is-callable@npm:1.2.7"
@ -6543,6 +6649,13 @@ __metadata:
languageName: node
linkType: hard
"is-plain-obj@npm:^4.0.0":
version: 4.1.0
resolution: "is-plain-obj@npm:4.1.0"
checksum: 6dc45da70d04a81f35c9310971e78a6a3c7a63547ef782e3a07ee3674695081b6ca4e977fbb8efc48dae3375e0b34558d2bcd722aec9bddfa2d7db5b041be8ce
languageName: node
linkType: hard
"is-plain-object@npm:^2.0.4":
version: 2.0.4
resolution: "is-plain-object@npm:2.0.4"
@ -6932,6 +7045,13 @@ __metadata:
languageName: node
linkType: hard
"kleur@npm:^4.0.3":
version: 4.1.5
resolution: "kleur@npm:4.1.5"
checksum: 1dc476e32741acf0b1b5b0627ffd0d722e342c1b0da14de3e8ae97821327ca08f9fb944542fb3c126d90ac5f27f9d804edbe7c585bf7d12ef495d115e0f22c12
languageName: node
linkType: hard
"launch-editor@npm:^2.6.0":
version: 2.6.0
resolution: "launch-editor@npm:2.6.0"
@ -7160,6 +7280,62 @@ __metadata:
languageName: node
linkType: hard
"mdast-util-definitions@npm:^5.0.0":
version: 5.1.2
resolution: "mdast-util-definitions@npm:5.1.2"
dependencies:
"@types/mdast": ^3.0.0
"@types/unist": ^2.0.0
unist-util-visit: ^4.0.0
checksum: 2544daccab744ea1ede76045c2577ae4f1cc1b9eb1ea51ab273fe1dca8db5a8d6f50f87759c0ce6484975914b144b7f40316f805cb9c86223a78db8de0b77bae
languageName: node
linkType: hard
"mdast-util-from-markdown@npm:^1.0.0":
version: 1.3.1
resolution: "mdast-util-from-markdown@npm:1.3.1"
dependencies:
"@types/mdast": ^3.0.0
"@types/unist": ^2.0.0
decode-named-character-reference: ^1.0.0
mdast-util-to-string: ^3.1.0
micromark: ^3.0.0
micromark-util-decode-numeric-character-reference: ^1.0.0
micromark-util-decode-string: ^1.0.0
micromark-util-normalize-identifier: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
unist-util-stringify-position: ^3.0.0
uvu: ^0.5.0
checksum: c2fac225167e248d394332a4ea39596e04cbde07d8cdb3889e91e48972c4c3462a02b39fda3855345d90231eb17a90ac6e082fb4f012a77c1d0ddfb9c7446940
languageName: node
linkType: hard
"mdast-util-to-hast@npm:^12.1.0":
version: 12.3.0
resolution: "mdast-util-to-hast@npm:12.3.0"
dependencies:
"@types/hast": ^2.0.0
"@types/mdast": ^3.0.0
mdast-util-definitions: ^5.0.0
micromark-util-sanitize-uri: ^1.1.0
trim-lines: ^3.0.0
unist-util-generated: ^2.0.0
unist-util-position: ^4.0.0
unist-util-visit: ^4.0.0
checksum: ea40c9f07dd0b731754434e81c913590c611b1fd753fa02550a1492aadfc30fb3adecaf62345ebb03cea2ddd250c15ab6e578fffde69c19955c9b87b10f2a9bb
languageName: node
linkType: hard
"mdast-util-to-string@npm:^3.1.0":
version: 3.2.0
resolution: "mdast-util-to-string@npm:3.2.0"
dependencies:
"@types/mdast": ^3.0.0
checksum: dc40b544d54339878ae2c9f2b3198c029e1e07291d2126bd00ca28272ee6616d0d2194eb1c9828a7c34d412a79a7e73b26512a734698d891c710a1e73db1e848
languageName: node
linkType: hard
"mdn-data@npm:2.0.28":
version: 2.0.28
resolution: "mdn-data@npm:2.0.28"
@ -7218,6 +7394,242 @@ __metadata:
languageName: node
linkType: hard
"micromark-core-commonmark@npm:^1.0.1":
version: 1.1.0
resolution: "micromark-core-commonmark@npm:1.1.0"
dependencies:
decode-named-character-reference: ^1.0.0
micromark-factory-destination: ^1.0.0
micromark-factory-label: ^1.0.0
micromark-factory-space: ^1.0.0
micromark-factory-title: ^1.0.0
micromark-factory-whitespace: ^1.0.0
micromark-util-character: ^1.0.0
micromark-util-chunked: ^1.0.0
micromark-util-classify-character: ^1.0.0
micromark-util-html-tag-name: ^1.0.0
micromark-util-normalize-identifier: ^1.0.0
micromark-util-resolve-all: ^1.0.0
micromark-util-subtokenize: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.1
uvu: ^0.5.0
checksum: c6dfedc95889cc73411cb222fc2330b9eda6d849c09c9fd9eb3cd3398af246167e9d3cdb0ae3ce9ae59dd34a14624c8330e380255d41279ad7350cf6c6be6c5b
languageName: node
linkType: hard
"micromark-factory-destination@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-factory-destination@npm:1.1.0"
dependencies:
micromark-util-character: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
checksum: 9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa
languageName: node
linkType: hard
"micromark-factory-label@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-factory-label@npm:1.1.0"
dependencies:
micromark-util-character: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
uvu: ^0.5.0
checksum: fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5
languageName: node
linkType: hard
"micromark-factory-space@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-factory-space@npm:1.1.0"
dependencies:
micromark-util-character: ^1.0.0
micromark-util-types: ^1.0.0
checksum: b58435076b998a7e244259a4694eb83c78915581206b6e7fc07b34c6abd36a1726ade63df8972fbf6c8fa38eecb9074f4e17be8d53f942e3b3d23d1a0ecaa941
languageName: node
linkType: hard
"micromark-factory-title@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-factory-title@npm:1.1.0"
dependencies:
micromark-factory-space: ^1.0.0
micromark-util-character: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
checksum: 4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1
languageName: node
linkType: hard
"micromark-factory-whitespace@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-factory-whitespace@npm:1.1.0"
dependencies:
micromark-factory-space: ^1.0.0
micromark-util-character: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
checksum: ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd
languageName: node
linkType: hard
"micromark-util-character@npm:^1.0.0":
version: 1.2.0
resolution: "micromark-util-character@npm:1.2.0"
dependencies:
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
checksum: 089e79162a19b4a28731736246579ab7e9482ac93cd681c2bfca9983dcff659212ef158a66a5957e9d4b1dba957d1b87b565d85418a5b009f0294f1f07f2aaac
languageName: node
linkType: hard
"micromark-util-chunked@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-chunked@npm:1.1.0"
dependencies:
micromark-util-symbol: ^1.0.0
checksum: c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b
languageName: node
linkType: hard
"micromark-util-classify-character@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-classify-character@npm:1.1.0"
dependencies:
micromark-util-character: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
checksum: 8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0
languageName: node
linkType: hard
"micromark-util-combine-extensions@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-combine-extensions@npm:1.1.0"
dependencies:
micromark-util-chunked: ^1.0.0
micromark-util-types: ^1.0.0
checksum: ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9
languageName: node
linkType: hard
"micromark-util-decode-numeric-character-reference@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0"
dependencies:
micromark-util-symbol: ^1.0.0
checksum: 4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73
languageName: node
linkType: hard
"micromark-util-decode-string@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-decode-string@npm:1.1.0"
dependencies:
decode-named-character-reference: ^1.0.0
micromark-util-character: ^1.0.0
micromark-util-decode-numeric-character-reference: ^1.0.0
micromark-util-symbol: ^1.0.0
checksum: f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813
languageName: node
linkType: hard
"micromark-util-encode@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-encode@npm:1.1.0"
checksum: 4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0
languageName: node
linkType: hard
"micromark-util-html-tag-name@npm:^1.0.0":
version: 1.2.0
resolution: "micromark-util-html-tag-name@npm:1.2.0"
checksum: ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750
languageName: node
linkType: hard
"micromark-util-normalize-identifier@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-normalize-identifier@npm:1.1.0"
dependencies:
micromark-util-symbol: ^1.0.0
checksum: 8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2
languageName: node
linkType: hard
"micromark-util-resolve-all@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-resolve-all@npm:1.1.0"
dependencies:
micromark-util-types: ^1.0.0
checksum: 1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c
languageName: node
linkType: hard
"micromark-util-sanitize-uri@npm:^1.0.0, micromark-util-sanitize-uri@npm:^1.1.0":
version: 1.2.0
resolution: "micromark-util-sanitize-uri@npm:1.2.0"
dependencies:
micromark-util-character: ^1.0.0
micromark-util-encode: ^1.0.0
micromark-util-symbol: ^1.0.0
checksum: 6663f365c4fe3961d622a580f4a61e34867450697f6806f027f21cf63c92989494895fcebe2345d52e249fe58a35be56e223a9776d084c9287818b40c779acc1
languageName: node
linkType: hard
"micromark-util-subtokenize@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-subtokenize@npm:1.1.0"
dependencies:
micromark-util-chunked: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.0
uvu: ^0.5.0
checksum: 4a9d780c4d62910e196ea4fd886dc4079d8e424e5d625c0820016da0ed399a281daff39c50f9288045cc4bcd90ab47647e5396aba500f0853105d70dc8b1fc45
languageName: node
linkType: hard
"micromark-util-symbol@npm:^1.0.0":
version: 1.1.0
resolution: "micromark-util-symbol@npm:1.1.0"
checksum: 02414a753b79f67ff3276b517eeac87913aea6c028f3e668a19ea0fc09d98aea9f93d6222a76ca783d20299af9e4b8e7c797fe516b766185dcc6e93290f11f88
languageName: node
linkType: hard
"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1":
version: 1.1.0
resolution: "micromark-util-types@npm:1.1.0"
checksum: b0ef2b4b9589f15aec2666690477a6a185536927ceb7aa55a0f46475852e012d75a1ab945187e5c7841969a842892164b15d58ff8316b8e0d6cc920cabd5ede7
languageName: node
linkType: hard
"micromark@npm:^3.0.0":
version: 3.2.0
resolution: "micromark@npm:3.2.0"
dependencies:
"@types/debug": ^4.0.0
debug: ^4.0.0
decode-named-character-reference: ^1.0.0
micromark-core-commonmark: ^1.0.1
micromark-factory-space: ^1.0.0
micromark-util-character: ^1.0.0
micromark-util-chunked: ^1.0.0
micromark-util-combine-extensions: ^1.0.0
micromark-util-decode-numeric-character-reference: ^1.0.0
micromark-util-encode: ^1.0.0
micromark-util-normalize-identifier: ^1.0.0
micromark-util-resolve-all: ^1.0.0
micromark-util-sanitize-uri: ^1.0.0
micromark-util-subtokenize: ^1.0.0
micromark-util-symbol: ^1.0.0
micromark-util-types: ^1.0.1
uvu: ^0.5.0
checksum: 56c15851ad3eb8301aede65603473443e50c92a54849cac1dadd57e4ec33ab03a0a77f3df03de47133e6e8f695dae83b759b514586193269e98c0bf319ecd5e4
languageName: node
linkType: hard
"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5":
version: 4.0.5
resolution: "micromatch@npm:4.0.5"
@ -7412,6 +7824,13 @@ __metadata:
languageName: node
linkType: hard
"mri@npm:^1.1.0":
version: 1.2.0
resolution: "mri@npm:1.2.0"
checksum: 83f515abbcff60150873e424894a2f65d68037e5a7fcde8a9e2b285ee9c13ac581b63cfc1e6826c4732de3aeb84902f7c1e16b7aff46cd3f897a0f757a894e85
languageName: node
linkType: hard
"mrmime@npm:^1.0.0":
version: 1.0.1
resolution: "mrmime@npm:1.0.1"
@ -8321,7 +8740,7 @@ __metadata:
languageName: node
linkType: hard
"prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
"prop-types@npm:^15.0.0, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
@ -8332,6 +8751,13 @@ __metadata:
languageName: node
linkType: hard
"property-information@npm:^6.0.0":
version: 6.2.0
resolution: "property-information@npm:6.2.0"
checksum: 23afce07ba821cbe7d926e63cdd680991961c82be4bbb6c0b17c47f48894359c1be6e51cd74485fc10a9d3fd361b475388e1e39311ed2b53127718f72aab1955
languageName: node
linkType: hard
"proxy-addr@npm:~2.0.7":
version: 2.0.7
resolution: "proxy-addr@npm:2.0.7"
@ -8483,6 +8909,32 @@ __metadata:
languageName: node
linkType: hard
"react-markdown@npm:^8.0.7":
version: 8.0.7
resolution: "react-markdown@npm:8.0.7"
dependencies:
"@types/hast": ^2.0.0
"@types/prop-types": ^15.0.0
"@types/unist": ^2.0.0
comma-separated-tokens: ^2.0.0
hast-util-whitespace: ^2.0.0
prop-types: ^15.0.0
property-information: ^6.0.0
react-is: ^18.0.0
remark-parse: ^10.0.0
remark-rehype: ^10.0.0
space-separated-tokens: ^2.0.0
style-to-object: ^0.4.0
unified: ^10.0.0
unist-util-visit: ^4.0.0
vfile: ^5.0.0
peerDependencies:
"@types/react": ">=16"
react: ">=16"
checksum: 0f3e570975134a3382c3fe5189e04e742ae154941463bdfaab2293319da1f1585cb9b75b6f07d99f514c4d728d69cc1af3c96ab37df90003b3bcc210dd0001ba
languageName: node
linkType: hard
"react-remove-scroll-bar@npm:^2.3.3":
version: 2.3.4
resolution: "react-remove-scroll-bar@npm:2.3.4"
@ -8726,6 +9178,29 @@ __metadata:
languageName: node
linkType: hard
"remark-parse@npm:^10.0.0":
version: 10.0.2
resolution: "remark-parse@npm:10.0.2"
dependencies:
"@types/mdast": ^3.0.0
mdast-util-from-markdown: ^1.0.0
unified: ^10.0.0
checksum: 5041b4b44725f377e69986e02f8f072ae2222db5e7d3b6c80829756b842e811343ffc2069cae1f958a96bfa36104ab91a57d7d7e2f0cef521e210ab8c614d5c7
languageName: node
linkType: hard
"remark-rehype@npm:^10.0.0":
version: 10.1.0
resolution: "remark-rehype@npm:10.1.0"
dependencies:
"@types/hast": ^2.0.0
"@types/mdast": ^3.0.0
mdast-util-to-hast: ^12.1.0
unified: ^10.0.0
checksum: b9ac8acff3383b204dfdc2599d0bdf86e6ca7e837033209584af2e6aaa6a9013e519a379afa3201299798cab7298c8f4b388de118c312c67234c133318aec084
languageName: node
linkType: hard
"renderkid@npm:^3.0.0":
version: 3.0.0
resolution: "renderkid@npm:3.0.0"
@ -8871,6 +9346,15 @@ __metadata:
languageName: node
linkType: hard
"sade@npm:^1.7.3":
version: 1.8.1
resolution: "sade@npm:1.8.1"
dependencies:
mri: ^1.1.0
checksum: 0756e5b04c51ccdc8221ebffd1548d0ce5a783a44a0fa9017a026659b97d632913e78f7dca59f2496aa996a0be0b0c322afd87ca72ccd909406f49dbffa0f45d
languageName: node
linkType: hard
"safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1":
version: 5.1.2
resolution: "safe-buffer@npm:5.1.2"
@ -9304,6 +9788,13 @@ __metadata:
languageName: node
linkType: hard
"space-separated-tokens@npm:^2.0.0":
version: 2.0.2
resolution: "space-separated-tokens@npm:2.0.2"
checksum: 202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990
languageName: node
linkType: hard
"spdy-transport@npm:^3.0.0":
version: 3.0.0
resolution: "spdy-transport@npm:3.0.0"
@ -9435,6 +9926,7 @@ __metadata:
react-dom: ^18.2.0
react-helmet: ^6.1.0
react-intersection-observer: ^9.5.1
react-markdown: ^8.0.7
react-router-dom: ^6.13.0
react-tag-input-component: ^2.0.2
semantic-sdp: ^3.26.2
@ -9603,6 +10095,15 @@ __metadata:
languageName: node
linkType: hard
"style-to-object@npm:^0.4.0":
version: 0.4.1
resolution: "style-to-object@npm:0.4.1"
dependencies:
inline-style-parser: 0.1.1
checksum: 2ea213e98eed21764ae1d1dc9359231a9f2d480d6ba55344c4c15eb275f0809f1845786e66d4caf62414a5cc8f112ce9425a58d251c77224060373e0db48f8c2
languageName: node
linkType: hard
"stylehacks@npm:^6.0.0":
version: 6.0.0
resolution: "stylehacks@npm:6.0.0"
@ -9801,6 +10302,20 @@ __metadata:
languageName: node
linkType: hard
"trim-lines@npm:^3.0.0":
version: 3.0.1
resolution: "trim-lines@npm:3.0.1"
checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed
languageName: node
linkType: hard
"trough@npm:^2.0.0":
version: 2.1.0
resolution: "trough@npm:2.1.0"
checksum: a577bb561c2b401cc0e1d9e188fcfcdf63b09b151ff56a668da12197fe97cac15e3d77d5b51f426ccfd94255744a9118e9e9935afe81a3644fa1be9783c82886
languageName: node
linkType: hard
"ts-api-utils@npm:^1.0.1":
version: 1.0.1
resolution: "ts-api-utils@npm:1.0.1"
@ -9992,6 +10507,21 @@ __metadata:
languageName: node
linkType: hard
"unified@npm:^10.0.0":
version: 10.1.2
resolution: "unified@npm:10.1.2"
dependencies:
"@types/unist": ^2.0.0
bail: ^2.0.0
extend: ^3.0.0
is-buffer: ^2.0.0
is-plain-obj: ^4.0.0
trough: ^2.0.0
vfile: ^5.0.0
checksum: 053e7c65ede644607f87bd625a299e4b709869d2f76ec8138569e6e886903b6988b21cd9699e471eda42bee189527be0a9dac05936f1d069a5e65d0125d5d756
languageName: node
linkType: hard
"unique-filename@npm:^3.0.0":
version: 3.0.0
resolution: "unique-filename@npm:3.0.0"
@ -10019,6 +10549,61 @@ __metadata:
languageName: node
linkType: hard
"unist-util-generated@npm:^2.0.0":
version: 2.0.1
resolution: "unist-util-generated@npm:2.0.1"
checksum: 6221ad0571dcc9c8964d6b054f39ef6571ed59cc0ce3e88ae97ea1c70afe76b46412a5ffaa91f96814644ac8477e23fb1b477d71f8d70e625728c5258f5c0d99
languageName: node
linkType: hard
"unist-util-is@npm:^5.0.0":
version: 5.2.1
resolution: "unist-util-is@npm:5.2.1"
dependencies:
"@types/unist": ^2.0.0
checksum: ae76fdc3d35352cd92f1bedc3a0d407c3b9c42599a52ab9141fe89bdd786b51f0ec5a2ab68b93fb532e239457cae62f7e39eaa80229e1cb94875da2eafcbe5c4
languageName: node
linkType: hard
"unist-util-position@npm:^4.0.0":
version: 4.0.4
resolution: "unist-util-position@npm:4.0.4"
dependencies:
"@types/unist": ^2.0.0
checksum: e7487b6cec9365299695e3379ded270a1717074fa11fd2407c9b934fb08db6fe1d9077ddeaf877ecf1813665f8ccded5171693d3d9a7a01a125ec5cdd5e88691
languageName: node
linkType: hard
"unist-util-stringify-position@npm:^3.0.0":
version: 3.0.3
resolution: "unist-util-stringify-position@npm:3.0.3"
dependencies:
"@types/unist": ^2.0.0
checksum: dbd66c15183607ca942a2b1b7a9f6a5996f91c0d30cf8966fb88955a02349d9eefd3974e9010ee67e71175d784c5a9fea915b0aa0b0df99dcb921b95c4c9e124
languageName: node
linkType: hard
"unist-util-visit-parents@npm:^5.1.1":
version: 5.1.3
resolution: "unist-util-visit-parents@npm:5.1.3"
dependencies:
"@types/unist": ^2.0.0
unist-util-is: ^5.0.0
checksum: 8ecada5978994f846b64658cf13b4092cd78dea39e1ba2f5090a5de842ba4852712c02351a8ae95250c64f864635e7b02aedf3b4a093552bb30cf1bd160efbaa
languageName: node
linkType: hard
"unist-util-visit@npm:^4.0.0":
version: 4.1.2
resolution: "unist-util-visit@npm:4.1.2"
dependencies:
"@types/unist": ^2.0.0
unist-util-is: ^5.0.0
unist-util-visit-parents: ^5.1.1
checksum: 95a34e3f7b5b2d4b68fd722b6229972099eb97b6df18913eda44a5c11df8b1e27efe7206dd7b88c4ed244a48c474a5b2e2629ab79558ff9eb936840295549cee
languageName: node
linkType: hard
"universalify@npm:^2.0.0":
version: 2.0.0
resolution: "universalify@npm:2.0.0"
@ -10143,6 +10728,20 @@ __metadata:
languageName: node
linkType: hard
"uvu@npm:^0.5.0":
version: 0.5.6
resolution: "uvu@npm:0.5.6"
dependencies:
dequal: ^2.0.0
diff: ^5.0.0
kleur: ^4.0.3
sade: ^1.7.3
bin:
uvu: bin.js
checksum: 09460a37975627de9fcad396e5078fb844d01aaf64a6399ebfcfd9e55f1c2037539b47611e8631f89be07656962af0cf48c334993db82b9ae9c3d25ce3862168
languageName: node
linkType: hard
"vary@npm:~1.1.2":
version: 1.1.2
resolution: "vary@npm:1.1.2"
@ -10150,6 +10749,28 @@ __metadata:
languageName: node
linkType: hard
"vfile-message@npm:^3.0.0":
version: 3.1.4
resolution: "vfile-message@npm:3.1.4"
dependencies:
"@types/unist": ^2.0.0
unist-util-stringify-position: ^3.0.0
checksum: d0ee7da1973ad76513c274e7912adbed4d08d180eaa34e6bd40bc82459f4b7bc50fcaff41556135e3339995575eac5f6f709aba9332b80f775618ea4880a1367
languageName: node
linkType: hard
"vfile@npm:^5.0.0":
version: 5.3.7
resolution: "vfile@npm:5.3.7"
dependencies:
"@types/unist": ^2.0.0
is-buffer: ^2.0.0
unist-util-stringify-position: ^3.0.0
vfile-message: ^3.0.0
checksum: 642cce703afc186dbe7cabf698dc954c70146e853491086f5da39e1ce850676fc96b169fcf7898aa3ff245e9313aeec40da93acd1e1fcc0c146dc4f6308b4ef9
languageName: node
linkType: hard
"watchpack@npm:^2.4.0":
version: 2.4.0
resolution: "watchpack@npm:2.4.0"