1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 06:45:23 -04:00

add stylelint

This commit is contained in:
Jake Jarvis 2025-03-12 09:44:06 -04:00
parent 8e89701453
commit 5b2caf4a96
Signed by: jake
SSH Key Fingerprint: SHA256:nCkvAjYA6XaSPUqc4TfbBQTpzr8Xj7ritg/sGInCdkc
21 changed files with 748 additions and 155 deletions

View File

@ -20,10 +20,12 @@
"typescript.tsdk": "node_modules/typescript/lib" "typescript.tsdk": "node_modules/typescript/lib"
}, },
"extensions": [ "extensions": [
"EditorConfig.EditorConfig",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"prisma.prisma", "prisma.prisma",
"unifiedjs.vscode-mdx" "unifiedjs.vscode-mdx",
"esbenp.prettier-vscode",
"stylelint.vscode-stylelint"
] ]
} }
}, },

View File

@ -1,6 +1,4 @@
/** /** @type {import("prettier").Config} */
* @type {import("prettier").Config}
*/
const config = { const config = {
singleQuote: false, singleQuote: false,
jsxSingleQuote: false, jsxSingleQuote: false,

19
.stylelintrc.mjs Normal file
View File

@ -0,0 +1,19 @@
/* eslint-disable import/no-anonymous-default-export */
/** @type {import("stylelint").Config} */
export default {
extends: ["stylelint-config-standard", "stylelint-config-css-modules"],
rules: {
"selector-class-pattern": null,
"custom-property-pattern": null,
"media-feature-range-notation": null,
"rule-empty-line-before": [
"always-multi-line",
{
except: ["after-single-line-comment"],
ignore: ["inside-block"],
},
],
"color-hex-length": "long",
},
};

View File

@ -58,6 +58,7 @@
.result.success { .result.success {
color: var(--colors-success); color: var(--colors-success);
} }
.result.error { .result.error {
color: var(--colors-error); color: var(--colors-error);
} }

View File

@ -35,11 +35,13 @@
white-space: nowrap; white-space: nowrap;
margin-right: 0.75em; margin-right: 0.75em;
} }
.meta .metaTag:before {
.meta .metaTag::before {
content: "\0023"; /* cosmetically hashtagify tags */ content: "\0023"; /* cosmetically hashtagify tags */
padding-right: 0.125em; padding-right: 0.125em;
color: var(--colors-light); color: var(--colors-light);
} }
.meta .metaTag:last-of-type { .meta .metaTag:last-of-type {
margin-right: 0; margin-right: 0;
} }

View File

@ -7,6 +7,7 @@
.section:first-of-type { .section:first-of-type {
margin-top: 0.25em; margin-top: 0.25em;
} }
.section:last-of-type { .section:last-of-type {
margin-bottom: 0.25em; margin-bottom: 0.25em;
} }

View File

@ -60,6 +60,7 @@
30% { 30% {
transform: rotate(0deg); transform: rotate(0deg);
} }
/* pause for ~9 out of 10 seconds */ /* pause for ~9 out of 10 seconds */
100% { 100% {
transform: rotate(0deg); transform: rotate(0deg);

View File

@ -11,7 +11,7 @@
.card { .card {
flex-grow: 1; flex-grow: 1;
width: 370px; width: 370px;
padding: 1.2em 1.2em 0.8em 1.2em; padding: 1.2em 1.2em 0.8em;
border: 1px solid var(--colors-kindaLight); border: 1px solid var(--colors-kindaLight);
border-radius: var(--radii-corner); border-radius: var(--radii-corner);
font-size: 0.9em; font-size: 0.9em;
@ -32,7 +32,6 @@
.card .meta { .card .meta {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: baseline;
} }
.card .metaItem { .card .metaItem {
@ -51,7 +50,7 @@
} }
.card .metaIcon { .card .metaIcon {
display: inline; display: inline-block;
width: 1.25em; width: 1.25em;
height: 1.25em; height: 1.25em;
vertical-align: -0.25em; vertical-align: -0.25em;
@ -61,9 +60,10 @@
.card .metaLanguage { .card .metaLanguage {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: 1.15em; width: 1.1em;
height: 1.15em; height: 1.1em;
line-height: 1;
margin-right: 0.5em; margin-right: 0.5em;
border-radius: 50%; border-radius: 50%;
vertical-align: text-top; vertical-align: -0.2em;
} }

View File

@ -114,11 +114,7 @@ export default async function Page() {
<div className={styles.grid}> <div className={styles.grid}>
{repos?.map((repo) => ( {repos?.map((repo) => (
<div key={repo.name} className={styles.card}> <div key={repo.name} className={styles.card}>
<Link <Link href={repo.url} className={styles.name}>
// @ts-ignore
href={repo.url}
className={styles.name}
>
{repo.name} {repo.name}
</Link> </Link>
@ -137,7 +133,6 @@ export default async function Page() {
{repo.stars && repo.stars > 0 && ( {repo.stars && repo.stars > 0 && (
<div className={styles.metaItem}> <div className={styles.metaItem}>
<Link <Link
// @ts-ignore
href={`${repo.url}/stargazers`} href={`${repo.url}/stargazers`}
title={`${commaNumber(repo.stars)} ${repo.stars === 1 ? "star" : "stars"}`} title={`${commaNumber(repo.stars)} ${repo.stars === 1 ? "star" : "stars"}`}
plain plain
@ -152,7 +147,6 @@ export default async function Page() {
{repo.forks && repo.forks > 0 && ( {repo.forks && repo.forks > 0 && (
<div className={styles.metaItem}> <div className={styles.metaItem}>
<Link <Link
// @ts-ignore
href={`${repo.url}/network/members`} href={`${repo.url}/network/members`}
title={`${commaNumber(repo.forks)} ${repo.forks === 1 ? "fork" : "forks"}`} title={`${commaNumber(repo.forks)} ${repo.forks === 1 ? "fork" : "forks"}`}
plain plain

View File

@ -1,7 +1,7 @@
:root { :root {
--colors-backgroundInner: #ffffff; --colors-backgroundInner: #ffffff;
--colors-backgroundOuter: #fcfcfc; --colors-backgroundOuter: #fcfcfc;
--colors-backgroundHeader: rgba(252, 252, 252, 0.7); --colors-backgroundHeader: rgb(252 252 252 / 70%);
--colors-text: #202020; --colors-text: #202020;
--colors-mediumDark: #515151; --colors-mediumDark: #515151;
--colors-medium: #5e5e5e; --colors-medium: #5e5e5e;
@ -11,7 +11,7 @@
--colors-superLight: #f4f4f4; --colors-superLight: #f4f4f4;
--colors-superDuperLight: #fbfbfb; --colors-superDuperLight: #fbfbfb;
--colors-link: #0e6dc2; --colors-link: #0e6dc2;
--colors-linkUnderline: rgba(14, 109, 194, 0.4); --colors-linkUnderline: rgb(14 109 194 / 40%);
--colors-success: #44a248; --colors-success: #44a248;
--colors-error: #ff1b1b; --colors-error: #ff1b1b;
--colors-warning: #f78200; --colors-warning: #f78200;
@ -33,7 +33,7 @@
[data-theme="dark"] { [data-theme="dark"] {
--colors-backgroundInner: #1e1e1e; --colors-backgroundInner: #1e1e1e;
--colors-backgroundOuter: #252525; --colors-backgroundOuter: #252525;
--colors-backgroundHeader: rgba(37, 37, 37, 0.85); --colors-backgroundHeader: rgb(37 37 37 / 85%);
--colors-text: #f1f1f1; --colors-text: #f1f1f1;
--colors-mediumDark: #d7d7d7; --colors-mediumDark: #d7d7d7;
--colors-medium: #b1b1b1; --colors-medium: #b1b1b1;
@ -43,7 +43,7 @@
--colors-superLight: #272727; --colors-superLight: #272727;
--colors-superDuperLight: #1f1f1f; --colors-superDuperLight: #1f1f1f;
--colors-link: #88c7ff; --colors-link: #88c7ff;
--colors-linkUnderline: rgba(136, 199, 255, 0.4); --colors-linkUnderline: rgb(136 199 255 / 40%);
--colors-success: #78df55; --colors-success: #78df55;
--colors-error: #ff5151; --colors-error: #ff5151;
--colors-warning: #f2b702; --colors-warning: #f2b702;

View File

@ -36,6 +36,7 @@
.codeBlock.highlight :global(.token.cdata) { .codeBlock.highlight :global(.token.cdata) {
color: var(--colors-codeComment); color: var(--colors-codeComment);
} }
.codeBlock.highlight :global(.token.delimiter), .codeBlock.highlight :global(.token.delimiter),
.codeBlock.highlight :global(.token.boolean), .codeBlock.highlight :global(.token.boolean),
.codeBlock.highlight :global(.token.keyword), .codeBlock.highlight :global(.token.keyword),
@ -46,11 +47,13 @@
.codeBlock.highlight :global(.token.url) { .codeBlock.highlight :global(.token.url) {
color: var(--colors-codeKeyword); color: var(--colors-codeKeyword);
} }
.codeBlock.highlight :global(.token.tag), .codeBlock.highlight :global(.token.tag),
.codeBlock.highlight :global(.token.builtin), .codeBlock.highlight :global(.token.builtin),
.codeBlock.highlight :global(.token.regex) { .codeBlock.highlight :global(.token.regex) {
color: var(--colors-codeNamespace); color: var(--colors-codeNamespace);
} }
.codeBlock.highlight :global(.token.property), .codeBlock.highlight :global(.token.property),
.codeBlock.highlight :global(.token.constant), .codeBlock.highlight :global(.token.constant),
.codeBlock.highlight :global(.token.variable), .codeBlock.highlight :global(.token.variable),
@ -60,29 +63,37 @@
.codeBlock.highlight :global(.token.char) { .codeBlock.highlight :global(.token.char) {
color: var(--colors-codeVariable); color: var(--colors-codeVariable);
} }
.codeBlock.highlight :global(.token.literal-property), .codeBlock.highlight :global(.token.literal-property),
.codeBlock.highlight :global(.token.attr-name) { .codeBlock.highlight :global(.token.attr-name) {
color: var(--colors-codeAttribute); color: var(--colors-codeAttribute);
} }
.codeBlock.highlight :global(.token.function) { .codeBlock.highlight :global(.token.function) {
color: var(--colors-codeLiteral); color: var(--colors-codeLiteral);
} }
.codeBlock.highlight :global(.token.tag .punctuation), .codeBlock.highlight :global(.token.tag .punctuation),
.codeBlock.highlight :global(.token.attr-value .punctuation) { .codeBlock.highlight :global(.token.attr-value .punctuation) {
color: var(--colors-codePunctuation); color: var(--colors-codePunctuation);
} }
.codeBlock.highlight :global(.token.inserted) { .codeBlock.highlight :global(.token.inserted) {
color: var(--colors-codeAddition); color: var(--colors-codeAddition);
} }
.codeBlock.highlight :global(.token.deleted) { .codeBlock.highlight :global(.token.deleted) {
color: var(--colors-codeDeletion); color: var(--colors-codeDeletion);
} }
.codeBlock.highlight :global(.token.url) { .codeBlock.highlight :global(.token.url) {
text-decoration: underline; text-decoration: underline;
} }
.codeBlock.highlight :global(.token.bold) { .codeBlock.highlight :global(.token.bold) {
font-weight: bold; font-weight: bold;
} }
.codeBlock.highlight :global(.token.italic) { .codeBlock.highlight :global(.token.italic) {
font-style: italic; font-style: italic;
} }
@ -91,9 +102,21 @@
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
padding: 0.65em; height: 3em;
background-color: var(--colors-backgroundInner); width: 3em;
color: var(--colors-mediumDark);
border: 1px solid var(--colors-kindaLight); border: 1px solid var(--colors-kindaLight);
border-top-right-radius: var(--radii-corner); border-top-right-radius: var(--radii-corner);
border-bottom-left-radius: var(--radii-corner); border-bottom-left-radius: var(--radii-corner);
background-color: var(--colors-backgroundHeader);
backdrop-filter: saturate(180%) blur(5px);
}
.cornerCopyButton > svg {
vertical-align: middle;
}
.cornerCopyButton:hover,
.cornerCopyButton:focus-visible {
color: var(--colors-link);
} }

View File

@ -1,20 +0,0 @@
.button {
color: var(--colors-mediumDark);
line-height: 1px;
cursor: pointer;
}
.button:hover,
.button:focus-visible {
color: var(--colors-link);
}
.button.copied {
color: var(--colors-success) !important;
}
.icon {
width: 1.25em;
height: 1.25em;
vertical-align: -0.3em;
}

View File

@ -3,24 +3,18 @@
import { forwardRef, useState, useEffect } from "react"; import { forwardRef, useState, useEffect } from "react";
import innerText from "react-innertext"; import innerText from "react-innertext";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import clsx from "clsx";
import { ClipboardIcon, CheckIcon } from "lucide-react"; import { ClipboardIcon, CheckIcon } from "lucide-react";
import type { ReactNode, Ref, ComponentPropsWithoutRef, ElementRef, MouseEventHandler } from "react"; import type { ReactNode, Ref, ComponentPropsWithoutRef, ComponentRef, MouseEventHandler } from "react";
import styles from "./CopyButton.module.css";
export type CopyButtonProps = ComponentPropsWithoutRef<"button"> & { export type CopyButtonProps = ComponentPropsWithoutRef<"button"> & {
source: string | ReactNode; source: string | ReactNode;
timeout?: number; timeout?: number;
}; };
const CopyButton = ( const CopyButton = ({ source, timeout = 2000, style, ...rest }: CopyButtonProps, ref: Ref<ComponentRef<"button">>) => {
{ source, timeout = 2000, className, ...rest }: CopyButtonProps,
ref: Ref<ElementRef<"button">>
) => {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const handleCopy: MouseEventHandler<ElementRef<"button">> = (e) => { const handleCopy: MouseEventHandler<ComponentRef<"button">> = (e) => {
// prevent unintentional double-clicks by unfocusing button // prevent unintentional double-clicks by unfocusing button
e.currentTarget.blur(); e.currentTarget.blur();
@ -54,13 +48,13 @@ const CopyButton = (
aria-label="Copy to clipboard" aria-label="Copy to clipboard"
onClick={handleCopy} onClick={handleCopy}
disabled={copied} disabled={copied}
className={clsx(styles.button, copied && styles.copied, className)} style={{ cursor: copied ? "default" : "pointer", ...style }}
{...rest} {...rest}
> >
{copied ? ( {copied ? (
<CheckIcon size="1.25em" className={styles.icon} /> <CheckIcon size="1.25em" style={{ stroke: "var(--colors-success)" }} />
) : ( ) : (
<ClipboardIcon size="1.25em" className={styles.icon} /> <ClipboardIcon size="1.25em" />
)} )}
</button> </button>
); );

View File

@ -66,6 +66,7 @@
8% { 8% {
transform: scale(1); transform: scale(1);
} }
/* pause for ~9 out of 10 seconds */ /* pause for ~9 out of 10 seconds */
100% { 100% {
transform: scale(1); transform: scale(1);

View File

@ -6,7 +6,6 @@ import MenuItem from "../MenuItem";
import ThemeToggle from "../ThemeToggle"; import ThemeToggle from "../ThemeToggle";
import { menuItems } from "../../lib/config/menu"; import { menuItems } from "../../lib/config/menu";
import type { ComponentPropsWithoutRef } from "react"; import type { ComponentPropsWithoutRef } from "react";
import type { LucideIcon } from "lucide-react";
import styles from "./Menu.module.css"; import styles from "./Menu.module.css";
@ -29,7 +28,10 @@ const Menu = ({ className, ...rest }: MenuProps) => {
})} })}
<li className={styles.menuItem}> <li className={styles.menuItem}>
<MenuItem icon={ThemeToggle as LucideIcon} /> <MenuItem
// @ts-expect-error
icon={ThemeToggle}
/>
</li> </li>
</ul> </ul>
); );

View File

@ -1,3 +1,4 @@
/* stylelint-disable-next-line selector-type-no-unknown */
.wrapper lite-youtube { .wrapper lite-youtube {
margin: 0 auto; margin: 0 auto;
} }

View File

@ -16,7 +16,8 @@ const compat = new FlatCompat({
export default [ export default [
{ ignores: ["README.md", ".next", ".vercel", "node_modules"] }, { ignores: ["README.md", ".next", ".vercel", "node_modules"] },
...compat.config({ ...compat.config({
extends: ["eslint:recommended", "next/core-web-vitals", "next/typescript"], plugins: ["css-modules"],
extends: ["eslint:recommended", "next/core-web-vitals", "next/typescript", "plugin:css-modules/recommended"],
}), }),
...eslintCustomConfig, ...eslintCustomConfig,
eslintPluginPrettierRecommended, eslintPluginPrettierRecommended,

View File

@ -77,7 +77,7 @@ const nextConfig: NextConfig = {
// NOTE: don't remove this, it ensures de-AMPing the site hasn't offended our google overlords too badly! // NOTE: don't remove this, it ensures de-AMPing the site hasn't offended our google overlords too badly!
// https://developers.google.com/search/docs/advanced/experience/remove-amp#remove-only-amp // https://developers.google.com/search/docs/advanced/experience/remove-amp#remove-only-amp
{ source: "/notes/:slug/amp.html", destination: "/notes/:slug/", permanent: true }, { source: "/notes/:slug/amp.html", destination: "/notes/:slug", permanent: true },
// mastodon via subdomain: // mastodon via subdomain:
// https://docs.joinmastodon.org/admin/config/#web_domain // https://docs.joinmastodon.org/admin/config/#web_domain
@ -107,32 +107,29 @@ const nextConfig: NextConfig = {
permanent: true, permanent: true,
}, },
// google search console has tons of 404s for images prefixed with /public... why? no clue.
{ source: "/public/static/:path*", destination: "/static/:path*", permanent: true },
// remnants of previous sites/CMSes: // remnants of previous sites/CMSes:
{ source: "/index.xml", destination: "/feed.xml", permanent: true }, { source: "/index.xml", destination: "/feed.xml", permanent: true },
{ source: "/feed", destination: "/feed.xml", permanent: true }, { source: "/feed", destination: "/feed.xml", permanent: true },
{ source: "/rss", destination: "/feed.xml", permanent: true }, { source: "/rss", destination: "/feed.xml", permanent: true },
{ source: "/blog/:path*", destination: "/notes/", permanent: true }, { source: "/blog/:path*", destination: "/notes", permanent: true },
{ source: "/archives/:path*", destination: "/notes/", permanent: true }, { source: "/archives/:path*", destination: "/notes", permanent: true },
{ source: "/resume", destination: "/static/resume.pdf", permanent: false }, { source: "/resume", destination: "/static/resume.pdf", permanent: false },
{ source: "/resume.pdf", destination: "/static/resume.pdf", permanent: false }, { source: "/resume.pdf", destination: "/static/resume.pdf", permanent: false },
// WordPress permalinks: // WordPress permalinks:
{ {
source: "/2016/02/28/millenial-with-hillary-clinton", source: "/2016/02/28/millenial-with-hillary-clinton",
destination: "/notes/millenial-with-hillary-clinton/", destination: "/notes/millenial-with-hillary-clinton",
permanent: true, permanent: true,
}, },
{ {
source: "/2018/12/04/how-to-shrink-linux-virtual-disk-vmware", source: "/2018/12/04/how-to-shrink-linux-virtual-disk-vmware",
destination: "/notes/how-to-shrink-linux-virtual-disk-vmware/", destination: "/notes/how-to-shrink-linux-virtual-disk-vmware",
permanent: true, permanent: true,
}, },
{ {
source: "/2018/12/10/cool-bash-tricks-for-your-terminal-dotfiles", source: "/2018/12/10/cool-bash-tricks-for-your-terminal-dotfiles",
destination: "/notes/cool-bash-tricks-for-your-terminal-dotfiles/", destination: "/notes/cool-bash-tricks-for-your-terminal-dotfiles",
permanent: true, permanent: true,
}, },
], ],

View File

@ -9,7 +9,6 @@
"email": "jake@jarv.is", "email": "jake@jarv.is",
"url": "https://github.com/jakejarvis" "url": "https://github.com/jakejarvis"
}, },
"type": "module",
"scripts": { "scripts": {
"dev": "next dev -H 0.0.0.0", "dev": "next dev -H 0.0.0.0",
"build": "next build", "build": "next build",
@ -23,9 +22,9 @@
"@giscus/react": "^3.1.0", "@giscus/react": "^3.1.0",
"@mdx-js/loader": "^3.1.0", "@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0", "@mdx-js/react": "^3.1.0",
"@next/bundle-analyzer": "15.3.0-canary.0", "@next/bundle-analyzer": "15.3.0-canary.1",
"@next/mdx": "15.3.0-canary.0", "@next/mdx": "15.3.0-canary.1",
"@next/third-parties": "15.3.0-canary.0", "@next/third-parties": "15.3.0-canary.1",
"@octokit/graphql": "^8.2.1", "@octokit/graphql": "^8.2.1",
"@octokit/graphql-schema": "^15.26.0", "@octokit/graphql-schema": "^15.26.0",
"@prisma/client": "^6.5.0", "@prisma/client": "^6.5.0",
@ -37,7 +36,7 @@
"feed": "^4.2.2", "feed": "^4.2.2",
"lucide-react": "0.479.0", "lucide-react": "0.479.0",
"modern-normalize": "^3.0.1", "modern-normalize": "^3.0.1",
"next": "15.3.0-canary.0", "next": "15.3.0-canary.1",
"obj-str": "^1.1.0", "obj-str": "^1.1.0",
"p-map": "^7.0.3", "p-map": "^7.0.3",
"p-memoize": "^7.1.1", "p-memoize": "^7.1.1",
@ -78,8 +77,9 @@
"@types/react-is": "^19.0.0", "@types/react-is": "^19.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^9.22.0", "eslint": "^9.22.0",
"eslint-config-next": "15.3.0-canary.0", "eslint-config-next": "15.3.0-canary.1",
"eslint-config-prettier": "^10.1.1", "eslint-config-prettier": "^10.1.1",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-mdx": "^3.2.0", "eslint-plugin-mdx": "^3.2.0",
@ -91,6 +91,10 @@
"prisma": "^6.5.0", "prisma": "^6.5.0",
"schema-dts": "^1.1.5", "schema-dts": "^1.1.5",
"simple-git-hooks": "^2.11.1", "simple-git-hooks": "^2.11.1",
"stylelint": "^16.15.0",
"stylelint-config-css-modules": "^4.4.0",
"stylelint-config-recommended": "^15.0.0",
"stylelint-config-standard": "^37.0.0",
"typescript": "5.8.2" "typescript": "5.8.2"
}, },
"optionalDependencies": { "optionalDependencies": {
@ -112,6 +116,7 @@
"eslint" "eslint"
], ],
"*.css": [ "*.css": [
"stylelint",
"prettier --check" "prettier --check"
] ]
}, },

714
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@ -24,13 +20,6 @@
} }
] ]
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"next-env.d.ts", "exclude": ["node_modules"]
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }