mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-04-26 09:25:22 -04:00
react-twitter-embed
➡️ react-tweet
This commit is contained in:
parent
61660d9d56
commit
1d8c2eab99
@ -1,7 +1,7 @@
|
||||
import Code from "../Code";
|
||||
import CopyButton from "../CopyButton";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Block = styled("div", {
|
||||
position: "relative",
|
||||
@ -87,7 +87,7 @@ const CornerCopyButton = styled(CopyButton, {
|
||||
transition: `background ${theme.transitions.fade}, border ${theme.transitions.fade}`,
|
||||
});
|
||||
|
||||
export type CodeBlockProps = ComponentProps<typeof Code> & {
|
||||
export type CodeBlockProps = ComponentPropsWithoutRef<typeof Code> & {
|
||||
highlight?: boolean;
|
||||
withCopyButton?: boolean;
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import Giscus from "@giscus/react";
|
||||
import useTheme from "../../hooks/useTheme";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import { giscusConfig } from "../../lib/config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
import type { GiscusProps } from "@giscus/react";
|
||||
|
||||
const Wrapper = styled("div", {
|
||||
@ -12,7 +12,7 @@ const Wrapper = styled("div", {
|
||||
minHeight: "360px",
|
||||
});
|
||||
|
||||
export type CommentsProps = ComponentProps<typeof Wrapper> & {
|
||||
export type CommentsProps = ComponentPropsWithoutRef<typeof Wrapper> & {
|
||||
title: string;
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,7 @@ import innerText from "react-innertext";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { ClipboardOcticon, CheckOcticon } from "../Icons";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ReactNode, Ref, ComponentPropsWithoutRef, MouseEventHandler } from "react";
|
||||
import type { ReactNode, Ref, ComponentPropsWithoutRef, ElementRef, MouseEventHandler } from "react";
|
||||
|
||||
const Button = styled("button", {
|
||||
lineHeight: 1,
|
||||
@ -37,10 +37,10 @@ export type CopyButtonProps = ComponentPropsWithoutRef<typeof Button> & {
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
const CopyButton = ({ source, timeout = 2000, ...rest }: CopyButtonProps, ref: Ref<HTMLButtonElement>) => {
|
||||
const CopyButton = ({ source, timeout = 2000, ...rest }: CopyButtonProps, ref: Ref<ElementRef<typeof Button>>) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy: MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
const handleCopy: MouseEventHandler<ElementRef<typeof Button>> = (e) => {
|
||||
// prevent unintentional double-clicks by unfocusing button
|
||||
e.currentTarget.blur();
|
||||
|
||||
|
@ -2,7 +2,7 @@ import Link from "../Link";
|
||||
import { HeartIcon, NextjsLogo } from "../Icons";
|
||||
import { styled, theme, keyframes } from "../../lib/styles/stitches.config";
|
||||
import * as config from "../../lib/config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Wrapper = styled("footer", {
|
||||
width: "100%",
|
||||
@ -78,7 +78,7 @@ const Heart = styled("span", {
|
||||
},
|
||||
});
|
||||
|
||||
export type FooterProps = ComponentProps<typeof Wrapper>;
|
||||
export type FooterProps = ComponentPropsWithoutRef<typeof Wrapper>;
|
||||
|
||||
const Footer = ({ ...rest }: FooterProps) => {
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Selfie from "../Selfie";
|
||||
import Menu from "../Menu";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Wrapper = styled("header", {
|
||||
width: "100%",
|
||||
@ -40,7 +40,7 @@ const ResponsiveMenu = styled(Menu, {
|
||||
},
|
||||
});
|
||||
|
||||
export type HeaderProps = ComponentProps<typeof Wrapper>;
|
||||
export type HeaderProps = ComponentPropsWithoutRef<typeof Wrapper>;
|
||||
|
||||
const Header = ({ ...rest }: HeaderProps) => {
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import innerText from "react-innertext";
|
||||
import HeadingAnchor from "../HeadingAnchor";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Anchor = styled(HeadingAnchor, {
|
||||
margin: "0 0.4em",
|
||||
@ -52,7 +52,7 @@ const H = styled("h1", {
|
||||
},
|
||||
});
|
||||
|
||||
export type HeadingProps = ComponentProps<typeof H> & {
|
||||
export type HeadingProps = ComponentPropsWithoutRef<typeof H> & {
|
||||
level: 1 | 2 | 3 | 4 | 5 | 6;
|
||||
divider?: boolean;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Link from "../Link";
|
||||
import { LinkIcon } from "../Icons";
|
||||
import { styled } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const AnchorLink = styled(Link, {
|
||||
lineHeight: 1,
|
||||
@ -12,7 +12,7 @@ const Icon = styled(LinkIcon, {
|
||||
height: "0.8em",
|
||||
});
|
||||
|
||||
export type HeadingAnchorProps = Omit<ComponentProps<typeof AnchorLink>, "href"> & {
|
||||
export type HeadingAnchorProps = Omit<ComponentPropsWithoutRef<typeof AnchorLink>, "href"> & {
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const RoundedIFrame = styled("iframe", {
|
||||
width: "100%",
|
||||
@ -9,7 +9,7 @@ const RoundedIFrame = styled("iframe", {
|
||||
borderRadius: theme.radii.corner,
|
||||
});
|
||||
|
||||
export type IFrameProps = ComponentProps<typeof RoundedIFrame> & {
|
||||
export type IFrameProps = ComponentPropsWithoutRef<typeof RoundedIFrame> & {
|
||||
src: string;
|
||||
height: number;
|
||||
width?: number; // defaults to 100%
|
||||
|
@ -1,7 +1,7 @@
|
||||
import NextImage from "next/image";
|
||||
import Link from "../Link";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
import type { ImageProps as NextImageProps, StaticImageData } from "next/image";
|
||||
|
||||
const DEFAULT_QUALITY = 60;
|
||||
@ -22,7 +22,7 @@ const StyledImage = styled(NextImage, {
|
||||
borderRadius: theme.radii.corner,
|
||||
});
|
||||
|
||||
export type ImageProps = ComponentProps<typeof StyledImage> & {
|
||||
export type ImageProps = ComponentPropsWithoutRef<typeof StyledImage> & {
|
||||
href?: string; // optionally wrap image in a link
|
||||
inline?: boolean; // don't wrap everything in a `<div>` block
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import Footer from "../Footer";
|
||||
import { SkipToContentLink, SkipToContentTarget } from "../SkipToContent";
|
||||
import useTheme from "../../hooks/useTheme";
|
||||
import { styled, theme, darkTheme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Flex = styled("div", {
|
||||
display: "flex",
|
||||
@ -34,7 +34,7 @@ const FlexedFooter = styled(Footer, {
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
export type LayoutProps = ComponentProps<typeof Flex> & {
|
||||
export type LayoutProps = ComponentPropsWithoutRef<typeof Flex> & {
|
||||
container?: boolean; // pass false to disable default `<main>` container styles with padding, etc.
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import NextLink from "next/link";
|
||||
import objStr from "obj-str";
|
||||
import { styled, theme, stitchesConfig } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const StyledLink = styled(NextLink, {
|
||||
color: theme.colors.link,
|
||||
@ -34,7 +34,7 @@ const StyledLink = styled(NextLink, {
|
||||
},
|
||||
});
|
||||
|
||||
export type LinkProps = ComponentProps<typeof StyledLink> & {
|
||||
export type LinkProps = ComponentPropsWithoutRef<typeof StyledLink> & {
|
||||
openInNewTab?: boolean;
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { styled, theme, keyframes } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Wrapper = styled("div", {
|
||||
display: "inline-block",
|
||||
@ -20,7 +20,7 @@ const Box = styled("div", {
|
||||
backgroundColor: theme.colors.mediumLight,
|
||||
});
|
||||
|
||||
export type LoadingProps = ComponentProps<typeof Wrapper> & {
|
||||
export type LoadingProps = ComponentPropsWithoutRef<typeof Wrapper> & {
|
||||
width: number; // of entire container, in pixels
|
||||
boxes?: number; // total number of boxes (default: 3)
|
||||
timing?: number; // staggered timing between each box's pulse, in seconds (default: 0.1s)
|
||||
|
@ -3,7 +3,7 @@ import MenuItem from "../MenuItem";
|
||||
import ThemeToggle from "../ThemeToggle";
|
||||
import { styled } from "../../lib/styles/stitches.config";
|
||||
import { menuItems } from "../../lib/config/menu";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Wrapper = styled("ul", {
|
||||
display: "inline-flex",
|
||||
@ -38,7 +38,7 @@ const Item = styled("li", {
|
||||
},
|
||||
});
|
||||
|
||||
export type MenuProps = ComponentProps<typeof Wrapper>;
|
||||
export type MenuProps = ComponentPropsWithoutRef<typeof Wrapper>;
|
||||
|
||||
const Menu = ({ ...rest }: MenuProps) => {
|
||||
const router = useRouter();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Link from "../Link";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
import type { NoteFrontMatter } from "../../types";
|
||||
|
||||
const Title = styled("h1", {
|
||||
@ -22,7 +22,8 @@ const TitleLink = styled(Link, {
|
||||
color: theme.colors.text,
|
||||
});
|
||||
|
||||
export type NoteTitleProps = Pick<NoteFrontMatter, "slug" | "title" | "htmlTitle"> & ComponentProps<typeof Title>;
|
||||
export type NoteTitleProps = Pick<NoteFrontMatter, "slug" | "title" | "htmlTitle"> &
|
||||
ComponentPropsWithoutRef<typeof Title>;
|
||||
|
||||
const NoteTitle = ({ slug, title, htmlTitle, ...rest }: NoteTitleProps) => {
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Link from "../Link";
|
||||
import { OctocatOcticon } from "../Icons";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const GitHubLink = styled(Link, {
|
||||
margin: "0 0.4em",
|
||||
@ -19,7 +19,7 @@ const Octocat = styled(OctocatOcticon, {
|
||||
fill: "currentColor",
|
||||
});
|
||||
|
||||
export type OctocatLinkProps = Omit<ComponentProps<typeof GitHubLink>, "href"> & {
|
||||
export type OctocatLinkProps = Omit<ComponentPropsWithoutRef<typeof GitHubLink>, "href"> & {
|
||||
repo: string;
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "../Link";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const Title = styled("h1", {
|
||||
marginTop: 0,
|
||||
@ -19,7 +19,7 @@ const TitleLink = styled(Link, {
|
||||
color: theme.colors.text,
|
||||
});
|
||||
|
||||
export type PageTitleProps = ComponentProps<typeof Title>;
|
||||
export type PageTitleProps = ComponentPropsWithoutRef<typeof Title>;
|
||||
|
||||
const PageTitle = ({ children, ...rest }: PageTitleProps) => {
|
||||
const router = useRouter();
|
||||
|
@ -2,7 +2,7 @@ import Link from "../Link";
|
||||
import Image from "../Image";
|
||||
import { styled, theme } from "../../lib/styles/stitches.config";
|
||||
import { authorName } from "../../lib/config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
import selfieJpg from "../../public/static/images/selfie.jpg";
|
||||
|
||||
@ -47,7 +47,7 @@ const Name = styled("span", {
|
||||
},
|
||||
});
|
||||
|
||||
export type SelfieProps = Omit<ComponentProps<typeof Link>, "href">;
|
||||
export type SelfieProps = Omit<ComponentPropsWithoutRef<typeof Link>, "href">;
|
||||
|
||||
const Selfie = ({ ...rest }: SelfieProps) => {
|
||||
return (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { keyframes, styled } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const BlackBox = styled("div", {
|
||||
width: "100%",
|
||||
@ -31,7 +31,7 @@ const Underscore = styled("span", {
|
||||
animation: `${keyframes({ "40%": { opacity: 0 } })} 1s step-end infinite`,
|
||||
});
|
||||
|
||||
export type TerminalProps = ComponentProps<typeof BlackBox>;
|
||||
export type TerminalProps = ComponentPropsWithoutRef<typeof BlackBox>;
|
||||
|
||||
// a DOS-style terminal box with dynamic text
|
||||
const Terminal = ({ children: message, ...rest }: TerminalProps) => {
|
||||
|
@ -1,40 +1,47 @@
|
||||
import { useState } from "react";
|
||||
import { TwitterTweetEmbed } from "react-twitter-embed";
|
||||
import useUpdateEffect from "../../hooks/useUpdateEffect";
|
||||
import { useEffect, useRef } from "react";
|
||||
import Image from "next/image";
|
||||
import { Tweet } from "react-tweet";
|
||||
import useTheme from "../../hooks/useTheme";
|
||||
import { styled } from "../../lib/styles/stitches.config";
|
||||
import type { ComponentPropsWithoutRef, ElementRef } from "react";
|
||||
|
||||
const Wrapper = styled("div", {
|
||||
// reserve a moderate amount of space for the widget, it takes a while to load...
|
||||
minHeight: "300px",
|
||||
minHeight: "300px", // help with layout shift
|
||||
|
||||
"& .react-tweet-theme": {
|
||||
"--tweet-container-margin": "1.5rem auto",
|
||||
},
|
||||
});
|
||||
|
||||
export type TweetEmbedProps = {
|
||||
id: string;
|
||||
options?: Record<string, unknown>;
|
||||
export type TweetEmbedProps = ComponentPropsWithoutRef<typeof Tweet> & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const TweetEmbed = ({ id, options, className }: TweetEmbedProps) => {
|
||||
const TweetEmbed = ({ id, className, ...rest }: TweetEmbedProps) => {
|
||||
const containerRef = useRef<ElementRef<typeof Wrapper>>(null);
|
||||
const { activeTheme } = useTheme();
|
||||
const [widgetTheme, setWidgetTheme] = useState(activeTheme);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
setWidgetTheme(activeTheme === "dark" ? activeTheme : "light");
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
// setting 'data-theme' attribute of parent div changes the tweet's theme (no re-render necessary)
|
||||
containerRef.current.dataset.theme = activeTheme;
|
||||
}
|
||||
}, [activeTheme]);
|
||||
|
||||
return (
|
||||
<Wrapper className={className}>
|
||||
<TwitterTweetEmbed
|
||||
tweetId={id}
|
||||
options={{
|
||||
dnt: true,
|
||||
align: "center",
|
||||
theme: widgetTheme,
|
||||
...options,
|
||||
<Wrapper ref={containerRef} className={className}>
|
||||
<Tweet
|
||||
key={`tweet-${id}`}
|
||||
id={id}
|
||||
apiUrl={`/api/tweet/?id=${id}`} // edge function at pages/api/tweet.ts
|
||||
components={{
|
||||
// https://react-tweet.vercel.app/twitter-theme/api-reference#custom-tweet-components
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
AvatarImg: (props) => <Image {...props} />,
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
MediaImg: (props) => <Image {...props} fill />,
|
||||
}}
|
||||
// changing this key forces the iframe URL to reformulate itself and update the theme:
|
||||
key={`tweet-${id}-${widgetTheme}`}
|
||||
{...rest}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
import RFB from "@novnc/novnc/core/rfb";
|
||||
import Terminal from "../Terminal";
|
||||
import { styled } from "../../lib/styles/stitches.config";
|
||||
import type { Ref, ComponentPropsWithoutRef } from "react";
|
||||
import type { Ref, ComponentPropsWithoutRef, ElementRef } from "react";
|
||||
|
||||
const Display = styled(
|
||||
"div",
|
||||
@ -50,7 +50,7 @@ const VNC = ({ server, style, ...rest }: VNCProps, ref: Ref<Partial<RFB>>) => {
|
||||
|
||||
// the actual connection and virtual screen (injected by noVNC when it's ready)
|
||||
const rfbRef = useRef<RFB | null>(null);
|
||||
const screenRef = useRef<HTMLDivElement>(null);
|
||||
const displayRef = useRef<ElementRef<typeof Display>>(null);
|
||||
|
||||
// ends the session forcefully
|
||||
const disconnect = useCallback(() => {
|
||||
@ -121,7 +121,7 @@ const VNC = ({ server, style, ...rest }: VNCProps, ref: Ref<Partial<RFB>>) => {
|
||||
setLoaded(true);
|
||||
|
||||
// https://github.com/novnc/noVNC/blob/master/docs/API.md
|
||||
rfbRef.current = new RFB(screenRef.current as Element, server, {
|
||||
rfbRef.current = new RFB(displayRef.current as Element, server, {
|
||||
wsProtocols: ["binary", "base64"],
|
||||
});
|
||||
|
||||
@ -164,7 +164,7 @@ const VNC = ({ server, style, ...rest }: VNCProps, ref: Ref<Partial<RFB>>) => {
|
||||
|
||||
{/* the VNC canvas is hidden until we've successfully connected to the socket */}
|
||||
<Display
|
||||
ref={screenRef}
|
||||
ref={displayRef}
|
||||
style={{
|
||||
display: !connected ? "none" : undefined,
|
||||
...style,
|
||||
|
@ -11,7 +11,10 @@ const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
trailingSlash: true,
|
||||
productionBrowserSourceMaps: true,
|
||||
transpilePackages: ["@novnc/novnc"],
|
||||
transpilePackages: [
|
||||
"@novnc/novnc",
|
||||
"react-tweet", // https://react-tweet.vercel.app/next#troubleshooting
|
||||
],
|
||||
env: {
|
||||
BASE_URL:
|
||||
// start with production check on Vercel, then see if this is a deploy preview, then fallback to local dev server.
|
||||
@ -27,6 +30,10 @@ const nextConfig = {
|
||||
images: {
|
||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
|
||||
formats: ["image/avif", "image/webp"],
|
||||
remotePatterns: [
|
||||
{ protocol: "https", hostname: "pbs.twimg.com" },
|
||||
{ protocol: "https", hostname: "abs.twimg.com" },
|
||||
],
|
||||
},
|
||||
experimental: {
|
||||
legacyBrowsers: false,
|
||||
|
24
package.json
24
package.json
@ -20,13 +20,13 @@
|
||||
"dependencies": {
|
||||
"@giscus/react": "^2.3.0",
|
||||
"@hcaptcha/react-hcaptcha": "^1.8.1",
|
||||
"@novnc/novnc": "github:novnc/novnc#ca6527c1bf7131adccfdcc5028964a1e67f9018c",
|
||||
"@novnc/novnc": "1.4.0-ge81602d",
|
||||
"@octokit/graphql": "^7.0.1",
|
||||
"@octokit/graphql-schema": "^14.27.2",
|
||||
"@octokit/graphql-schema": "^14.27.3",
|
||||
"@primer/octicons": "^19.7.0",
|
||||
"@prisma/client": "^5.2.0",
|
||||
"@react-spring/web": "^9.7.3",
|
||||
"@stitches/react": "^1.3.1-1",
|
||||
"@stitches/react": "1.3.1-1",
|
||||
"@vercel/postgres": "^0.4.1",
|
||||
"comma-number": "^2.1.0",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
@ -35,7 +35,7 @@
|
||||
"fathom-client": "^3.5.0",
|
||||
"feather-icons": "^4.29.1",
|
||||
"feed": "^4.2.2",
|
||||
"formik": "^2.4.3",
|
||||
"formik": "^2.4.4",
|
||||
"gray-matter": "^4.0.3",
|
||||
"next": "13.4.19",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
@ -55,7 +55,7 @@
|
||||
"react-is": "18.2.0",
|
||||
"react-player": "~2.10.1",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
"react-twitter-embed": "^4.0.4",
|
||||
"react-tweet": "^3.1.1",
|
||||
"rehype-prism-plus": "^1.6.3",
|
||||
"rehype-sanitize": "^5.0.1",
|
||||
"rehype-slug": "^5.1.0",
|
||||
@ -66,17 +66,17 @@
|
||||
"remark-smartypants": "^2.0.0",
|
||||
"remark-unwrap-images": "^3.0.1",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"simple-icons": "^9.12.0",
|
||||
"simple-icons": "^9.13.0",
|
||||
"sitemap": "^7.1.1",
|
||||
"stitches-normalize": "^2.0.0",
|
||||
"swr": "^2.2.2",
|
||||
"unified": "^10.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jakejarvis/eslint-config": "github:jakejarvis/eslint-config",
|
||||
"@jakejarvis/eslint-config": "^3.1.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/comma-number": "^2.1.0",
|
||||
"@types/node": "^18.17.12",
|
||||
"@types/node": "^18.17.14",
|
||||
"@types/novnc__novnc": "^1.3.0",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react": "^18.2.21",
|
||||
@ -84,8 +84,8 @@
|
||||
"@types/react-is": "^18.2.1",
|
||||
"@types/remove-markdown": "^0.3.1",
|
||||
"@types/uglify-js": "^3.17.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
||||
"@typescript-eslint/parser": "^6.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
||||
"@typescript-eslint/parser": "^6.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "~8.48.0",
|
||||
"eslint-config-next": "13.4.19",
|
||||
@ -117,9 +117,9 @@
|
||||
"eslint"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@8.7.1",
|
||||
"packageManager": "pnpm@8.7.4",
|
||||
"volta": {
|
||||
"node": "18.17.1",
|
||||
"pnpm": "8.7.1"
|
||||
"pnpm": "8.7.4"
|
||||
}
|
||||
}
|
||||
|
36
pages/api/tweet.ts
Normal file
36
pages/api/tweet.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getTweet } from "react-tweet/api";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export const config = {
|
||||
runtime: "edge",
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default async (req: NextRequest) => {
|
||||
const tweetId = req.nextUrl.searchParams.get("id");
|
||||
|
||||
if (typeof tweetId !== "string") {
|
||||
return NextResponse.json({ error: "Bad request." }, { status: 400 });
|
||||
}
|
||||
|
||||
// https://react-tweet.vercel.app/twitter-theme/api-reference
|
||||
try {
|
||||
const tweet = await getTweet(tweetId);
|
||||
return NextResponse.json(
|
||||
{ data: tweet ?? null },
|
||||
{
|
||||
status: tweet ? 200 : 404,
|
||||
headers: {
|
||||
// cache on edge for 12 hours
|
||||
"Cache-Control": "public, max-age=0, s-maxage=43200, stale-while-revalidate",
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
error: any
|
||||
) {
|
||||
return NextResponse.json({ error: error.message ?? "Bad request." }, { status: 400 });
|
||||
}
|
||||
};
|
@ -6,7 +6,7 @@ import { NextSeo } from "next-seo";
|
||||
import Layout from "../components/Layout";
|
||||
import Terminal from "../components/Terminal";
|
||||
import { styled } from "../lib/styles/stitches.config";
|
||||
import type { ReactElement, ComponentProps } from "react";
|
||||
import type { ReactElement, ComponentPropsWithoutRef, ElementRef } from "react";
|
||||
|
||||
// obviously, an interactive VNC display will not work even a little bit server-side
|
||||
const VNC = dynamic(() => import("../components/VNC"), { ssr: false });
|
||||
@ -25,8 +25,8 @@ const Wallpaper = styled("main", {
|
||||
backgroundPosition: "center",
|
||||
});
|
||||
|
||||
const RandomWallpaper = ({ ...rest }: ComponentProps<typeof Wallpaper>) => {
|
||||
const wallpaperRef = useRef<HTMLDivElement>(null);
|
||||
const RandomWallpaper = ({ ...rest }: ComponentPropsWithoutRef<typeof Wallpaper>) => {
|
||||
const wallpaperRef = useRef<ElementRef<typeof Wallpaper>>(null);
|
||||
|
||||
// set a random retro Windows ME desktop tile for the entire content area
|
||||
useEffect(() => {
|
||||
|
1299
pnpm-lock.yaml
generated
1299
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user