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,
tabWidth: 2,
useTabs: false,
};
}

View File

@ -22,7 +22,16 @@ _Patches submitted in issues, email, or elsewhere may be ignored._
## 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

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"],
"settings": {
"extends": [
"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/language-mapper": {}
"mdx/language-mapper": {},
"sort-imports": "error"
}
}

3
web/.gitignore vendored
View File

@ -39,3 +39,6 @@ yarn-error.log*
# IDEs
.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="px-2 py-3">
<Link href={`#${doc.name}`}>
<a>
<LinkIcon size={20} />
</a>
<LinkIcon size={20} />
</Link>
</div>
<div className="flex-col p-1">

View File

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

View File

@ -34,7 +34,7 @@ export default function Layout({ children, searchIndex }: LayoutProps) {
}, [darkMode])
useEffect(() => {
var darkQuery = window.matchMedia('(prefers-color-scheme: dark)')
const darkQuery = window.matchMedia('(prefers-color-scheme: dark)')
darkQuery.onchange = (e) => {
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 { random } from 'colord'
@ -40,12 +40,12 @@ function fetchData() {
const data = JSON.parse(storedData)
return data || []
}
var data = fetchData()
const data = fetchData()
export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
const timerRef = useRef<NodeJS.Timeout>()
const svgRef = useRef(null)
const [stroke, setStroke] = useState(random())
const stroke = useMemo(() => random(), [])
useEffect(() => {
const doIt = (data: DataPoint[]) => {
@ -59,7 +59,7 @@ export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
.range([height, 0])
// X axis
const [xMin, xMax] = d3.extent(data, (d) => d.x)
const [, xMax] = d3.extent(data, (d) => d.x)
const xScale = d3
.scaleLinear()
.domain([(xMax || 0) - width, xMax || 0])
@ -83,7 +83,7 @@ export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
.attr('x', (d) => xScale(d.x))
.attr('y', (d) => yScale(d.y))
.attr('height', (d) => yScale((max || 0) - d.y))
.attr('width', (d) => 1),
.attr('width', () => 1),
(update) =>
update
.transition()
@ -95,7 +95,7 @@ export const LineChart = ({ width, height, darkMode }: LineChartProps) => {
.attr('x', (d) => xScale(d.x))
.attr('y', (d) => yScale(d.y))
.attr('height', (d) => yScale((max || 0) - d.y))
.attr('width', (d) => 1),
.attr('width', () => 1),
(exit) => exit.call((d) => d.transition().remove())
)

View File

@ -1,12 +1,12 @@
import Fuse from 'fuse.js'
import React, { Fragment, useCallback, useEffect, useState } from 'react'
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 { useRouter } from 'next/router'
export interface SearchProps {
index: SearchIndex
fuse: Fuse<SearchItem>
}
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 [searchText, setSearchText] = useState('')
const [selected, setSelected] = useState<
Fuse.FuseResult<SearchItem> | undefined
>(undefined)
const [searchResults, setSearchResults] = useState<
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 handleKeyPress = useCallback(
(event: KeyboardEvent) => {
@ -159,7 +149,7 @@ const Search: React.FunctionComponent<SearchProps> = (props) => {
<div className="fixed inset-0">
<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">
<Combobox value={selected} nullable onChange={onChange}>
<Combobox nullable onChange={onChange}>
<div className="flex">
<Combobox.Label className="flex items-center ml-2">
<SearchIcon size={32} />

View File

@ -1,5 +1,3 @@
import { Dispatch, SetStateAction } from 'react'
const sunIcon = (
<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"
},
"scripts": {
"dev": "next",
"dev": "next dev",
"dev:watch": "next-remote-watch ./documents",
"build": "next build",
"start": "next start",
"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": {
"@fontsource/fira-code": "^4.5.13",
"@fontsource/inter": "^4.5.15",
"@fontsource/newsreader": "^4.5.10",
"@headlessui/react": "^1.7.11",
"@headlessui/react": "^1.7.12",
"@mapbox/rehype-prism": "^0.8.0",
"@netlify/plugin-nextjs": "^4.30.4",
"@tailwindcss/typography": "^0.5.9",
@ -32,7 +34,7 @@
"fuse.js": "^6.6.2",
"gray-matter": "^4.0.3",
"inter-ui": "^3.19.3",
"next": "^13.1.6",
"next": "^13.2.1",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@ -41,23 +43,25 @@
"devDependencies": {
"@types/d3": "^7.4.0",
"@types/mapbox__rehype-prism": "^0.8.0",
"@types/node": "^18.14.0",
"@types/node": "^18.14.1",
"@types/react": "^18.0.28",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.53.0",
"autoprefixer": "^10.4.13",
"cypress": "^12.6.0",
"cypress": "^12.7.0",
"eslint": "^8.34.0",
"eslint-config-next": "^13.1.6",
"eslint-config-next": "^13.2.1",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-mdx": "^2.0.5",
"js-yaml": "^4.1.0",
"netlify-plugin-cypress": "^2.2.0",
"netlify-plugin-cypress": "^2.2.1",
"next-mdx-remote": "^4.3.0",
"next-remote-watch": "2.0.0",
"postcss": "^8.4.21",
"rehype-stringify": "^9.0.3",
"remark-gfm": "^3.0.1",
"start-server-and-test": "^1.15.4",
"tailwindcss": "^3.2.7",
"tslint": "^6.1.3",
"typescript": "^4.9.5"
}
}

View File

@ -7,18 +7,19 @@ import {
import { MDXRemote, MDXRemoteProps } from 'next-mdx-remote'
import Head from 'next/head'
import CustomLink from '../../components/CustomLink'
import Layout from '../../components/Layout'
import SEO from '../../components/SEO'
import { GetStaticProps } from 'next'
import { getSearchIndex, SearchIndex } from '../../utils/search'
import Link from 'next/link'
import { MDXComponents } from 'mdx/types'
// Custom components/renderers to pass to MDX.
// 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
// here.
const components = {
a: CustomLink,
a: Link,
// It also works with dynamically-imported components, which is especially
// useful for conditionally loading components for certain routes.
// See the notes in README.md for more details.
@ -58,7 +59,7 @@ export default function DocumentPage({
</header>
<main>
<article className="prose dark:prose-invert prose-lg lg:prose-xl">
<MDXRemote {...source} components={components as any} />
<MDXRemote {...source} components={components as MDXComponents} />
</article>
</main>
</article>

View File

@ -4,7 +4,6 @@ import Layout from '../components/Layout'
import ArrowIcon from '../components/ArrowIcon'
import SEO from '../components/SEO'
import { getSearchIndex, SearchIndex } from '../utils/search'
import { Url } from 'url'
const pages = [
{
@ -34,14 +33,14 @@ interface IndexItemProps {
const IndexItem: React.FunctionComponent<IndexItemProps> = (props) => {
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">
<Link as={props.as} href={props.href}>
<a className="py-2 lg:py-4 px-2 lg:px-4 block focus:outline-none focus:ring-4">
<h2 className="text-xl md:text-2xl">{props.title}</h2>
{props.desc && (
<p className="mt-3 text-lg opacity-60">{props.desc}</p>
)}
<ArrowIcon className="mt-4" />
</a>
<Link
as={props.as}
href={props.href}
className="py-2 lg:py-4 px-2 lg:px-4 block focus:outline-none focus:ring-4"
>
<h2 className="text-xl md:text-2xl">{props.title}</h2>
{props.desc && <p className="mt-3 text-lg opacity-60">{props.desc}</p>}
<ArrowIcon className="mt-4" />
</Link>
</div>
)

View File

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

View File

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@ -19,12 +15,6 @@
"isolatedModules": true,
"jsx": "preserve"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}