mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-04-27 08:58:30 -04:00
more optimization/error handling
This commit is contained in:
parent
831139c132
commit
8a97b706be
@ -1,7 +1,7 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import Loading from "../Loading";
|
import Loading from "../Loading";
|
||||||
import { fetcher } from "../../lib/helpers/fetcher";
|
import { fetcher } from "../../lib/helpers/fetcher";
|
||||||
import { siteLocale } from "../../lib/config";
|
import { commafy } from "../../lib/helpers/format-number";
|
||||||
|
|
||||||
export type HitCounterProps = {
|
export type HitCounterProps = {
|
||||||
slug: string;
|
slug: string;
|
||||||
@ -15,7 +15,6 @@ const HitCounter = ({ slug, className }: HitCounterProps) => {
|
|||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
// show spinning loading indicator if data isn't fetched yet
|
// show spinning loading indicator if data isn't fetched yet
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <Loading boxes={3} width={20} />;
|
return <Loading boxes={3} width={20} />;
|
||||||
@ -28,16 +27,10 @@ const HitCounter = ({ slug, className }: HitCounterProps) => {
|
|||||||
|
|
||||||
// we have data!
|
// we have data!
|
||||||
return (
|
return (
|
||||||
<span
|
<span title={`${commafy(data.hits)} ${data.hits === 1 ? "view" : "views"}`} className={className}>
|
||||||
title={`${data.hits.toLocaleString(siteLocale)} ${data.hits === 1 ? "view" : "views"}`}
|
{commafy(data.hits)}
|
||||||
className={className}
|
|
||||||
>
|
|
||||||
{data.hits.toLocaleString(siteLocale)}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HitCounter;
|
export default HitCounter;
|
||||||
|
@ -42,6 +42,8 @@ const Loading = ({ width, boxes = 3, timing = 0.1, css, ...rest }: LoadingProps)
|
|||||||
key={i}
|
key={i}
|
||||||
css={{
|
css={{
|
||||||
width: `${width / (boxes + 1)}px`,
|
width: `${width / (boxes + 1)}px`,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
animationDelay: `${i * timing}s`,
|
animationDelay: `${i * timing}s`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Time from "../Time";
|
import Time from "../Time";
|
||||||
import HitCounter from "../HitCounter";
|
import HitCounter from "../HitCounter";
|
||||||
@ -108,6 +109,7 @@ const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaProps) =>
|
|||||||
|
|
||||||
{/* only count hits on production site */}
|
{/* only count hits on production site */}
|
||||||
{process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && (
|
{process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && (
|
||||||
|
<ErrorBoundary fallback={null}>
|
||||||
<MetaItem
|
<MetaItem
|
||||||
// fix potential layout shift when number of hits loads
|
// fix potential layout shift when number of hits loads
|
||||||
css={{ minWidth: "7em", marginRight: 0 }}
|
css={{ minWidth: "7em", marginRight: 0 }}
|
||||||
@ -117,6 +119,7 @@ const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaProps) =>
|
|||||||
</span>
|
</span>
|
||||||
<HitCounter slug={`notes/${slug}`} />
|
<HitCounter slug={`notes/${slug}`} />
|
||||||
</MetaItem>
|
</MetaItem>
|
||||||
|
</ErrorBoundary>
|
||||||
)}
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|
||||||
|
@ -3,17 +3,20 @@ import { formatDateTZ, formatDateISO, formatTimeAgo, FlexibleDate } from "../../
|
|||||||
|
|
||||||
export type RelativeTimeProps = {
|
export type RelativeTimeProps = {
|
||||||
date: FlexibleDate;
|
date: FlexibleDate;
|
||||||
|
prefix?: string; // optional "Updated", "Published", "Created", etc.
|
||||||
|
staticFormat?: string; // full date (without timestamp)
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RelativeTime = ({ date, className }: RelativeTimeProps) => {
|
const RelativeTime = ({ date, prefix, staticFormat = "PP", className }: RelativeTimeProps) => {
|
||||||
// play nice with SSR -- only use relative time on the client, since it'll quickly become outdated on the server and
|
// play nice with SSR -- only use relative time on the client, since it'll quickly become outdated on the server and
|
||||||
// cause a react hydration mismatch error.
|
// cause a react hydration mismatch error.
|
||||||
const hasMounted = useHasMounted();
|
const hasMounted = useHasMounted();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<time dateTime={formatDateISO(date)} title={formatDateTZ(date)} className={className}>
|
<time dateTime={formatDateISO(date)} title={formatDateTZ(date)} className={className}>
|
||||||
Updated {hasMounted ? formatTimeAgo(date) : `on ${formatDateTZ(date, "PP")}`}
|
{prefix && `${prefix} `}
|
||||||
|
{hasMounted ? formatTimeAgo(date) : `on ${formatDateTZ(date, staticFormat)}`}
|
||||||
</time>
|
</time>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import Link from "../Link";
|
import Link from "../Link";
|
||||||
import RelativeTime from "../RelativeTime";
|
import RelativeTime from "../RelativeTime";
|
||||||
import { StarOcticon, ForkOcticon } from "../Icons";
|
import { StarOcticon, ForkOcticon } from "../Icons";
|
||||||
|
import { commafy } from "../../lib/helpers/format-number";
|
||||||
import { styled } from "../../lib/styles/stitches.config";
|
import { styled } from "../../lib/styles/stitches.config";
|
||||||
import { siteLocale } from "../../lib/config";
|
|
||||||
import type { RepositoryType } from "../../types";
|
import type { RepositoryType } from "../../types";
|
||||||
|
|
||||||
const Wrapper = styled("div", {
|
const Wrapper = styled("div", {
|
||||||
@ -100,12 +100,12 @@ const RepositoryCard = ({
|
|||||||
<MetaItem>
|
<MetaItem>
|
||||||
<MetaLink
|
<MetaLink
|
||||||
href={`${url}/stargazers`}
|
href={`${url}/stargazers`}
|
||||||
title={`${stars.toLocaleString(siteLocale)} ${stars === 1 ? "star" : "stars"}`}
|
title={`${commafy(stars)} ${stars === 1 ? "star" : "stars"}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<MetaIcon as={StarOcticon} />
|
<MetaIcon as={StarOcticon} />
|
||||||
<span>{stars.toLocaleString(siteLocale)}</span>
|
<span>{commafy(stars)}</span>
|
||||||
</MetaLink>
|
</MetaLink>
|
||||||
</MetaItem>
|
</MetaItem>
|
||||||
)}
|
)}
|
||||||
@ -114,19 +114,19 @@ const RepositoryCard = ({
|
|||||||
<MetaItem>
|
<MetaItem>
|
||||||
<MetaLink
|
<MetaLink
|
||||||
href={`${url}/network/members`}
|
href={`${url}/network/members`}
|
||||||
title={`${forks.toLocaleString(siteLocale)} ${forks === 1 ? "fork" : "forks"}`}
|
title={`${commafy(forks)} ${forks === 1 ? "fork" : "forks"}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<MetaIcon as={ForkOcticon} />
|
<MetaIcon as={ForkOcticon} />
|
||||||
<span>{forks.toLocaleString(siteLocale)}</span>
|
<span>{commafy(forks)}</span>
|
||||||
</MetaLink>
|
</MetaLink>
|
||||||
</MetaItem>
|
</MetaItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* only use relative "time ago" on client side, since it'll be outdated via SSG and cause hydration errors */}
|
{/* only use relative "time ago" on client side, since it'll be outdated via SSG and cause hydration errors */}
|
||||||
<MetaItem>
|
<MetaItem>
|
||||||
<RelativeTime date={updatedAt} />
|
<RelativeTime date={updatedAt} prefix="Updated" />
|
||||||
</MetaItem>
|
</MetaItem>
|
||||||
</Meta>
|
</Meta>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { memo } from "react";
|
import { useMemo } from "react";
|
||||||
import { minify } from "uglify-js";
|
import { minify } from "uglify-js";
|
||||||
import { clientScript } from "./script";
|
import { clientScript } from "./script";
|
||||||
import { darkModeQuery, themeStorageKey, themeClassNames } from "../../lib/config/themes";
|
import { darkModeQuery, themeStorageKey, themeClassNames } from "../../lib/config/themes";
|
||||||
|
|
||||||
const ThemeScript = () => {
|
const ThemeScript = () => {
|
||||||
// since the function above will end up being injected as a plain dumb string, we need to set the dynamic values here:
|
const minified = useMemo(() => {
|
||||||
|
// since the client function will end up being injected as a plain dumb string, we need to set dynamic values here:
|
||||||
const functionString = String(clientScript)
|
const functionString = String(clientScript)
|
||||||
.replace('"__MEDIA_QUERY__"', `"${darkModeQuery}"`)
|
.replace('"__MEDIA_QUERY__"', `"${darkModeQuery}"`)
|
||||||
.replace('"__STORAGE_KEY__"', `"${themeStorageKey}"`)
|
.replace('"__STORAGE_KEY__"', `"${themeStorageKey}"`)
|
||||||
@ -12,13 +13,13 @@ const ThemeScript = () => {
|
|||||||
.replace(
|
.replace(
|
||||||
'"__LIST_OF_CLASSES__"',
|
'"__LIST_OF_CLASSES__"',
|
||||||
Object.values(themeClassNames)
|
Object.values(themeClassNames)
|
||||||
.map((t: string) => `"${t}"`)
|
.map((t) => `"${t}"`)
|
||||||
.join(",")
|
.join(",")
|
||||||
);
|
);
|
||||||
|
|
||||||
// minify the final code, a bit hacky but this is ONLY done at build-time, so uglify-js is never bundled or sent to
|
// minify the final code, a bit hacky but this is ONLY done at build-time, so uglify-js is never bundled or sent to
|
||||||
// the browser to execute:
|
// the browser to execute:
|
||||||
const minified = minify(`(${functionString})()`, {
|
return minify(`(${functionString})()`, {
|
||||||
toplevel: true,
|
toplevel: true,
|
||||||
compress: {
|
compress: {
|
||||||
negate_iife: false,
|
negate_iife: false,
|
||||||
@ -27,6 +28,7 @@ const ThemeScript = () => {
|
|||||||
bare_returns: true,
|
bare_returns: true,
|
||||||
},
|
},
|
||||||
}).code;
|
}).code;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// the script tag injected manually into `<head>` in _document.tsx.
|
// the script tag injected manually into `<head>` in _document.tsx.
|
||||||
// even though it's the proper method, using next/script with `strategy="beforeInteractive"` still causes flash of
|
// even though it's the proper method, using next/script with `strategy="beforeInteractive"` still causes flash of
|
||||||
@ -36,10 +38,10 @@ const ThemeScript = () => {
|
|||||||
key="restore-theme"
|
key="restore-theme"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
// make it an IIFE:
|
// make it an IIFE:
|
||||||
__html: `(function(){${minified}})();`,
|
__html: `(function(){${minified}})()`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ThemeScript);
|
export default ThemeScript;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable no-var */
|
/* eslint-disable no-empty, no-var, prefer-destructuring */
|
||||||
|
|
||||||
// this function is converted to a string verbatim, substitutions are made to insert dynamic values, minified, and then
|
// this function is converted to a string verbatim, substitutions are made to insert dynamic values, minified, and then
|
||||||
// finally exported as an inline `<script>` tag in ThemeScript.tsx for pages/_document.tsx to use.
|
// finally exported as an inline `<script>` tag in ThemeScript.tsx for _document.tsx to use.
|
||||||
export const clientScript = () => {
|
export const clientScript = () => {
|
||||||
// `try/catch` in case I messed something up here bigly... (will default to light theme)
|
// `try/catch` in case I messed something up here bigly... (will default to light theme)
|
||||||
try {
|
try {
|
||||||
@ -9,9 +9,9 @@ export const clientScript = () => {
|
|||||||
var pref = localStorage.getItem("__STORAGE_KEY__");
|
var pref = localStorage.getItem("__STORAGE_KEY__");
|
||||||
// map of theme -> classname:
|
// map of theme -> classname:
|
||||||
var classNames = "__CLASS_NAMES__";
|
var classNames = "__CLASS_NAMES__";
|
||||||
// the list of <html>'s current class(es), from which `classNames` are removed to start fresh
|
// the list of <html>'s current class(es)...
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
var classList = document.documentElement.classList;
|
var classList = document.documentElement.classList;
|
||||||
|
// ...from which `classNames` are removed to start fresh:
|
||||||
classList.remove("__LIST_OF_CLASSES__");
|
classList.remove("__LIST_OF_CLASSES__");
|
||||||
|
|
||||||
if (pref === "light" || pref === "dark") {
|
if (pref === "light" || pref === "dark") {
|
||||||
@ -23,5 +23,5 @@ export const clientScript = () => {
|
|||||||
var prefersDark = window.matchMedia(darkQuery);
|
var prefersDark = window.matchMedia(darkQuery);
|
||||||
classList.add(classNames[prefersDark.media !== darkQuery || prefersDark.matches ? "dark" : "light"]);
|
classList.add(classNames[prefersDark.media !== darkQuery || prefersDark.matches ? "dark" : "light"]);
|
||||||
}
|
}
|
||||||
} catch (error) {} // eslint-disable-line no-empty
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
5
lib/helpers/format-number.ts
Normal file
5
lib/helpers/format-number.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { siteLocale } from "../config";
|
||||||
|
|
||||||
|
// adds thousands separator
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
|
||||||
|
export const commafy = Intl.NumberFormat(siteLocale, { useGrouping: true }).format;
|
@ -54,6 +54,7 @@
|
|||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"react": "18.0.0",
|
"react": "18.0.0",
|
||||||
"react-dom": "18.0.0",
|
"react-dom": "18.0.0",
|
||||||
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-gist": "^1.2.4",
|
"react-gist": "^1.2.4",
|
||||||
"react-innertext": "^1.1.5",
|
"react-innertext": "^1.1.5",
|
||||||
"react-intersection-observer": "^9.0.0",
|
"react-intersection-observer": "^9.0.0",
|
||||||
|
@ -2,7 +2,6 @@ import { NextSeo } from "next-seo";
|
|||||||
import Content from "../../components/Content";
|
import Content from "../../components/Content";
|
||||||
import NotesList, { NotesListProps } from "../../components/NotesList";
|
import NotesList, { NotesListProps } from "../../components/NotesList";
|
||||||
import { getAllNotes } from "../../lib/helpers/parse-notes";
|
import { getAllNotes } from "../../lib/helpers/parse-notes";
|
||||||
import { formatDateTZ } from "../../lib/helpers/format-date";
|
|
||||||
import type { GetStaticProps } from "next";
|
import type { GetStaticProps } from "next";
|
||||||
|
|
||||||
const Notes = ({ notesByYear }: NotesListProps) => (
|
const Notes = ({ notesByYear }: NotesListProps) => (
|
||||||
@ -26,7 +25,7 @@ export const getStaticProps: GetStaticProps = async () => {
|
|||||||
const notesByYear: NotesListProps["notesByYear"] = {};
|
const notesByYear: NotesListProps["notesByYear"] = {};
|
||||||
|
|
||||||
getAllNotes().map((note) => {
|
getAllNotes().map((note) => {
|
||||||
const year = formatDateTZ(note.date, "yyyy");
|
const year = new Date(note.date).getUTCFullYear();
|
||||||
(notesByYear[year] || (notesByYear[year] = [])).push(note);
|
(notesByYear[year] || (notesByYear[year] = [])).push(note);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { NextSeo } from "next-seo";
|
import { NextSeo } from "next-seo";
|
||||||
|
import Terminal from "../components/Terminal";
|
||||||
import { styled } from "../lib/styles/stitches.config";
|
import { styled } from "../lib/styles/stitches.config";
|
||||||
|
|
||||||
// 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
|
||||||
@ -56,7 +58,11 @@ const Y2K = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Wallpaper style={{ backgroundImage: wallpaperUrl ? `url(${wallpaperUrl})` : "" }}>
|
<Wallpaper style={{ backgroundImage: wallpaperUrl ? `url(${wallpaperUrl})` : "" }}>
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={<Terminal>Oh dear, it looks like something's gone VERY wrong. Sorry about that!</Terminal>}
|
||||||
|
>
|
||||||
<VNC server={SOCKET_PROXY} />
|
<VNC server={SOCKET_PROXY} />
|
||||||
|
</ErrorBoundary>
|
||||||
</Wallpaper>
|
</Wallpaper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -982,7 +982,7 @@
|
|||||||
core-js-pure "^3.20.2"
|
core-js-pure "^3.20.2"
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4":
|
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4":
|
||||||
version "7.17.9"
|
version "7.17.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
|
||||||
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
|
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
|
||||||
@ -5105,6 +5105,13 @@ react-dom@18.0.0:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.21.0"
|
scheduler "^0.21.0"
|
||||||
|
|
||||||
|
react-error-boundary@^3.1.4:
|
||||||
|
version "3.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
|
||||||
|
integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.5"
|
||||||
|
|
||||||
react-fast-compare@^2.0.1:
|
react-fast-compare@^2.0.1:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user