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