Update/fix some web stuff. (#1429)

* Update/fix some web stuff.

Also added lefthook with some linting config.

* Add note about lefthook.

* Enable sort imports eslint rule.

* This statement is untrue

* Remove cypress video, add to gitignore

* Add web CI action

* Set the right dir here

* Try this
This commit is contained in:
Brenden Matthews 2023-02-25 20:12:11 -05:00 committed by GitHub
parent 1296ab8517
commit bd5b7c87fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1785 additions and 1047 deletions

27
.github/workflows/web.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Web CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18
uses: actions/setup-node@v3
with:
node-version: 18
cache: npm
cache-dependency-path: '**/package-lock.json'
- run: npm ci
working-directory: web
- run: npm run lint
working-directory: web
- run: npm run build --if-present
working-directory: web
- run: npm run e2e:headless
working-directory: web

View File

@ -4,4 +4,4 @@ module.exports = {
singleQuote: true, singleQuote: true,
tabWidth: 2, tabWidth: 2,
useTabs: false, useTabs: false,
}; }

View File

@ -22,7 +22,16 @@ _Patches submitted in issues, email, or elsewhere may be ignored._
## Coding Style ## Coding Style
Code should be formatted using `clang-format`. By configuring Conky with `cmake -DCHECK_CODE_QUALITY=ON`, you will be able to run `make clang-format` to automatically format code. If code in your PR is not formatted according to [`.clang-format`](.clang-format), the checks will not pass. Code should be formatted using `clang-format`. By configuring Conky with `cmake -DCHECK_CODE_QUALITY=ON`, you will be able to run `make clang-format` to automatically format code.
## Git hooks
To make life easier, you can use
[lefthook](https://github.com/evilmartians/lefthook) to handle some basic
linting with a pre-commit hook, as defined in [lefthook.yml](lefthook.yml).
Follow the [installation guide for
lefhook](https://github.com/evilmartians/lefthook/blob/master/docs/install.md),
then run `lefthook install` to enable the hooks.
## Unit Testing ## Unit Testing

19
lefthook.yml Normal file
View File

@ -0,0 +1,19 @@
pre-commit:
commands:
web-linter:
parallel: true
run: |
npx eslint --fix {staged_files} \
&& git add {staged_files}
root: web/
glob: '*.{ts,tsx,js,jsx}'
cpp-linter:
run: |
clang-format -i {staged_files} \
&& git add {staged_files}
glob: '*.{c,cc,cxx,h,cpp}'
misc-linter:
run: |
npx prettier --write {staged_files} \
&& git add {staged_files}
glob: '*.{md,json,yml,yaml}'

View File

@ -1,7 +1,16 @@
{ {
"extends": ["next/core-web-vitals", "prettier"], "extends": [
"settings": { "eslint:recommended",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals",
"prettier"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"root": true,
"settings": {
"mdx/code-blocks": true, "mdx/code-blocks": true,
"mdx/language-mapper": {} "mdx/language-mapper": {},
"sort-imports": "error"
} }
} }

3
web/.gitignore vendored
View File

@ -39,3 +39,6 @@ yarn-error.log*
# IDEs # IDEs
.vscode/ .vscode/
# Cypress videos
cypress/videos

View File

@ -1,21 +0,0 @@
import Link from 'next/link'
import { Url } from 'url'
interface CustomLinkProps {
as: Url
href: Url
}
export default function CustomLink({
as,
href,
...otherProps
}: CustomLinkProps) {
return (
<>
<Link as={as} href={href}>
<a {...otherProps} />
</Link>
</>
)
}

View File

@ -32,9 +32,7 @@ export default function Docs({ docs, braces, assign }: DocsProps) {
<div className="flex"> <div className="flex">
<div className="px-2 py-3"> <div className="px-2 py-3">
<Link href={`#${doc.name}`}> <Link href={`#${doc.name}`}>
<a> <LinkIcon size={20} />
<LinkIcon size={20} />
</a>
</Link> </Link>
</div> </div>
<div className="flex-col p-1"> <div className="flex-col p-1">

View File

@ -13,7 +13,8 @@ type HeaderProps = {
import * as React from 'react' import * as React from 'react'
import Search from './Search' import Search from './Search'
import { SearchIndex } from '../utils/search' import { SearchIndex, SearchItem } from '../utils/search'
import Fuse from 'fuse.js'
interface NavLinkProps { interface NavLinkProps {
href: string href: string
@ -26,12 +27,11 @@ const NavLink: React.FunctionComponent<NavLinkProps> = (props) => {
? 'bg-rose-100 dark:bg-rose-900' ? 'bg-rose-100 dark:bg-rose-900'
: '' : ''
return ( return (
<Link href={props.href}> <Link
<a href={props.href}
className={`m-0.5 p-1 self-end hover:ring-1 ring-black dark:ring-white hover:bg-rose-300 dark:hover:bg-rose-700 ${bg} rounded`} className={`m-0.5 p-1 self-end hover:ring-1 ring-black dark:ring-white hover:bg-rose-300 dark:hover:bg-rose-700 ${bg} rounded`}
> >
{props.name} {props.name}
</a>
</Link> </Link>
) )
} }
@ -43,14 +43,21 @@ export default function Header({
searchIndex, searchIndex,
}: HeaderProps) { }: HeaderProps) {
const router = useRouter() const router = useRouter()
const fuse = React.useMemo(() => {
const options: Fuse.IFuseOptions<SearchItem> = {}
return new Fuse(
searchIndex.list,
options,
Fuse.parseIndex(searchIndex.index)
)
}, [searchIndex])
return ( return (
<div className="border-b-1 backdrop-blur-lg bg-white dark:bg-black bg-opacity-20 dark:bg-opacity-20 transition"> <div className="border-b-1 backdrop-blur-lg bg-white dark:bg-black bg-opacity-20 dark:bg-opacity-20 transition">
<header className="max-w-3xl mx-auto m-0 p-1 grow flex w-full"> <header className="max-w-3xl mx-auto m-0 p-1 grow flex w-full">
<h1 className="px-2 text-3xl dark:text-white self-end"> <h1 className="px-2 text-3xl dark:text-white self-end">
<Link href="/"> <Link href="/">
<a> <strong>{name}</strong>
<strong>{name}</strong>
</a>
</Link> </Link>
</h1> </h1>
{router.asPath != '/' && ( {router.asPath != '/' && (
@ -61,7 +68,7 @@ export default function Header({
</div> </div>
)} )}
<LineChart width={380} height={40} darkMode={darkMode} /> <LineChart width={380} height={40} darkMode={darkMode} />
<Search index={searchIndex} /> <Search fuse={fuse} />
<div className="flex"> <div className="flex">
<div className="border-r mx-1 px-1 border-slate-700"> <div className="border-r mx-1 px-1 border-slate-700">
<a href="https://github.com/brndnmtthws/conky"> <a href="https://github.com/brndnmtthws/conky">

View File

@ -34,7 +34,7 @@ export default function Layout({ children, searchIndex }: LayoutProps) {
}, [darkMode]) }, [darkMode])
useEffect(() => { useEffect(() => {
var darkQuery = window.matchMedia('(prefers-color-scheme: dark)') const darkQuery = window.matchMedia('(prefers-color-scheme: dark)')
darkQuery.onchange = (e) => { darkQuery.onchange = (e) => {
if (e.matches) { if (e.matches) {

View File

@ -1,4 +1,4 @@
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef } from 'react'
import * as d3 from 'd3' import * as d3 from 'd3'
import { random } from 'colord' import { random } from 'colord'
@ -40,12 +40,12 @@ function fetchData() {
const data = JSON.parse(storedData) const data = JSON.parse(storedData)
return data || [] return data || []
} }
var data = fetchData() const data = fetchData()
export const LineChart = ({ width, height, darkMode }: LineChartProps) => { export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
const timerRef = useRef<NodeJS.Timeout>() const timerRef = useRef<NodeJS.Timeout>()
const svgRef = useRef(null) const svgRef = useRef(null)
const [stroke, setStroke] = useState(random()) const stroke = useMemo(() => random(), [])
useEffect(() => { useEffect(() => {
const doIt = (data: DataPoint[]) => { const doIt = (data: DataPoint[]) => {
@ -59,7 +59,7 @@ export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
.range([height, 0]) .range([height, 0])
// X axis // X axis
const [xMin, xMax] = d3.extent(data, (d) => d.x) const [, xMax] = d3.extent(data, (d) => d.x)
const xScale = d3 const xScale = d3
.scaleLinear() .scaleLinear()
.domain([(xMax || 0) - width, xMax || 0]) .domain([(xMax || 0) - width, xMax || 0])
@ -83,7 +83,7 @@ export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
.attr('x', (d) => xScale(d.x)) .attr('x', (d) => xScale(d.x))
.attr('y', (d) => yScale(d.y)) .attr('y', (d) => yScale(d.y))
.attr('height', (d) => yScale((max || 0) - d.y)) .attr('height', (d) => yScale((max || 0) - d.y))
.attr('width', (d) => 1), .attr('width', () => 1),
(update) => (update) =>
update update
.transition() .transition()
@ -95,7 +95,7 @@ export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
.attr('x', (d) => xScale(d.x)) .attr('x', (d) => xScale(d.x))
.attr('y', (d) => yScale(d.y)) .attr('y', (d) => yScale(d.y))
.attr('height', (d) => yScale((max || 0) - d.y)) .attr('height', (d) => yScale((max || 0) - d.y))
.attr('width', (d) => 1), .attr('width', () => 1),
(exit) => exit.call((d) => d.transition().remove()) (exit) => exit.call((d) => d.transition().remove())
) )

View File

@ -1,12 +1,12 @@
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import React, { Fragment, useCallback, useEffect, useState } from 'react' import React, { Fragment, useCallback, useEffect, useState } from 'react'
import { Search as SearchIcon } from 'react-feather' import { Search as SearchIcon } from 'react-feather'
import { SearchIndex, SearchItem } from '../utils/search' import { SearchItem } from '../utils/search'
import { Dialog, Transition, Combobox } from '@headlessui/react' import { Dialog, Transition, Combobox } from '@headlessui/react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
export interface SearchProps { export interface SearchProps {
index: SearchIndex fuse: Fuse<SearchItem>
} }
interface SearchResultProps { interface SearchResultProps {
@ -57,23 +57,13 @@ const SearchResult: React.FunctionComponent<SearchResultProps> = (props) => {
) )
} }
const Search: React.FunctionComponent<SearchProps> = (props) => { const Search: React.FunctionComponent<SearchProps> = ({ fuse }) => {
const router = useRouter() const router = useRouter()
const [searchText, setSearchText] = useState('') const [searchText, setSearchText] = useState('')
const [selected, setSelected] = useState<
Fuse.FuseResult<SearchItem> | undefined
>(undefined)
const [searchResults, setSearchResults] = useState< const [searchResults, setSearchResults] = useState<
Fuse.FuseResult<SearchItem>[] Fuse.FuseResult<SearchItem>[]
>([]) >([])
const [fuse, setFuse] = useState(() => {
const options: Fuse.IFuseOptions<SearchItem> = {}
return new Fuse(
props.index.list,
options,
Fuse.parseIndex(props.index.index)
)
})
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const handleKeyPress = useCallback( const handleKeyPress = useCallback(
(event: KeyboardEvent) => { (event: KeyboardEvent) => {
@ -159,7 +149,7 @@ const Search: React.FunctionComponent<SearchProps> = (props) => {
<div className="fixed inset-0"> <div className="fixed inset-0">
<div className="flex h-screen w-screen items-start justify-center p-16 text-center"> <div className="flex h-screen w-screen items-start justify-center p-16 text-center">
<Dialog.Panel className="flex flex-col max-h-full w-full max-w-2xl p-1 bg-gray-200 dark:bg-gray-800 transform rounded-xl text-left align-middle shadow transition-all border border-gray-800 dark:border-white border-opacity-10 dark:border-opacity-10"> <Dialog.Panel className="flex flex-col max-h-full w-full max-w-2xl p-1 bg-gray-200 dark:bg-gray-800 transform rounded-xl text-left align-middle shadow transition-all border border-gray-800 dark:border-white border-opacity-10 dark:border-opacity-10">
<Combobox value={selected} nullable onChange={onChange}> <Combobox nullable onChange={onChange}>
<div className="flex"> <div className="flex">
<Combobox.Label className="flex items-center ml-2"> <Combobox.Label className="flex items-center ml-2">
<SearchIcon size={32} /> <SearchIcon size={32} />

View File

@ -1,5 +1,3 @@
import { Dispatch, SetStateAction } from 'react'
const sunIcon = ( const sunIcon = (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

2601
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,18 +11,20 @@
"url": "https://github.com/brndnmtthws/conky/issues" "url": "https://github.com/brndnmtthws/conky/issues"
}, },
"scripts": { "scripts": {
"dev": "next", "dev": "next dev",
"dev:watch": "next-remote-watch ./documents", "dev:watch": "next-remote-watch ./documents",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"export": "next build && next export", "export": "next build && next export",
"lint": "next lint" "lint": "next lint",
"e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"",
"e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\""
}, },
"dependencies": { "dependencies": {
"@fontsource/fira-code": "^4.5.13", "@fontsource/fira-code": "^4.5.13",
"@fontsource/inter": "^4.5.15", "@fontsource/inter": "^4.5.15",
"@fontsource/newsreader": "^4.5.10", "@fontsource/newsreader": "^4.5.10",
"@headlessui/react": "^1.7.11", "@headlessui/react": "^1.7.12",
"@mapbox/rehype-prism": "^0.8.0", "@mapbox/rehype-prism": "^0.8.0",
"@netlify/plugin-nextjs": "^4.30.4", "@netlify/plugin-nextjs": "^4.30.4",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
@ -32,7 +34,7 @@
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"inter-ui": "^3.19.3", "inter-ui": "^3.19.3",
"next": "^13.1.6", "next": "^13.2.1",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -41,23 +43,25 @@
"devDependencies": { "devDependencies": {
"@types/d3": "^7.4.0", "@types/d3": "^7.4.0",
"@types/mapbox__rehype-prism": "^0.8.0", "@types/mapbox__rehype-prism": "^0.8.0",
"@types/node": "^18.14.0", "@types/node": "^18.14.1",
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.53.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"cypress": "^12.6.0", "cypress": "^12.7.0",
"eslint": "^8.34.0", "eslint": "^8.34.0",
"eslint-config-next": "^13.1.6", "eslint-config-next": "^13.2.1",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-mdx": "^2.0.5", "eslint-plugin-mdx": "^2.0.5",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"netlify-plugin-cypress": "^2.2.0", "netlify-plugin-cypress": "^2.2.1",
"next-mdx-remote": "^4.3.0", "next-mdx-remote": "^4.3.0",
"next-remote-watch": "2.0.0", "next-remote-watch": "2.0.0",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"rehype-stringify": "^9.0.3", "rehype-stringify": "^9.0.3",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"start-server-and-test": "^1.15.4",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",
"tslint": "^6.1.3",
"typescript": "^4.9.5" "typescript": "^4.9.5"
} }
} }

View File

@ -7,18 +7,19 @@ import {
import { MDXRemote, MDXRemoteProps } from 'next-mdx-remote' import { MDXRemote, MDXRemoteProps } from 'next-mdx-remote'
import Head from 'next/head' import Head from 'next/head'
import CustomLink from '../../components/CustomLink'
import Layout from '../../components/Layout' import Layout from '../../components/Layout'
import SEO from '../../components/SEO' import SEO from '../../components/SEO'
import { GetStaticProps } from 'next' import { GetStaticProps } from 'next'
import { getSearchIndex, SearchIndex } from '../../utils/search' import { getSearchIndex, SearchIndex } from '../../utils/search'
import Link from 'next/link'
import { MDXComponents } from 'mdx/types'
// Custom components/renderers to pass to MDX. // Custom components/renderers to pass to MDX.
// Since the MDX files aren't loaded by webpack, they have no knowledge of how // Since the MDX files aren't loaded by webpack, they have no knowledge of how
// to handle import statements. Instead, you must include components in scope // to handle import statements. Instead, you must include components in scope
// here. // here.
const components = { const components = {
a: CustomLink, a: Link,
// It also works with dynamically-imported components, which is especially // It also works with dynamically-imported components, which is especially
// useful for conditionally loading components for certain routes. // useful for conditionally loading components for certain routes.
// See the notes in README.md for more details. // See the notes in README.md for more details.
@ -58,7 +59,7 @@ export default function DocumentPage({
</header> </header>
<main> <main>
<article className="prose dark:prose-invert prose-lg lg:prose-xl"> <article className="prose dark:prose-invert prose-lg lg:prose-xl">
<MDXRemote {...source} components={components as any} /> <MDXRemote {...source} components={components as MDXComponents} />
</article> </article>
</main> </main>
</article> </article>

View File

@ -4,7 +4,6 @@ import Layout from '../components/Layout'
import ArrowIcon from '../components/ArrowIcon' import ArrowIcon from '../components/ArrowIcon'
import SEO from '../components/SEO' import SEO from '../components/SEO'
import { getSearchIndex, SearchIndex } from '../utils/search' import { getSearchIndex, SearchIndex } from '../utils/search'
import { Url } from 'url'
const pages = [ const pages = [
{ {
@ -34,14 +33,14 @@ interface IndexItemProps {
const IndexItem: React.FunctionComponent<IndexItemProps> = (props) => { const IndexItem: React.FunctionComponent<IndexItemProps> = (props) => {
return ( return (
<div className="md:first:rounded-t-lg md:last:rounded-b-lg backdrop-blur-lg bg-slate-300 dark:bg-black dark:bg-opacity-30 bg-opacity-10 hover:bg-opacity-30 dark:hover:bg-opacity-50 transition border border-gray-800 dark:border-white border-opacity-10 dark:border-opacity-10 border-b-0 last:border-b hover:border-b hovered-sibling:border-t-0"> <div className="md:first:rounded-t-lg md:last:rounded-b-lg backdrop-blur-lg bg-slate-300 dark:bg-black dark:bg-opacity-30 bg-opacity-10 hover:bg-opacity-30 dark:hover:bg-opacity-50 transition border border-gray-800 dark:border-white border-opacity-10 dark:border-opacity-10 border-b-0 last:border-b hover:border-b hovered-sibling:border-t-0">
<Link as={props.as} href={props.href}> <Link
<a className="py-2 lg:py-4 px-2 lg:px-4 block focus:outline-none focus:ring-4"> as={props.as}
<h2 className="text-xl md:text-2xl">{props.title}</h2> href={props.href}
{props.desc && ( className="py-2 lg:py-4 px-2 lg:px-4 block focus:outline-none focus:ring-4"
<p className="mt-3 text-lg opacity-60">{props.desc}</p> >
)} <h2 className="text-xl md:text-2xl">{props.title}</h2>
<ArrowIcon className="mt-4" /> {props.desc && <p className="mt-3 text-lg opacity-60">{props.desc}</p>}
</a> <ArrowIcon className="mt-4" />
</Link> </Link>
</div> </div>
) )

View File

@ -5,4 +5,4 @@ module.exports = {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; }

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,
@ -19,12 +15,6 @@
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve" "jsx": "preserve"
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"next-env.d.ts", "exclude": ["node_modules"]
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
} }