Skip to content

Commit

Permalink
fix: docs missing icon
Browse files Browse the repository at this point in the history
  • Loading branch information
nonzzz committed Feb 16, 2025
1 parent 4b10158 commit 567dbfa
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 44 deletions.
122 changes: 106 additions & 16 deletions scripts/h.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable no-use-before-define */
// preact is fine, but i won't need it for the project.
// Note: This is a minimal implementation that only do jsx to html string conversion.

export type HTMLTag = keyof HTMLElementTagNameMap

export type ProprsWithChildren<P = unknown> = P & { children?: Child | Child[] }
export type ProprsWithChildren<P = unknown> = P & { children?: Child | Child[], ref?: Ref<unknown> }

export type Component<P = Any> = (props: ProprsWithChildren<P>) => VNode

Expand All @@ -16,9 +17,10 @@ export type DeepOptionalProps<T> = {
export type InferElement<T extends HTMLTag> = HTMLElementTagNameMap[T]

export interface VNode<P = Any> {
type: HTMLTag | Component<P>
type: HTMLTag | Component<P> | 'svg'
props: ProprsWithChildren<P>
children: Child[]
__id__?: string
}

export type JSXElement<E extends HTMLTag | Component> = E extends HTMLTag ? VNode<DeepOptionalProps<InferElement<E>>>
Expand All @@ -43,16 +45,30 @@ export function h<T extends HTMLTag | Component>(
export const Fragment = Symbol('Fragment') as unknown as Component<Any>
export type FragmentType = typeof Fragment

function normalizeKey(key: string): string {
function normalizeKey(key: string, isSvg: boolean): string {
if (isSvg) {
const svgSpecialCases: Record<string, string> = {
className: 'class',
htmlFor: 'for',
viewBox: 'viewBox',
fillRule: 'fill-rule',
clipRule: 'clip-rule',
strokeWidth: 'stroke-width',
strokeLinecap: 'stroke-linecap',
strokeLinejoin: 'stroke-linejoin',
strokeDasharray: 'stroke-dasharray',
strokeDashoffset: 'stroke-dashoffset'
}
return svgSpecialCases[key] || key
}
const specialCases: Record<string, string> = {
className: 'class',
htmlFor: 'for'
}

return specialCases[key] || key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
}

function renderProps(props: ProprsWithChildren<Record<string, Any>>): string {
function renderProps(props: ProprsWithChildren<Record<string, Any>>, isSvg: boolean): string {
if (!props) { return '' }
return Object.entries(props)
.filter(([key]) => key !== 'children')
Expand All @@ -61,23 +77,47 @@ function renderProps(props: ProprsWithChildren<Record<string, Any>>): string {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const style = Object.entries(value)
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
.map(([k, v]) => `${normalizeKey(k)}:${v}`)
.map(([k, v]) => `${normalizeKey(k, isSvg)}:${v}`)
.join(';')
return `style="${style}"`
}
if (typeof value === 'boolean' && value) {
return normalizeKey(key)
return normalizeKey(key, isSvg)
}
if (typeof value === 'string' || typeof value === 'number') {
return `${normalizeKey(key)}="${value}"`
return `${normalizeKey(key, isSvg)}="${value}"`
}
return ''
})
.filter(Boolean)
.join(' ')
}

export function renderToString(node: Child): string {
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
const SVG_TAGS = new Set([
'svg',
'path',
'rect',
'circle',
'line',
'g',
'defs',
'pattern',
'mask',
'use',
'polyline',
'polygon',
'text',
'tspan'
])

export function renderToString(node: VNode): string {
const { vnode } = processVNode(node)

return processNodeToStr(vnode)
}

export function processNodeToStr(node: Child): string {
if (node == null || typeof node === 'boolean') {
return ''
}
Expand All @@ -86,18 +126,22 @@ export function renderToString(node: Child): string {
return String(node)
}

const { type, props, children } = node as VNode<unknown>
const { type, props, children, __id__ } = node as VNode<unknown>

const refAttr = __id__ ? `data-ref="${__id__}"` : ''

if (type === Fragment) {
return children.map(renderToString).join('')
return children.map(processNodeToStr).join('')
}

if (typeof type === 'function') {
return renderToString(type(props))
return processNodeToStr(type(props))
}

const propsString = renderProps(props)
const childrenString = children.map(renderToString).join('')
const isSvg = typeof type === 'string' && SVG_TAGS.has(type)

const propsString = renderProps(props, isSvg)
const childrenString = children.map(processNodeToStr).join('')

// Self-closing tags
const voidElements = new Set([
Expand All @@ -116,9 +160,55 @@ export function renderToString(node: Child): string {
'track',
'wbr'
])
if (isSvg && type === 'svg') {
return `<svg xmlns="${SVG_NAMESPACE}"${propsString ? ' ' + propsString : ''}${refAttr}>${childrenString}</svg>`
}

if (voidElements.has(type)) {
return `<${type}${propsString ? ' ' + propsString : ''}/>`
return `<${type}${propsString ? ' ' + propsString : ''} ${refAttr}/>`
}

return `<${type}${propsString ? ' ' + propsString : ''} ${refAttr}>${childrenString}</${type}>`
}

export interface RefObject<T> {
current: T | null
}

export type RefCallback<T> = (instance: T | null) => void
export type Ref<T> = RefObject<T> | RefCallback<T>

export function useRef<T>(initialValue: T | null = null): RefObject<T> {
return { current: initialValue }
}

export function processVNode(rootNode: VNode) {
const refMap: Record<string, Ref<unknown>> = {}
let refId = 0

function processNode(node: VNode<unknown>): VNode<unknown> {
if (typeof node.type === 'function') {
const result = node.type(node.props)
return processNode(result)
}

const processedNode = { ...node }

if (node.props?.ref) {
processedNode.__id__ = `ref_${refId++}`
refMap[processedNode.__id__] = node.props.ref
}

processedNode.children = node.children.map((child) => {
if (child && typeof child === 'object' && 'type' in child) {
return processNode(child as VNode)
}
return child
})

return processedNode
}

return `<${type}${propsString ? ' ' + propsString : ''}>${childrenString}</${type}>`
const processedVNode = processNode(rootNode)
return { vnode: processedVNode, refMap }
}
7 changes: 7 additions & 0 deletions scripts/hydrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type VNode, processVNode } from './h'

export function hydrate(rootNode: VNode) {
const { refMap } = processVNode(rootNode)

return { refMap }
}
75 changes: 47 additions & 28 deletions scripts/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import yaml from 'js-yaml'
import markdownit from 'markdown-it'
import path from 'path'
import type { Component } from './h'
import { Fragment, h, renderToString } from './h'
import { Fragment, h, renderToString, useRef } from './h'
import { hydrate } from './hydrate'
import type { Theme } from './theme'
/// <reference path="./jsx-namespace.d.ts" />

Expand All @@ -30,11 +31,11 @@ const Icons = {
<path
fill="currentColor"
d="M233.54 142.23a8 8 0 0 0-8-2a88.08 88.08 0 0
1-109.8-109.8a8 8 0 0 0-10-10a104.84 104.84 0 0 0-52.91 37A104 104 0 0 0
136 224a103.1 103.1 0 0 0 62.52-20.88a104.84 104.84 0 0 0 37-52.91a8 8 0
0 0-1.98-7.98m-44.64 48.11A88 88 0 0 1 65.66 67.11a89 89 0 0 1
31.4-26A106 106 0 0 0 96 56a104.11 104.11 0 0 0 104 104a106 106 0 0 0
14.92-1.06a89 89 0 0 1-26.02 31.4"
1-109.8-109.8a8 8 0 0 0-10-10a104.84 104.84 0 0 0-52.91 37A104 104 0 0 0
136 224a103.1 103.1 0 0 0 62.52-20.88a104.84 104.84 0 0 0 37-52.91a8 8 0
0 0-1.98-7.98m-44.64 48.11A88 88 0 0 1 65.66 67.11a89 89 0 0 1
31.4-26A106 106 0 0 0 96 56a104.11 104.11 0 0 0 104 104a106 106 0 0 0
14.92-1.06a89 89 0 0 1-26.02 31.4"
/>
</svg>
),
Expand All @@ -43,14 +44,14 @@ const Icons = {
<path
fill="currentColor"
d="M120 40V16a8 8 0 0 1 16 0v24a8 8 0 0 1-16 0m72
88a64 64 0 1 1-64-64a64.07 64.07 0 0 1 64 64m-16 0a48 48 0 1 0-48 48a48.05
48.05 0 0 0 48-48M58.34 69.66a8 8 0 0 0 11.32-11.32l-16-16a8 8 0 0
0-11.32 11.32Zm0 116.68l-16 16a8 8 0 0 0 11.32 11.32l16-16a8 8 0 0
0-11.32-11.32M192 72a8 8 0 0 0 5.66-2.34l16-16a8 8 0 0 0-11.32-11.32l-16
16A8 8 0 0 0 192 72m5.66 114.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0
11.32-11.32ZM48 128a8 8 0 0 0-8-8H16a8 8 0 0 0 0 16h24a8 8 0 0 0
8-8m80 80a8 8 0 0 0-8 8v24a8 8 0 0 0 16 0v-24a8 8 0 0 0-8-8m112-88h-24a8
8 0 0 0 0 16h24a8 8 0 0 0 0-16"
88a64 64 0 1 1-64-64a64.07 64.07 0 0 1 64 64m-16 0a48 48 0 1 0-48 48a48.05
48.05 0 0 0 48-48M58.34 69.66a8 8 0 0 0 11.32-11.32l-16-16a8 8 0 0
0-11.32 11.32Zm0 116.68l-16 16a8 8 0 0 0 11.32 11.32l16-16a8 8 0 0
0-11.32-11.32M192 72a8 8 0 0 0 5.66-2.34l16-16a8 8 0 0 0-11.32-11.32l-16
16A8 8 0 0 0 192 72m5.66 114.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0
11.32-11.32ZM48 128a8 8 0 0 0-8-8H16a8 8 0 0 0 0 16h24a8 8 0 0 0
8-8m80 80a8 8 0 0 0-8 8v24a8 8 0 0 0 16 0v-24a8 8 0 0 0-8-8m112-88h-24a8
8 0 0 0 0 16h24a8 8 0 0 0 0-16"
/>
</svg>
),
Expand All @@ -59,17 +60,17 @@ const Icons = {
<path
fill="currentColor"
d="M208.31 75.68A59.78 59.78 0 0 0 202.93 28a8 8
0 0 0-6.93-4a59.75 59.75 0 0 0-48 24h-24a59.75 59.75 0 0 0-48-24a8 8 0 0
0-6.93 4a59.78 59.78 0 0 0-5.38 47.68A58.14 58.14 0 0 0 56 104v8a56.06
56.06 0 0 0 48.44 55.47A39.8 39.8 0 0 0 96 192v8H72a24 24 0 0 1-24-24a40
40 0 0 0-40-40a8 8 0 0 0 0 16a24 24 0 0 1 24 24a40 40 0 0 0 40 40h24v16a8
8 0 0 0 16 0v-40a24 24 0 0 1 48 0v40a8 8 0 0 0 16 0v-40a39.8 39.8 0 0
0-8.44-24.53A56.06 56.06 0 0 0 216 112v-8a58.14 58.14 0 0 0-7.69-28.32M200
112a40 40 0 0 1-40 40h-48a40 40 0 0 1-40-40v-8a41.74 41.74 0 0 1
6.9-22.48a8 8 0 0 0 1.1-7.69a43.8 43.8 0 0 1 .79-33.58a43.88 43.88 0 0 1
32.32 20.06a8 8 0 0 0 6.71 3.69h32.35a8 8 0 0 0 6.74-3.69a43.87 43.87 0 0
1 32.32-20.06a43.8 43.8 0 0 1 .77 33.58a8.09 8.09 0 0 0 1 7.65a41.7 41.7
0 0 1 7 22.52Z"
0 0 0-6.93-4a59.75 59.75 0 0 0-48 24h-24a59.75 59.75 0 0 0-48-24a8 8 0 0
0-6.93 4a59.78 59.78 0 0 0-5.38 47.68A58.14 58.14 0 0 0 56 104v8a56.06
56.06 0 0 0 48.44 55.47A39.8 39.8 0 0 0 96 192v8H72a24 24 0 0 1-24-24a40
40 0 0 0-40-40a8 8 0 0 0 0 16a24 24 0 0 1 24 24a40 40 0 0 0 40 40h24v16a8
8 0 0 0 16 0v-40a24 24 0 0 1 48 0v40a8 8 0 0 0 16 0v-40a39.8 39.8 0 0
0-8.44-24.53A56.06 56.06 0 0 0 216 112v-8a58.14 58.14 0 0 0-7.69-28.32M200
112a40 40 0 0 1-40 40h-48a40 40 0 0 1-40-40v-8a41.74 41.74 0 0 1
6.9-22.48a8 8 0 0 0 1.1-7.69a43.8 43.8 0 0 1 .79-33.58a43.88 43.88 0 0 1
32.32 20.06a8 8 0 0 0 6.71 3.69h32.35a8 8 0 0 0 6.74-3.69a43.87 43.87 0 0
1 32.32-20.06a43.8 43.8 0 0 1 .77 33.58a8.09 8.09 0 0 0 1 7.65a41.7 41.7
0 0 1 7 22.52Z"
/>
</svg>
),
Expand Down Expand Up @@ -107,7 +108,7 @@ const data = yaml.load(fs.readFileSync(path.join(Dirs.docs, 'index.yaml'), 'utf8
const pages = Object.entries(data)

const commonCSS = minifyCSS(fs.readFileSync(path.join(Dirs.script, 'style.css'), 'utf8'))
const coomonScript = minifyJS(fs.readFileSync(path.join(Dirs.script, 'theme.ts'), 'utf8'))
const commonScript = minifyJS(fs.readFileSync(path.join(Dirs.script, 'theme.ts'), 'utf8'))

function createTag<T extends DocTagElement>(tag: T, value: DocTagValue<T>): FormattedDocTag<T> {
return { tag, value }
Expand Down Expand Up @@ -259,6 +260,8 @@ function Menu() {
structure.push(root)
}

const ref = useRef()

return (
<aside>
<nav id="menu">
Expand All @@ -267,7 +270,7 @@ function Menu() {
<a aria-label="View this project on GitHub" href="https://github.com/nonzzz/squarified">
<Icons.GitHub />
</a>
<a href="javascript:void(0)" aria-label="Toggle theme" id="theme-toggle">
<a href="javascript:void(0)" aria-label="Toggle theme" id="theme-toggle" ref={ref}>
<Icons.Moon />
<Icons.Sun />
</a>
Expand Down Expand Up @@ -418,18 +421,34 @@ function buildExampleDisplay() {
async function main() {
for (const [page, pageData] of formatedPages) {
const html: string[] = []
const { refMap } = hydrate(<Layout data={pageData} />)
html.push('<!DOCTYPE html>')
html.push(renderToString(
<html lang="en">
<Head title={pageData.title} />
<body>
<Layout data={pageData} />
<script>
{coomonScript}
window.__REFS__ = {JSON.stringify(refMap)};
{commonScript}
</script>
</body>
</html>
))

html.push(`<script>
const refObject = window.__REFS__
window.addEventListener('DOMContentLoaded', () => {
for (const id in refObject) {
const ref = refObject[id]
const el = document.querySelector('[data-ref="' + id + '"]')
if (el && typeof ref === 'object') {
ref.current = el
}
}
})
</script>`)

if (!fs.existsSync(Dirs.dest)) {
fs.mkdirSync(Dirs.dest)
}
Expand Down

0 comments on commit 567dbfa

Please sign in to comment.