` in ``')
+ }
+ })
+ }
+ })
+ .use(rehypeStringify)
+ .process(
+ "console.log('hi!')
hello!
"
+ )
+```
+
+### `unist-util-select`
+
+Sometimes CSS selectors are easier to read than several (nested) if/else
+statements.
+[`unist-util-select`][select] lets you do that.
+For example if we want to find all `Paragraph`s that are somewhere in a
+`Blockquote`, we could:
+
+```ts twoslash
+///
+// ---cut---
+import type {Paragraph, Root} from 'mdast'
+import {remark} from 'remark'
+import {selectAll} from 'unist-util-select'
+
+remark()
+ .use(function () {
+ return function (mdast: Root) {
+ const matches = selectAll('blockquote paragraph', mdast) as Paragraph[]
+ console.log(matches)
+ }
+ })
+ .process('> block quote')
+```
+
+[tree-traversal]: /learn/recipe/tree-traversal/
+
+[visit]: https://github.com/syntax-tree/unist-util-visit
+
+[visit-parents]: https://github.com/syntax-tree/unist-util-visit-parents
+
+[select]: https://github.com/syntax-tree/unist-util-select
diff --git a/doc/learn/tree-traversal.md b/doc/learn/tree-traversal.md
index ae24fa8fb122..bf0862d5f219 100644
--- a/doc/learn/tree-traversal.md
+++ b/doc/learn/tree-traversal.md
@@ -1,28 +1,28 @@
---
-group: recipe
-title: Tree traversal
+authorGithub: wooorm
+author: Titus Wormer
description: How to do tree traversal (also known as walking or visiting a tree)
+group: recipe
+index: 3
+modified: 2024-08-02
+published: 2019-12-23
tags:
- - unist
- - tree
- traverse
- - walk
+ - tree
+ - unist
- visit
-author: Titus Wormer
-authorTwitter: wooorm
-published: 2019-12-23
-modified: 2019-12-23
-index: 1
+ - walk
+title: Tree traversal
---
## How to walk a tree
### Contents
-* [Tree traversal](#tree-traversal)
-* [Set up](#set-up)
-* [Traverse the tree](#traverse-the-tree)
-* [Visiting a certain kind of node](#visiting-a-certain-kind-of-node)
+* [Tree traversal](#tree-traversal)
+* [Set up](#set-up)
+* [Traverse the tree](#traverse-the-tree)
+* [Visiting a certain kind of node](#visiting-a-certain-kind-of-node)
### Tree traversal
@@ -32,13 +32,14 @@ inside it).
Traversal means stopping at every node to do something.
So, tree traversal means doing something for every node in a tree.
-Tree traversal is often also called “walking a tree”, or “visiting a tree”.
+Tree traversal is often also called “walking a tree”.
+Or “visiting a tree”.
To learn more, continue reading, but when working with unist (unified’s trees)
you probably need either:
-* [`unist-util-visit`][visit]
-* [`unist-util-visit-parents`][visit-parents]
+* [`unist-util-visit`][visit]
+* [`unist-util-visit-parents`][visit-parents]
### Set up
@@ -56,16 +57,16 @@ Let’s say we have the following fragment of HTML, in a file `example.html`:
You could parse that with the following code (using [`unified`][unified] and
[`rehype-parse`][rehype-parse]):
-```js
-var fs = require('fs')
-var unified = require('unified')
-var parse = require('rehype-parse')
+```js twoslash
+///
+// ---cut---
+import fs from 'node:fs/promises'
+import rehypeParse from 'rehype-parse'
+import {unified} from 'unified'
-var doc = fs.readFileSync('example.html')
+const document = await fs.readFile('example.html')
-var tree = unified()
- .use(parse, {fragment: true})
- .parse(doc)
+const tree = unified().use(rehypeParse, {fragment: true}).parse(document)
console.log(tree)
```
@@ -81,35 +82,35 @@ Which would yield (ignoring positional info for brevity):
tagName: 'p',
properties: {},
children: [
- {type: 'text', value: '\n '},
- {type: 'comment', value: ' A comment. '},
- {type: 'text', value: '\n Some '},
+ { type: 'text', value: '\n ' },
+ { type: 'comment', value: ' A comment. ' },
+ { type: 'text', value: '\n Some ' },
{
type: 'element',
tagName: 'strong',
properties: {},
- children: [{type: 'text', value: 'strong importance'}]
+ children: [ { type: 'text', value: 'strong importance' } ]
},
- {type: 'text', value: ', '},
+ { type: 'text', value: ', ' },
{
type: 'element',
tagName: 'em',
properties: {},
- children: [{type: 'text', value: 'emphasis'}]
+ children: [ { type: 'text', value: 'emphasis' } ]
},
- {type: 'text', value: ', and a dash of\n '},
+ { type: 'text', value: ', and a dash of\n ' },
{
type: 'element',
tagName: 'code',
properties: {},
- children: [{type: 'text', value: 'code'}]
+ children: [ { type: 'text', value: 'code' } ]
},
- {type: 'text', value: '.\n'}
+ { type: 'text', value: '.\n' }
]
},
- {type: 'text', value: '\n'}
+ { type: 'text', value: '\n' }
],
- data: {quirksMode: false}
+ data: { quirksMode: false }
}
```
@@ -119,12 +120,18 @@ As we are all set up, we can traverse the tree.
A useful utility for that is [`unist-util-visit`][visit], and it works like so:
-```js
-var visit = require('unist-util-visit')
+```js twoslash
+///
+/**
+ * @import {Root} from 'hast'
+ */
+const tree = /** @type {Root} */ (/** @type {unknown} */ (undefined))
+// ---cut---
+import {visit} from 'unist-util-visit'
// …
-visit(tree, function(node) {
+visit(tree, function (node) {
console.log(node.type)
})
```
@@ -154,12 +161,16 @@ We traversed the entire tree, and for each node, we printed its `type`.
To “visit” only a certain `type` of node, pass a test to
[`unist-util-visit`][visit] like so:
-```js
-var visit = require('unist-util-visit')
+```js twoslash
+///
+/**
+ * @import {Root} from 'hast'
+ */
+import {visit} from 'unist-util-visit'
-// …
-
-visit(tree, 'element', function(node) {
+const tree = /** @type {Root} */ (/** @type {unknown} */ (undefined))
+// ---cut---
+visit(tree, 'element', function (node) {
console.log(node.tagName)
})
```
@@ -174,8 +185,16 @@ code
You can do this yourself as well.
The above works the same as:
-```js
-visit(tree, function(node) {
+```js twoslash
+///
+/**
+ * @import {Root} from 'hast'
+ */
+import {visit} from 'unist-util-visit'
+
+const tree = /** @type {Root} */ (/** @type {unknown} */ (undefined))
+// ---cut---
+visit(tree, function (node) {
if (node.type === 'element') {
console.log(node.tagName)
}
@@ -185,8 +204,17 @@ visit(tree, function(node) {
But the test passed to `visit` can be more advanced, such as the following to
visit different kinds of nodes.
-```js
-visit(tree, ['comment', 'text'], function(node) {
+```js twoslash
+///
+/**
+ * @import {Root} from 'hast'
+ */
+import {visit} from 'unist-util-visit'
+
+const tree = /** @type {Root} */ (/** @type {unknown} */ (undefined))
+// @errors: 2339
+// ---cut---
+visit(tree, ['comment', 'text'], function (node) {
console.log([node.value])
})
```
@@ -204,6 +232,30 @@ visit(tree, ['comment', 'text'], function(node) {
[ '\n' ]
```
+Sadly,
+TypeScript isn’t great with arrays and discriminated unions.
+When you want to do more complex tests with TypeScript,
+it’s recommended to omit the test and use explicit `if` statements:
+
+```js twoslash
+///
+/**
+ * @import {Root} from 'hast'
+ */
+import {visit} from 'unist-util-visit'
+
+const tree = /** @type {Root} */ (/** @type {unknown} */ (undefined))
+// ---cut---
+visit(tree, function (node) {
+ if (node.type === 'comment' || node.type === 'text') {
+ console.log([node.value])
+ }
+})
+```
+
+Code that is more explicit and is understandable by TypeScript,
+is often also easier to understand by humans.
+
Read more about [`unist-util-visit`][visit] in its readme.
[visit]: https://github.com/syntax-tree/unist-util-visit
@@ -212,4 +264,4 @@ Read more about [`unist-util-visit`][visit] in its readme.
[unified]: https://github.com/unifiedjs/unified
-[rehype-parse]: https://github.com/rehypejs/rehype/tree/master/packages/rehype-parse
+[rehype-parse]: https://github.com/rehypejs/rehype/tree/HEAD/packages/rehype-parse
diff --git a/doc/learn/unified-in-the-browser.md b/doc/learn/unified-in-the-browser.md
new file mode 100644
index 000000000000..406966d85519
--- /dev/null
+++ b/doc/learn/unified-in-the-browser.md
@@ -0,0 +1,146 @@
+---
+authorGithub: wooorm
+author: Titus Wormer
+description: How to use unified in the browser
+group: guide
+modified: 2024-08-09
+published: 2024-08-09
+tags:
+ - browser
+ - dom
+ - esbuild
+ - hast
+ - mdast
+ - nlcst
+ - rehype
+ - remark
+ - retext
+ - unified
+ - web
+title: unified in the browser
+---
+
+## unified in the browser
+
+unified is many different projects that are maintained on GitHub
+and distributed mainly through npm.
+Almost all the projects can be used anywhere: in Bun, Deno, Node.js,
+on the edge, or in browsers.
+To use our projects in a browser,
+you need to do one or two things.
+And there’s different ways to go about it.
+
+### Contents
+
+* [Bundle](#bundle)
+* [CDN](#cdn)
+
+### Bundle
+
+A common way to use unified in the browser is to bundle it with a bundler.
+You perhaps know bundlers already: webpack, Rollup, or esbuild.
+You might be using one.
+Or otherwise have a favorite.
+If not,
+[esbuild][] is a good choice.
+
+Bundling is almost always a good idea.
+It gives end users a single file to download.
+Often minified.
+
+Say we have some code using unified in `example.js`:
+
+```js twoslash
+///
+// ---cut---
+import rehypeStringify from 'rehype-stringify'
+import remarkParse from 'remark-parse'
+import remarkRehype from 'remark-rehype'
+import {unified} from 'unified'
+
+const file = await unified()
+ .use(remarkParse)
+ .use(remarkRehype)
+ .use(rehypeStringify)
+ .process('Hello, *world*!')
+
+console.log(String(file))
+```
+
+And you want to use that in some HTML called `index.html`:
+
+```html
+
+
+Example
+
+```
+
+To make `example.js` work in the browser,
+you can bundle it with esbuild.
+First,
+set up a package.
+Go to the folder in your terminal and run:
+
+```sh
+npm init --yes
+npm install esbuild --save-dev rehype-stringify remark-parse remark-rehype unified
+```
+
+Then, bundle `example.js`:
+
+```sh
+npx esbuild --bundle --format=esm --minify --outfile=example.min.js example.js
+```
+
+Now, open `index.html` in a browser.
+When you open the console of your developer tools,
+you should see `Hello, world!`
+
+You probably also want to configure the target environment for the browsers
+that you support.
+That way,
+JavaScript syntax which is too new for some browsers,
+will be transformed into older JavaScript syntax that works.
+Pass the [`--target`][esbuild-target] flag to do this.
+
+### CDN
+
+If you don’t want to bundle unified yourself,
+you can use a CDN.
+
+A CDN hosts files for you.
+And they can process them for you as well.
+The nice thing is that you do not have to install and bundle things yourself.
+The downside is that you’re dependent on a particular server that you do not
+control.
+
+One such CDN is [esm.sh][esmsh].
+Like the code above,
+you can use it in a browser like this:
+
+```html
+
+
+Example
+
+```
+
+[esbuild]: https://esbuild.github.io/
+
+[esbuild-target]: https://esbuild.github.io/api/#target
+
+[esmsh]: https://esm.sh/
diff --git a/doc/learn/using-plugins.md b/doc/learn/using-plugins.md
index cacbfee9601c..4f7003d4b90d 100644
--- a/doc/learn/using-plugins.md
+++ b/doc/learn/using-plugins.md
@@ -1,15 +1,104 @@
---
+authorGithub: wooorm
+author: Titus Wormer
+description: How to use plugins and presets
group: guide
+modified: 2024-08-08
+published: 2024-08-08
+tags:
+ - plugin
+ - preset
+ - unified
+ - use
title: Using plugins
-description: Guide that shows how to use plugins and presets
-published: 2019-12-12
-modified: 2019-12-12
---
## Using plugins
-Unfortunately, this guide is not yet written.
-We’re looking for help with that, if you want please edit this file on
-[GitHub][].
+You can use plugins and presets to extend unified by calling
+[`use`][unified-use] on a processor.
-[github]: https://github.com/unifiedjs/unifiedjs.github.io/blob/src/doc/learn/using-plugins.md
+A small example is:
+
+```js twoslash
+///
+// ---cut---
+import rehypeDocument from 'rehype-document'
+import rehypeFormat from 'rehype-format'
+import rehypeStringify from 'rehype-stringify'
+import remarkParse from 'remark-parse'
+import remarkRehype from 'remark-rehype'
+import {read} from 'to-vfile'
+import {unified} from 'unified'
+
+const file = await read('example.md')
+
+await unified()
+ .use(remarkParse)
+ .use(remarkRehype)
+ .use(rehypeDocument, {title: '👋🌍'})
+ .use(rehypeFormat)
+ .use(rehypeStringify)
+ .process(file)
+
+console.log(String(file))
+```
+
+This example shows how several plugins are used.
+It shows that the order in which plugins are used is important.
+And it shows that plugins can be configured by passing options to them.
+In this case,
+`rehypeDocument` receives a `title` field.
+
+Using presets is similar:
+
+```js twoslash
+///
+// ---cut---
+import rehypeParse from 'rehype-parse'
+import rehypePresetMinify from 'rehype-preset-minify'
+import rehypeStringify from 'rehype-stringify'
+import {read} from 'to-vfile'
+import {unified} from 'unified'
+
+const file = await read('example.html')
+
+await unified()
+ .use(rehypeParse)
+ .use(rehypePresetMinify)
+ .use(rehypeStringify)
+ .process(file)
+
+console.log(String(file))
+```
+
+Presets themselves cannot receive options.
+Sometimes,
+you still want to pass options to a particular plugin in a preset.
+To configure a plugin in a preset,
+use it after the preset,
+with the correct options:
+
+```js twoslash
+///
+// ---cut---
+import rehypeMinifyWhitespace from 'rehype-minify-whitespace'
+import rehypeParse from 'rehype-parse'
+import rehypePresetMinify from 'rehype-preset-minify'
+import rehypeStringify from 'rehype-stringify'
+import {read} from 'to-vfile'
+import {unified} from 'unified'
+
+const file = await read('example.html')
+
+await unified()
+ .use(rehypeParse)
+ .use(rehypePresetMinify)
+ .use(rehypeMinifyWhitespace, {newlines: true})
+ .use(rehypeStringify)
+ .process(file)
+
+console.log(String(file))
+```
+
+[unified-use]: https://github.com/unifiedjs/unified#processoruseplugin-options
diff --git a/doc/learn/using-unified.md b/doc/learn/using-unified.md
index 1a47b4566ebe..40bfcf14fabb 100644
--- a/doc/learn/using-unified.md
+++ b/doc/learn/using-unified.md
@@ -1,166 +1,207 @@
---
+authorGithub: wooorm
+author: Titus Wormer
+description: Guide that delves into transforming markdown to HTML
group: guide
index: 2
-title: Use unified
-description: Guide that delves into transforming Markdown to HTML
-author: Titus Wormer
-authorTwitter: wooorm
+modified: 2024-08-02
+published: 2017-05-03
tags:
- - use
- - transform
- - remark
- rehype
-published: 2017-05-03
-modified: 2019-12-12
+ - remark
+ - transform
+ - use
+title: Use unified
---
## Using unified
-This guide delves into how unified can be used to transform a Markdown file to
-HTML.
-It’ll also show how to generate a table of contents, and sidestep into checking
+This guide shows how unified can be used to transform a markdown file to HTML.
+It also shows how to generate a table of contents and sidesteps into checking
prose.
> Stuck?
-> A good place to get help fast is [Spectrum][].
> Have an idea for another guide?
-> Share it on Spectrum!
+> See [`support.md`][support].
### Contents
-* [Tree transformations](#tree-transformations)
-* [Plugins](#plugins)
-* [Reporting](#reporting)
-* [Checking prose](#checking-prose)
-* [Further exercises](#further-exercises)
+* [Tree transformations](#tree-transformations)
+* [Plugins](#plugins)
+* [Reporting](#reporting)
+* [Checking prose](#checking-prose)
+* [Further exercises](#further-exercises)
### Tree transformations
-For this example, we’ll start out with Markdown content, then transform to HTML.
-We need a Markdown parser and an HTML stringifier for that.
+For this example we start out with markdown content and then turn it into HTML.
+We need something to parse markdown and something to compile (stringify) HTML
+for that.
The relevant projects are respectively [`remark-parse`][parse] and
[`rehype-stringify`][stringify].
-To transform between the two syntaxes, we’ll use
-[`remark-rehype`][remark-rehype].
-Finally, we’ll use unified itself to glue these together, and
-[`unified-stream`][unified-stream] for streaming.
+To transform between the two syntaxes we use [`remark-rehype`][remark-rehype].
+Finally, we use unified itself to glue these together.
-Let’s install those with [npm][], which comes bundled with [Node][].
+First set up a project.
+Create a folder `example`,
+enter it,
+and initialize a new package:
```sh
-$ npm install unified unified-stream remark-parse remark-rehype rehype-stringify
-/Users/tilde/example
-├── rehype-stringify@6.0.1
-├── remark-parse@7.0.2
-├── remark-rehype@5.0.0
-├── unified-stream@1.0.5
-└── unified@8.4.2
+mkdir example
+cd example
+npm init -y
```
-Let’s first create a Markdown file that we’re going to transform.
+Then make sure the project is a module so that `import` and `export` work by
+specifying `"type": "module"`:
-```markdown
-# Hello World
+```diff
+--- a/package.json
++++ b/package.json
+@@ -1,6 +1,7 @@
+ {
+ "name": "example",
+ "version": "1.0.0",
++ "type": "module",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+```
-## Table of Content
+Now let’s install the needed dependencies with [npm][], which comes bundled with
+[Node.js][node].
-## Install
+```sh
+npm install rehype-stringify remark-parse remark-rehype unified
+```
-A **example**.
+Now create a markdown file, `example.md`, that we’re going to transform.
-## Use
+```md
+# Pluto
-More `text`.
+Pluto is an dwarf planet in the Kuiper belt.
-## License
+## Contents
-MIT
+## History
+
+### Discovery
+
+In the 1840s, Urbain Le Verrier used Newtonian mechanics to predict the
+position of…
+
+### Name and symbol
+
+The name Pluto is for the Roman god of the underworld, from a Greek epithet for
+Hades…
+
+### Planet X disproved
+
+Once Pluto was found, its faintness and lack of a viewable disc cast doubt…
+
+## Orbit
+
+Pluto’s orbital period is about 248 years…
```
-Then, create an `index.js` script as well.
-It’ll transform Markdown to HTML.
-It’s hooked up to read from stdin and write to stdout.
+Then create `index.js` as well.
+It transforms markdown to HTML:
-```javascript
-var unified = require('unified')
-var stream = require('unified-stream')
-var markdown = require('remark-parse')
-var remark2rehype = require('remark-rehype')
-var html = require('rehype-stringify')
+```js twoslash
+///
+// ---cut---
+import fs from 'node:fs/promises'
+import rehypeStringify from 'rehype-stringify'
+import remarkParse from 'remark-parse'
+import remarkRehype from 'remark-rehype'
+import {unified} from 'unified'
-var processor = unified()
- .use(markdown)
- .use(remark2rehype)
- .use(html)
+const document = await fs.readFile('example.md', 'utf8')
-process.stdin.pipe(stream(processor)).pipe(process.stdout)
+const file = await unified()
+ .use(remarkParse)
+ .use(remarkRehype)
+ .use(rehypeStringify).process(document)
+
+console.log(String(file))
```
-Now, running our script with [Node][] (this uses your Shell to read
-`example.md` and write `example.html`):
+Now, running our module with [Node][]:
```sh
-$ node index.js < example.md > example.html
+node index.js
```
…gives us an `example.html` file that looks as follows:
```html
-Hello World
-Table of Content
-Install
-A example.
-Use
-More text
.
-License
-MIT
+Pluto
+Pluto is an dwarf planet in the Kuiper belt.
+Contents
+History
+Discovery
+In the 1840s, Urbain Le Verrier used Newtonian mechanics to predict the
+position of…
+Name and symbol
+The name Pluto is for the Roman god of the underworld, from a Greek epithet for
+Hades…
+Planet X disproved
+Once Pluto was found, its faintness and lack of a viewable disc cast doubt…
+Orbit
+Pluto’s orbital period is about 248 years…
```
+> 👉
> Note that [`remark-rehype`][remark-rehype] doesn’t deal with HTML inside the
-> Markdown.
-> You’ll need [`rehype-raw`][rehype-raw] if you’re planning on doing that.
+> markdown.
+> See [*HTML and remark*][html-and-remark] for more info.
-🎉 Nifty!
-It doesn’t do much yet, but we’ll get there.
-In the next section, we’ll make this more useful by introducing plugins.
+🎉
+Nifty!
+It doesn’t do much yet.
+We’ll get there.
+In the next section, we make this more useful by introducing plugins.
### Plugins
-We’re still missing some things, notably a table of contents, and proper HTML
-document structure.
+We’re still missing some things
+Notably a table of contents and proper HTML document structure.
-We can use [`remark-slug`][slug] and [`remark-toc`][toc] for the former, and
-[`rehype-document`][document] to do the latter tasks.
+We can use [`rehype-slug`][slug] and [`remark-toc`][toc] for the former
+and [`rehype-document`][document] for the latter task.
```sh
-$ npm install remark-slug remark-toc rehype-document
-/Users/tilde/example
-├── remark-slug@5.1.2
-├── remark-toc@6.0.0
-└── rehype-document@3.1.0
+npm install rehype-document rehype-slug remark-toc
```
Let’s now use those two as well, by modifying our `index.js` file:
```diff
- var unified = require('unified')
- var stream = require('unified-stream')
- var markdown = require('remark-parse')
-+var slug = require('remark-slug')
-+var toc = require('remark-toc')
- var remark2rehype = require('remark-rehype')
-+var doc = require('rehype-document')
- var html = require('rehype-stringify')
-
- var processor = unified()
- .use(markdown)
-+ .use(slug)
-+ .use(toc)
- .use(remark2rehype)
-+ .use(doc, {title: 'Contents'})
- .use(html)
-
- process.stdin.pipe(stream(processor)).pipe(process.stdout)
+--- a/index.js
++++ b/index.js
+@@ -1,14 +1,20 @@
+ import fs from 'node:fs/promises'
++import rehypeDocument from 'rehype-document'
++import rehypeSlug from 'rehype-slug'
+ import rehypeStringify from 'rehype-stringify'
+ import remarkParse from 'remark-parse'
+ import remarkRehype from 'remark-rehype'
++import remarkToc from 'remark-toc'
+ import {unified} from 'unified'
+
+ const document = await fs.readFile('example.md', 'utf8')
+
+ const file = await unified()
+ .use(remarkParse)
++ .use(remarkToc)
+ .use(remarkRehype)
++ .use(rehypeSlug)
++ .use(rehypeDocument, {title: 'Pluto'})
+ .use(rehypeStringify)
+ .process(document)
+
```
We pass options to `rehype-document`.
@@ -172,214 +213,224 @@ These are described in detail in its [`readme.md`][document].
Many other plugins accept options as well, so make sure to read through their
docs to learn more.
-> Note that remark plugins work on a Markdown tree, and rehype plugins work on
-> an HTML tree.
-> It’s important that you place your `.use` calls in the correct places.
+> 👉
+> Note that remark plugins work on a markdown tree.
+> rehype plugins work on an HTML tree.
+> It’s important that you place your `.use` calls in the correct places:
+> plugins are order sensitive!
-Now, when running our script like before, we’d get the following `example.html`
-file:
+When running our module like before,
+we’d get the following `example.html` file:
```html
-Contents
-
+Pluto
+
-Hello World
-Table of Content
+Pluto
+Pluto is an dwarf planet in the Kuiper belt.
+Contents
-Install
-A example.
-Use
-More text
.
-License
-MIT
+History
+Discovery
+In the 1840s, Urbain Le Verrier used Newtonian mechanics to predict the
+position of…
+Name and symbol
+The name Pluto is for the Roman god of the underworld, from a Greek epithet for
+Hades…
+Planet X disproved
+Once Pluto was found, its faintness and lack of a viewable disc cast doubt…
+Orbit
+Pluto’s orbital period is about 248 years…
```
-> You may noticed the document isn’t formatted nicely.
+> 👉
+> Note that the document isn’t formatted nicely.
> There’s a plugin for that though!
-> Feel free to add [`rehype-format`][rehype-format] to the plugins, below `doc`!
+> Feel free to add [`rehype-format`][rehype-format] to the plugins.
+> Right after `rehypeDocument`!
-💯 You’re acing it!
+💯
+You’re acing it!
This is getting pretty useful, right?
-In the next section, we’ll lay the groundwork for creating a report.
+In the next section,
+we lay the groundwork for creating a report.
### Reporting
-Before we check some prose (yes, we’re getting there), we’ll first switch up our
-`index.js` file to print a pretty report (we’ll fill it in the next section).
+Before we check some prose, let’s first switch up our `index.js` file to print
+a pretty report.
We can use [`to-vfile`][to-vfile] to read and write virtual files from the file
-system, and we can use [`vfile-reporter`][reporter] to report messages relating
-to those files.
+system.
+Then we can use [`vfile-reporter`][reporter] to report messages relating to
+those files.
Let’s install those.
```sh
-$ npm install to-vfile vfile-reporter
-/Users/tilde/example
-├── to-vfile@6.0.0
-└── vfile-reporter@6.0.0
+npm install to-vfile vfile-reporter
```
-…and now unhook stdin/stdout from our example and use the file-system instead,
-like so:
+…and then use vfile in our example instead, like so:
```diff
- var unified = require('unified')
--var stream = require('unified-stream')
-+var vfile = require('to-vfile')
-+var report = require('vfile-reporter')
- var markdown = require('remark-parse')
- var slug = require('remark-slug')
- var toc = require('remark-toc')
- var remark2rehype = require('remark-rehype')
- var doc = require('rehype-document')
- var html = require('rehype-stringify')
-
- var processor = unified()
- .use(markdown)
- .use(slug)
- .use(toc)
- .use(remark2rehype)
- .use(doc, {title: 'Contents'})
- .use(html)
-
--process.stdin.pipe(stream(processor)).pipe(process.stdout)
-+processor.process(vfile.readSync('example.md'), function(err, file) {
-+ if (err) throw err
-+ console.error(report(file))
-+ file.extname = '.html'
-+ vfile.writeSync(file)
-+})
+--- a/index.js
++++ b/index.js
+@@ -1,21 +1,24 @@
+-import fs from 'node:fs/promises'
+ import rehypeDocument from 'rehype-document'
+ import rehypeSlug from 'rehype-slug'
+ import rehypeStringify from 'rehype-stringify'
+ import remarkParse from 'remark-parse'
+ import remarkRehype from 'remark-rehype'
+ import remarkToc from 'remark-toc'
++import {read, write} from 'to-vfile'
+ import {unified} from 'unified'
++import {reporter} from 'vfile-reporter'
+
+-const document = await fs.readFile('example.md', 'utf8')
++const file = await read('example.md')
+
+-const file = await unified()
++await unified()
+ .use(remarkParse)
+ .use(remarkToc)
+ .use(remarkRehype)
+ .use(rehypeSlug)
+ .use(rehypeDocument, {title: 'Pluto'})
+ .use(rehypeStringify)
+- .process(document)
++ .process(file)
+
+-console.log(String(file))
++console.error(reporter(file))
++file.extname = '.html'
++await write(file)
```
-If we now run our script on its own, without shell redirects, we get a report
-showing everything’s fine:
+If we now run our module on its own we get a report showing everything’s fine:
```sh
$ node index.js
example.md: no issues found
```
-But everything’s not fine, there’s a typo in the Markdown!
+But everything’s not fine: there’s a typo in the markdown!
The next section shows how to detect prose errors by adding retext.
### Checking prose
-I did notice a typo in there, so let’s check some prose to prevent that from
-happening in the future.
+I did notice a typo in there.
+So let’s check some prose to prevent that from happening in the future.
We can use retext and its ecosystem for our natural language parsing.
-As we’re writing in English, we use [`retext-english`][english] specifically to
-parse English natural language.
-The problem in our `example.md` file is that it has `a example` instead of
-`an example`, which is conveniently checked for by
+As we’re writing in English,
+we use [`retext-english`][english] specifically to parse English natural
+language.
+The problem in our `example.md` file is that it has `an dwarf planet` instead
+of `a dwarf planet`,
+which is conveniently checked for by
[`retext-indefinite-article`][indefinite-article].
-To bridge from markup to prose, we’ll use [`remark-retext`][remark-retext].
-First, let’s install these dependencies as well.
+To bridge from markup to prose we use [`remark-retext`][remark-retext].
+Let’s install these dependencies as well.
```sh
-$ npm install remark-retext retext-english retext-indefinite-article
-/Users/tilde/example
-├── remark-retext@3.1.3
-├── retext-english@3.0.4
-└── retext-indefinite-article@1.1.7
+npm install remark-retext retext-english retext-indefinite-article
```
…and change our `index.js` like so:
```diff
- var unified = require('unified')
- var vfile = require('to-vfile')
- var report = require('vfile-reporter')
- var markdown = require('remark-parse')
- var slug = require('remark-slug')
- var toc = require('remark-toc')
-+var remark2retext = require('remark-retext')
-+var english = require('retext-english')
-+var indefiniteArticle = require('retext-indefinite-article')
- var remark2rehype = require('remark-rehype')
- var doc = require('rehype-document')
- var html = require('rehype-stringify')
-
- var processor = unified()
- .use(markdown)
-+ .use(
-+ remark2retext,
-+ unified()
-+ .use(english)
-+ .use(indefiniteArticle)
-+ )
- .use(slug)
- .use(toc)
- .use(remark2rehype)
- .use(doc, {title: 'Contents'})
- .use(html)
-
- processor.process(vfile.readSync('example.md'), function(err, file) {
- if (err) throw err
- console.error(report(file))
- file.extname = '.html'
- vfile.writeSync(file)
- })
+--- a/index.js
++++ b/index.js
+@@ -3,7 +3,10 @@ import rehypeSlug from 'rehype-slug'
+ import rehypeStringify from 'rehype-stringify'
+ import remarkParse from 'remark-parse'
+ import remarkRehype from 'remark-rehype'
++import remarkRetext from 'remark-retext'
+ import remarkToc from 'remark-toc'
++import retextEnglish from 'retext-english'
++import retextIndefiniteArticle from 'retext-indefinite-article'
+ import {read, write} from 'to-vfile'
+ import {unified} from 'unified'
+ import {reporter} from 'vfile-reporter'
+@@ -12,6 +15,8 @@ const file = await read('example.md')
+
+ await unified()
+ .use(remarkParse)
++ // @ts-expect-error: fine.
++ .use(remarkRetext, unified().use(retextEnglish).use(retextIndefiniteArticle))
+ .use(remarkToc)
+ .use(remarkRehype)
+ .use(rehypeSlug)
```
-As the code shows, `remark-retext` receives another `unified` middleware
-pipeline.
+As the code shows,
+`remark-retext` receives another `unified` pipeline.
A natural language pipeline.
-The plugin will transform the origin syntax (Markdown) with the given pipeline’s
-parser.
-Then, it’ll run the attached plugins on the natural language syntax tree.
+The plugin will transform the origin syntax (markdown) with the parser defined
+on the given pipeline.
+Then it runs the attached plugins on the natural language syntax tree.
-Now, when running our script one final time:
+Now when running our module one final time:
```sh
$ node index.js
example.md
- 7:1-7:2 warning Use `An` before `example`, not `A` retext-indefinite-article retext-indefinite-article
+3:10-3:12 warning Unexpected article `an` before `dwarf`, expected `a` retext-indefinite-article retext-indefinite-article
⚠ 1 warning
```
-…we’ll get a useful message.
+…we get a useful message.
-💃 You’ve got a really cool system set up already, nicely done!
+💃
+You’ve got a really cool system set up already.
+Nicely done!
That’s a wrap though, check out the next section for further exercises and
resources.
### Further exercises
-Finally, check out the lists of available plugins for [retext][retext-plugins],
-[remark][remark-plugins], and [rehype][rehype-plugins], and try some of them
-out.
+Finally,
+check out the lists of available plugins for [retext][retext-plugins],
+[remark][remark-plugins],
+and [rehype][rehype-plugins],
+and try some of them out.
If you haven’t already, check out the other articles in the
[learn section][learn]!
-[spectrum]: https://spectrum.chat/unified
+[support]: https://github.com/unifiedjs/.github/blob/main/support.md
-[parse]: https://github.com/remarkjs/remark/tree/master/packages/remark-parse
+[parse]: https://github.com/remarkjs/remark/tree/HEAD/packages/remark-parse
-[stringify]: https://github.com/rehypejs/rehype/tree/master/packages/rehype-stringify
+[stringify]: https://github.com/rehypejs/rehype/tree/HEAD/packages/rehype-stringify
[remark-rehype]: https://github.com/remarkjs/remark-rehype
[npm]: https://www.npmjs.com
-[node]: https://nodejs.org
+[node]: https://nodejs.org/en
-[slug]: https://github.com/remarkjs/remark-slug
+[slug]: https://github.com/rehypejs/rehype-slug
[toc]: https://github.com/remarkjs/remark-toc
@@ -389,22 +440,20 @@ If you haven’t already, check out the other articles in the
[reporter]: https://github.com/vfile/vfile-reporter
-[unified-stream]: https://github.com/unifiedjs/unified-stream
-
-[english]: https://github.com/retextjs/retext/tree/master/packages/retext-english
+[english]: https://github.com/retextjs/retext/tree/HEAD/packages/retext-english
[indefinite-article]: https://github.com/retextjs/retext-indefinite-article
[remark-retext]: https://github.com/remarkjs/remark-retext
-[retext-plugins]: https://github.com/retextjs/retext/blob/master/doc/plugins.md
-
-[remark-plugins]: https://github.com/remarkjs/remark/blob/master/doc/plugins.md
+[retext-plugins]: https://github.com/retextjs/retext/blob/HEAD/doc/plugins.md
-[rehype-plugins]: https://github.com/rehypejs/rehype/blob/master/doc/plugins.md
+[remark-plugins]: https://github.com/remarkjs/remark/blob/HEAD/doc/plugins.md
-[rehype-raw]: https://github.com/rehypejs/rehype-raw
+[rehype-plugins]: https://github.com/rehypejs/rehype/blob/HEAD/doc/plugins.md
[rehype-format]: https://github.com/rehypejs/rehype-format
[learn]: /learn/
+
+[html-and-remark]: /learn/recipe/remark-html/
diff --git a/doc/showcase.yml b/doc/showcase.yml
index 6746e1488784..9db66e4b1922 100644
--- a/doc/showcase.yml
+++ b/doc/showcase.yml
@@ -1,5 +1,5 @@
- title: Prettier
- short: Uses unified to format Markdown
+ short: Uses unified to format markdown
url: https://prettier.io
gh: prettier/prettier
src: /image/prettier.png
@@ -24,7 +24,7 @@
gh: get-alex/alex
src: /image/alex.png
- title: MDX
- short: Uses unified to combine Markdown with JSX
+ short: Uses unified to combine markdown with JSX
url: https://mdxjs.com
gh: mdx-js/mdx
src: /image/mdx.png
@@ -33,11 +33,11 @@
url: https://mdx-deck.jxnblk.com
gh: jxnblk/mdx-deck
src: /image/mdx-deck.png
-- title: Netlify
+- title: Decap
short: Uses unified for their CMS
- url: https://www.netlifycms.org
- gh: netlify/netlify-cms
- src: /image/netlify-cms.png
+ url: https://decapcms.org/
+ gh: decaporg/decap-cms
+ src: /image/decap-cms.png
- title: freeCodeCamp
short: Uses unifed to parse challenges
url: https://www.freecodecamp.org
@@ -53,7 +53,7 @@
gh: storybookjs/storybook
src: /image/storybook.png
- title: ESLint
- short: Uses unified to check JavaScript in Markdown
+ short: Uses unified to check JavaScript in markdown
url: https://eslint.org
gh: eslint/eslint-plugin-markdown
src: /image/eslint.png
@@ -108,8 +108,8 @@
gh: documentationjs/documentation
src: /image/documentation.png
- title: react-markdown
- short: Uses unified for parse Markdown
- url: https://rexxars.github.io/react-markdown/
+ short: Uses unified for parse markdown
+ url: https://remarkjs.github.io/react-markdown/
gh: rexxars/react-markdown
src: /image/react-markdown.png
- title: Readability
@@ -117,18 +117,8 @@
url: https://wooorm.com/readability/
gh: wooorm/readability
src: /image/readability.png
-- title: PrettyHTML
- short: Uses unified to format content
- url: https://prettyhtml.netlify.com
- gh: Prettyhtml/prettyhtml
- src: /image/prettyhtml.png
-- title: Nextein
- short: Uses unified to generate static sites
- url: https://nextein.elmasse.io
- gh: elmasse/nextein/
- src: /image/nextein.png
- title: nteract
- short: Uses unified for math in Markdown
+ short: Uses unified for math in markdown
url: https://nteract.io
gh: nteract/nteract
src: /image/nteract.png
diff --git a/generate/asset.js b/generate/asset.js
index e35a5c7b8e4a..ea67ec38567e 100644
--- a/generate/asset.js
+++ b/generate/asset.js
@@ -1,176 +1,143 @@
-var fs = require('fs')
-var path = require('path')
-var {promisify} = require('util')
-var glob = require('glob')
-var sharp = require('sharp')
-var pAll = require('p-all')
-var mkdirp = require('vfile-mkdirp')
-var trough = require('trough')
-var vfile = require('to-vfile')
-var reporter = require('vfile-reporter')
-var browserify = require('browserify')
-var postcss = require('postcss')
-var postcssPresetEnv = require('postcss-preset-env')
-var cssnano = require('cssnano')
-var pack = require('../package.json')
-
-require('dotenv').config()
-
-var externals = {
- '.css': trough().use(transformCss),
- '.js': trough().use(bundleJs)
+import assert from 'node:assert/strict'
+import fs from 'node:fs/promises'
+import process from 'node:process'
+import cssnano from 'cssnano'
+import dotenv from 'dotenv'
+import esbuild from 'esbuild'
+import {glob} from 'glob'
+import postcssPresetEnv from 'postcss-preset-env'
+import postcss from 'postcss'
+import sharp from 'sharp'
+import {read, write} from 'to-vfile'
+import {reporter} from 'vfile-reporter'
+import {VFile} from 'vfile'
+
+dotenv.config()
+
+const postCssProcessor = postcss(
+ postcssPresetEnv({stage: 0}),
+ cssnano({preset: 'advanced'})
+)
+
+/** @type {Record Promise | VFile>>} */
+const externals = {
+ '.css': transformCss,
+ '.js': transformJs
}
if (process.env.UNIFIED_OPTIMIZE_IMAGES) {
- externals['.png'] = trough().use(transformPng)
+ externals['.png'] = transformPng
} else {
console.log(
'Not optimizing images: set `UNIFIED_OPTIMIZE_IMAGES=1` to turn on'
)
}
-var processPipeline = trough()
- .use(vfile.read)
- .use(processFile)
- .use(move)
- .use(mkdir)
- .use(vfile.write)
-
-var copyPipeline = trough()
- .use(move)
- .use(mkdir)
- .use(copy)
-
-var imagePipeline = trough()
- .use(move)
- .use(mkdir)
- .use(vfile.write)
- .use(print)
-
-var filePipeline = trough()
- .use(function(fp, next) {
- var file = vfile(fp)
- var ext = file.extname
- var pipeline = ext in externals ? processPipeline : copyPipeline
- pipeline.run(file, next)
- })
- .use(print)
-
-trough()
- .use(glob)
- .use(function(paths, done) {
- var run = promisify(filePipeline.run)
-
- pAll(
- paths.map(path => () => run(path)),
- {concurrency: 3}
- ).then(files => done(null, files), done)
- })
- .use(function(files, next) {
- var contents = new URL(pack.homepage).host + '\n'
- vfile.write({dirname: 'build', basename: 'CNAME', contents: contents}, next)
- })
- .run('asset/**/*.*', function(err) {
- if (err) {
- console.error(reporter(err))
- process.exitCode = 1
+const paths = await glob('asset/**/*.*')
+
+for (const fp of paths) {
+ const file = new VFile({path: fp})
+ const extname = file.extname
+
+ if (extname && extname in externals) {
+ const pipeline = externals[extname]
+ const result = await pipeline(file)
+ const files = Array.isArray(result) ? result : [result]
+ for (const file of files) {
+ assert(file.dirname)
+ file.dirname = 'build' + file.dirname.replace(/^asset/, '')
+ await fs.mkdir(file.dirname, {recursive: true})
+ await write(file)
+ file.stored = true
+ console.error(reporter(file))
}
- })
-
-function processFile(file, next) {
- externals[file.extname].run(file, function(err) {
- file.processed = true
- next(err)
- })
+ } else {
+ assert(file.dirname)
+ file.dirname = 'build/' + file.dirname.replace(/^asset\//, '')
+ await fs.mkdir(file.dirname, {recursive: true})
+ await fs.copyFile(file.history[0], file.path)
+ file.stored = true
+ console.error(reporter(file))
+ }
}
-function move(file) {
- var sep = path.sep
- file.dirname = ['build'].concat(file.dirname.split(sep).slice(1)).join(sep)
+/**
+ * @param {VFile} file
+ * @returns {Promise}
+ */
+async function transformCss(file) {
+ await read(file)
+ const result = await postCssProcessor.process(String(file), {from: file.path})
+ file.value = result.css
+ return file
}
-function mkdir(file, next) {
- mkdirp(file, done)
- function done(err) {
- next(err)
- }
-}
+/**
+ * @param {VFile} file
+ * @returns {Promise}
+ */
+async function transformJs(file) {
+ const result = await esbuild.build({
+ bundle: true,
+ entryPoints: [file.path],
+ minify: true,
+ write: false
+ })
-function copy(file, next) {
- fs.copyFile(file.history[0], file.path, done)
- function done(err) {
- next(err)
+ const logs = [
+ ...(await esbuild.formatMessages(result.errors, {kind: 'error'})),
+ ...(await esbuild.formatMessages(result.warnings, {kind: 'warning'}))
+ ]
+
+ if (logs.length > 0) {
+ console.error(logs.join('\n'))
}
+
+ assert.equal(result.outputFiles.length, 1)
+ const output = result.outputFiles[0]
+ file.value = output.contents
+ return file
}
-function print(file) {
- file.stored = true
+/**
+ * @param {VFile} file
+ * @returns {Promise>}
+ */
+async function transformPng(file) {
+ // Note: see `rehype-pictures` for the inverse.
+ const sizes = [200, 600, 1200, 2000]
+ const options = {
+ png: {compressionLevel: 9, quality: 50},
+ webp: {alphaQuality: 50, quality: 50}
+ }
+ const formats = /** @type {Array} */ (
+ Object.keys(options)
+ )
- // Clear memory.
- file.contents = null
- console.error(reporter(file))
-}
+ await read(file)
+ const sharpPipeline = sharp(file.value)
+ const metadata = await sharpPipeline.metadata()
+ assert(metadata.width)
-function transformCss(file) {
- return postcss(postcssPresetEnv({stage: 0}), cssnano({preset: 'advanced'}))
- .process(file.toString('utf8'), {from: file.path})
- .then(function(result) {
- file.contents = result.css
- })
-}
+ const files = [file]
-function bundleJs(file, next) {
- browserify(file.path)
- .plugin('tinyify')
- .bundle(done)
+ for (const size of sizes) {
+ if (size > metadata.width) continue
- function done(err, buf) {
- if (buf) {
- file.contents = String(buf)
- }
+ for (const format of formats) {
+ const buf = await sharpPipeline
+ .clone()
+ .resize(size)
+ [format](options[format])
+ .toBuffer()
- next(err)
- }
-}
+ const copy = new VFile({path: file.path, value: buf})
-function transformPng(file, next) {
- var sizes = [200, 600, 1200, 2000]
- var formats = ['webp', 'png']
- var options = {
- webp: {quality: 50, alphaQuality: 50},
- png: {quality: 50, compressionLevel: 9}
+ copy.stem += '-' + size
+ copy.extname = '.' + format
+ files.push(copy)
+ }
}
- var run = promisify(imagePipeline.run)
- var pipeline = sharp(file.contents)
-
- pipeline
- .metadata()
- .then(metadata =>
- pAll(
- sizes
- .flatMap(size => formats.map(format => ({size, format})))
- .filter(d => d.size <= metadata.width)
- .map(file => () => one(file).then(d => run(d))),
- {concurrency: 3}
- )
- )
- .then(() => next(null, file), next)
-
- function one(media) {
- return pipeline
- .clone()
- .resize(media.size)
- [media.format](options[media.format])
- .toBuffer()
- .then(buf => {
- var copy = vfile(file.path)
-
- copy.contents = buf
- copy.stem += '-' + media.size
- copy.extname = '.' + media.format
-
- return copy
- })
- }
+ return files
}
diff --git a/generate/atom/box/item.js b/generate/atom/box/item.js
index e9005ac53a99..960d1427f6fc 100644
--- a/generate/atom/box/item.js
+++ b/generate/atom/box/item.js
@@ -1,13 +1,30 @@
-'use strict'
+/**
+ * @import {ElementContent, Element, Root} from 'hast'
+ * @import {BuildVisitor} from 'unist-util-visit'
+ */
-var h = require('hastscript')
-var block = require('../macro/block')
+import {h} from 'hastscript'
+import {SKIP, visit} from 'unist-util-visit'
+import {block} from '../macro/block.js'
-module.exports = item
+/**
+ * @param {string} href
+ * @param {Array | ElementContent | undefined} [main]
+ * @param {Array | ElementContent | undefined} [footer]
+ * @returns {Element}
+ */
+export function item(href, main, footer) {
+ const box = h('a.box', {href}, structuredClone(main))
-function item(href, main, footer) {
- return block(
- h('a.box', {href}, main),
- footer ? h('ol.row', footer) : undefined
- )
+ visit(box, 'element', cleanNestedLinks)
+
+ return block(box, footer ? h('ol.row', {}, footer) : undefined)
+
+ /** @type {BuildVisitor} */
+ function cleanNestedLinks(node, index, parent) {
+ if (parent && typeof index === 'number' && node.tagName === 'a') {
+ parent.children.splice(index, 1, ...node.children)
+ return [SKIP, index]
+ }
+ }
}
diff --git a/generate/atom/box/list.js b/generate/atom/box/list.js
index 2bcfbd13abe4..a688e63dee2f 100644
--- a/generate/atom/box/list.js
+++ b/generate/atom/box/list.js
@@ -1,10 +1,18 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {Map, Options} from '../macro/list.js'
+ */
-var h = require('hastscript')
-var list = require('../macro/list')
+import {h} from 'hastscript'
+import {list as baseList} from '../macro/list.js'
-module.exports = boxes
-
-function boxes(names, map, options) {
- return h('.block', h('ol.flow.boxes', list(names, map, options)))
+/**
+ * @template T
+ * @param {ReadonlyArray} names
+ * @param {Map} map
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(names, map, options) {
+ return h('.block', {}, h('ol.flow.boxes', {}, baseList(names, map, options)))
}
diff --git a/generate/atom/box/more.js b/generate/atom/box/more.js
index ef7e6d52694c..7731a42fce2f 100644
--- a/generate/atom/box/more.js
+++ b/generate/atom/box/more.js
@@ -1,10 +1,16 @@
-'use strict'
+/**
+ * @import {Child} from 'hastscript'
+ * @import {Element} from 'hast'
+ */
-var h = require('hastscript')
-var block = require('../macro/block')
+import {h} from 'hastscript'
+import {block} from '../macro/block.js'
-module.exports = more
-
-function more(href, children) {
- return block(h('a.box.more', {href}, h('.column', h('p', children))))
+/**
+ * @param {string} href
+ * @param {Child} children
+ * @returns {Element}
+ */
+export function more(href, children) {
+ return block(h('a.box.more', {href}, h('.column', {}, h('p', {}, children))))
}
diff --git a/generate/atom/card/item.js b/generate/atom/card/item.js
index 294397d5726e..be1f49dc24ee 100644
--- a/generate/atom/card/item.js
+++ b/generate/atom/card/item.js
@@ -1,13 +1,19 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var h = require('hastscript')
-var block = require('../macro/block')
+import {h} from 'hastscript'
+import {block} from '../macro/block.js'
-module.exports = item
-
-function item(href, main, footer) {
+/**
+ * @param {string} href
+ * @param {Array | ElementContent} main
+ * @param {Array | ElementContent | undefined} [footer]
+ * @returns {Element}
+ */
+export function item(href, main, footer) {
return block(
h('a.card', {href}, main),
- footer ? h('ol.row', footer) : undefined
+ footer ? h('ol.row', {}, footer) : undefined
)
}
diff --git a/generate/atom/card/list.js b/generate/atom/card/list.js
index c1b784cf8ebb..982e8f778c9f 100644
--- a/generate/atom/card/list.js
+++ b/generate/atom/card/list.js
@@ -1,10 +1,22 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {Map, Options} from '../macro/list.js'
+ */
-var h = require('hastscript')
-var list = require('../macro/list')
+import {h} from 'hastscript'
+import {list as baseList} from '../macro/list.js'
-module.exports = cards
-
-function cards(names, map, options) {
- return h('.block-big', h('ol.flow-big.cards', list(names, map, options)))
+/**
+ * @template T
+ * @param {ReadonlyArray} values
+ * @param {Map} map
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(values, map, options) {
+ return h(
+ '.block-big',
+ {},
+ h('ol.flow-big.cards', {}, baseList(values, map, options))
+ )
}
diff --git a/generate/atom/card/more.js b/generate/atom/card/more.js
index 72894c6a06d9..f8cf5befe5aa 100644
--- a/generate/atom/card/more.js
+++ b/generate/atom/card/more.js
@@ -1,10 +1,16 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var h = require('hastscript')
-var block = require('../macro/block')
+import {h} from 'hastscript'
+import {block} from '../macro/block.js'
-module.exports = more
-
-function more(href, children) {
- return block(h('a.card.more', {href}, h('.column', h('p', children))))
+/**
+ *
+ * @param {string} href
+ * @param {Array} children
+ * @returns {Element}
+ */
+export function more(href, children) {
+ return block(h('a.card.more', {href}, h('.column', {}, h('p', {}, children))))
}
diff --git a/generate/atom/icon/downloads.js b/generate/atom/icon/downloads.js
index 74f8541fc3e7..30c70cc6e147 100644
--- a/generate/atom/icon/downloads.js
+++ b/generate/atom/icon/downloads.js
@@ -1,23 +1,26 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = downloads
-
-function downloads() {
+/**
+ * @returns {Element}
+ */
+export function downloads() {
return s(
'svg.icon',
{
ariaLabel: 'downloads',
- viewBox: [0, 0, 16, 16],
- width: 18,
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [0, 0, 16, 16],
+ width: 18
},
s('path', {
- fill: 'currentcolor',
+ d: 'M7.46967 10.7803C7.76256 11.0732 8.23744 11.0732 8.53033 10.7803L12.2803 7.03033C12.5732 6.73744 12.5732 6.26256 12.2803 5.96967C11.9874 5.67678 11.5126 5.67678 11.2197 5.96967L8.75 8.43934L8.75 1.75C8.75 1.33579 8.41421 1 8 1C7.58579 1 7.25 1.33579 7.25 1.75L7.25 8.43934L4.78033 5.96967C4.48744 5.67678 4.01256 5.67678 3.71967 5.96967C3.42678 6.26256 3.42678 6.73744 3.71967 7.03033L7.46967 10.7803ZM3.75 13.0001C3.33579 13.0001 3 13.3359 3 13.7501C3 14.1643 3.33579 14.5001 3.75 14.5001H12.25C12.6642 14.5001 13 14.1643 13 13.7501C13 13.3359 12.6642 13.0001 12.25 13.0001H3.75Z',
fillRule: 'evenodd',
- d: 'M7.46967 10.7803C7.76256 11.0732 8.23744 11.0732 8.53033 10.7803L12.2803 7.03033C12.5732 6.73744 12.5732 6.26256 12.2803 5.96967C11.9874 5.67678 11.5126 5.67678 11.2197 5.96967L8.75 8.43934L8.75 1.75C8.75 1.33579 8.41421 1 8 1C7.58579 1 7.25 1.33579 7.25 1.75L7.25 8.43934L4.78033 5.96967C4.48744 5.67678 4.01256 5.67678 3.71967 5.96967C3.42678 6.26256 3.42678 6.73744 3.71967 7.03033L7.46967 10.7803ZM3.75 13.0001C3.33579 13.0001 3 13.3359 3 13.7501C3 14.1643 3.33579 14.5001 3.75 14.5001H12.25C12.6642 14.5001 13 14.1643 13 13.7501C13 13.3359 12.6642 13.0001 12.25 13.0001H3.75Z'
+ fill: 'currentcolor'
})
)
}
diff --git a/generate/atom/icon/gh.js b/generate/atom/icon/gh.js
index 6033ddeb3db8..2f2d96344620 100644
--- a/generate/atom/icon/gh.js
+++ b/generate/atom/icon/gh.js
@@ -1,25 +1,28 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = gh
-
-function gh() {
+/**
+ * @returns {Element}
+ */
+export function gh() {
return s(
'svg.icon',
{
ariaLabel: 'GitHub',
- viewBox: [0, 0, 16, 16],
- width: 18,
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [0, 0, 16, 16],
+ width: 18
},
[
s('path', {
- fill: 'currentcolor',
+ d: 'M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z',
clipRule: 'evenodd',
fillRule: 'evenodd',
- d: 'M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z'
+ fill: 'currentcolor'
})
]
)
diff --git a/generate/atom/icon/license.js b/generate/atom/icon/license.js
index b426fb7bdad2..f8cc1671009d 100644
--- a/generate/atom/icon/license.js
+++ b/generate/atom/icon/license.js
@@ -1,24 +1,27 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = license
-
-function license() {
+/**
+ * @returns {Element}
+ */
+export function license() {
return s(
'svg.icon',
{
ariaLabel: 'License',
- viewBox: [0, 0, 16, 16],
- width: 18,
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [0, 0, 16, 16],
+ width: 18
},
[
s('path', {
- fill: 'currentcolor',
+ d: 'M8.75003 0.75C8.75003 0.335786 8.41424 0 8.00003 0C7.58582 0 7.25003 0.335786 7.25003 0.75V2H6.26559C5.96105 2 5.66177 2.07948 5.39735 2.23057L4.1085 2.96706C4.07073 2.98865 4.02797 3 3.98447 3H1.75003C1.33582 3 1.00003 3.33579 1.00003 3.75C1.00003 4.16421 1.33582 4.5 1.75003 4.5H2.17758L0.0660806 9.19223C-0.0617883 9.47638 -0.000641479 9.81 0.219692 10.0303L0.750022 9.5C0.219692 10.0303 0.219954 10.0306 0.220219 10.0309L0.220765 10.0314L0.221913 10.0325L0.224433 10.035L0.230373 10.0408L0.245855 10.0556C0.257778 10.0668 0.272924 10.0806 0.2913 10.0967C0.328031 10.1288 0.377797 10.17 0.440647 10.2172C0.566243 10.3114 0.744981 10.4298 0.977112 10.5458C1.44383 10.7792 2.12045 11 3.00002 11C3.8796 11 4.55622 10.7792 5.02293 10.5458C5.25506 10.4298 5.4338 10.3114 5.5594 10.2172C5.62225 10.17 5.67201 10.1288 5.70874 10.0967C5.72712 10.0806 5.74227 10.0668 5.75419 10.0556L5.76967 10.0408L5.77561 10.035L5.77813 10.0325L5.77928 10.0314L5.77982 10.0309C5.78009 10.0306 5.78035 10.0303 5.25002 9.5L5.78035 10.0303C6.00069 9.81 6.06183 9.47638 5.93396 9.19223L3.82246 4.5H3.98447C4.28901 4.5 4.58829 4.42052 4.85271 4.26943L6.14156 3.53294C6.17933 3.51135 6.22209 3.5 6.26559 3.5H7.25003V13H4.75003C4.33582 13 4.00003 13.3358 4.00003 13.75C4.00003 14.1642 4.33582 14.5 4.75003 14.5H11.25C11.6642 14.5 12 14.1642 12 13.75C12 13.3358 11.6642 13 11.25 13H8.75003V3.5H9.73447C9.77797 3.5 9.82073 3.51135 9.8585 3.53294L11.1474 4.26943C11.4118 4.42052 11.711 4.5 12.0156 4.5H12.1776L10.0661 9.19223C9.93821 9.47638 9.99936 9.81 10.2197 10.0303L10.75 9.5C10.2197 10.0303 10.22 10.0306 10.2202 10.0309L10.2208 10.0314L10.2219 10.0325L10.2244 10.035L10.2304 10.0408L10.2459 10.0556C10.2578 10.0668 10.2729 10.0806 10.2913 10.0967C10.328 10.1288 10.3778 10.17 10.4406 10.2172C10.5662 10.3114 10.745 10.4298 10.9771 10.5458C11.4438 10.7792 12.1204 11 13 11C13.8796 11 14.5562 10.7792 15.0229 10.5458C15.2551 10.4298 15.4338 10.3114 15.5594 10.2172C15.6222 10.17 15.672 10.1288 15.7087 10.0967C15.7271 10.0806 15.7423 10.0668 15.7542 10.0556L15.7638 10.0465L15.7697 10.0408L15.7756 10.035L15.7781 10.0325L15.7793 10.0314L15.7798 10.0309C15.7801 10.0306 15.7804 10.0303 15.25 9.5L15.7804 10.0303C16.0007 9.81 16.0618 9.47638 15.934 9.19223L13.8225 4.5H14.25C14.6642 4.5 15 4.16421 15 3.75C15 3.33579 14.6642 3 14.25 3H12.0156C11.9721 3 11.9293 2.98865 11.8916 2.96706L10.6027 2.23057C10.3383 2.07948 10.039 2 9.73447 2H8.75003V0.75ZM1.69523 9.22719C1.9801 9.36201 2.41307 9.5 3.00002 9.5C3.58698 9.5 4.01994 9.36201 4.30482 9.22719L3.00002 6.32764L1.69523 9.22719ZM11.6952 9.22719C11.9801 9.36201 12.4131 9.5 13 9.5C13.587 9.5 14.0199 9.36201 14.3048 9.22719L13 6.32764L11.6952 9.22719Z',
fillRule: 'evenodd',
- d: 'M8.75003 0.75C8.75003 0.335786 8.41424 0 8.00003 0C7.58582 0 7.25003 0.335786 7.25003 0.75V2H6.26559C5.96105 2 5.66177 2.07948 5.39735 2.23057L4.1085 2.96706C4.07073 2.98865 4.02797 3 3.98447 3H1.75003C1.33582 3 1.00003 3.33579 1.00003 3.75C1.00003 4.16421 1.33582 4.5 1.75003 4.5H2.17758L0.0660806 9.19223C-0.0617883 9.47638 -0.000641479 9.81 0.219692 10.0303L0.750022 9.5C0.219692 10.0303 0.219954 10.0306 0.220219 10.0309L0.220765 10.0314L0.221913 10.0325L0.224433 10.035L0.230373 10.0408L0.245855 10.0556C0.257778 10.0668 0.272924 10.0806 0.2913 10.0967C0.328031 10.1288 0.377797 10.17 0.440647 10.2172C0.566243 10.3114 0.744981 10.4298 0.977112 10.5458C1.44383 10.7792 2.12045 11 3.00002 11C3.8796 11 4.55622 10.7792 5.02293 10.5458C5.25506 10.4298 5.4338 10.3114 5.5594 10.2172C5.62225 10.17 5.67201 10.1288 5.70874 10.0967C5.72712 10.0806 5.74227 10.0668 5.75419 10.0556L5.76967 10.0408L5.77561 10.035L5.77813 10.0325L5.77928 10.0314L5.77982 10.0309C5.78009 10.0306 5.78035 10.0303 5.25002 9.5L5.78035 10.0303C6.00069 9.81 6.06183 9.47638 5.93396 9.19223L3.82246 4.5H3.98447C4.28901 4.5 4.58829 4.42052 4.85271 4.26943L6.14156 3.53294C6.17933 3.51135 6.22209 3.5 6.26559 3.5H7.25003V13H4.75003C4.33582 13 4.00003 13.3358 4.00003 13.75C4.00003 14.1642 4.33582 14.5 4.75003 14.5H11.25C11.6642 14.5 12 14.1642 12 13.75C12 13.3358 11.6642 13 11.25 13H8.75003V3.5H9.73447C9.77797 3.5 9.82073 3.51135 9.8585 3.53294L11.1474 4.26943C11.4118 4.42052 11.711 4.5 12.0156 4.5H12.1776L10.0661 9.19223C9.93821 9.47638 9.99936 9.81 10.2197 10.0303L10.75 9.5C10.2197 10.0303 10.22 10.0306 10.2202 10.0309L10.2208 10.0314L10.2219 10.0325L10.2244 10.035L10.2304 10.0408L10.2459 10.0556C10.2578 10.0668 10.2729 10.0806 10.2913 10.0967C10.328 10.1288 10.3778 10.17 10.4406 10.2172C10.5662 10.3114 10.745 10.4298 10.9771 10.5458C11.4438 10.7792 12.1204 11 13 11C13.8796 11 14.5562 10.7792 15.0229 10.5458C15.2551 10.4298 15.4338 10.3114 15.5594 10.2172C15.6222 10.17 15.672 10.1288 15.7087 10.0967C15.7271 10.0806 15.7423 10.0668 15.7542 10.0556L15.7638 10.0465L15.7697 10.0408L15.7756 10.035L15.7781 10.0325L15.7793 10.0314L15.7798 10.0309C15.7801 10.0306 15.7804 10.0303 15.25 9.5L15.7804 10.0303C16.0007 9.81 16.0618 9.47638 15.934 9.19223L13.8225 4.5H14.25C14.6642 4.5 15 4.16421 15 3.75C15 3.33579 14.6642 3 14.25 3H12.0156C11.9721 3 11.9293 2.98865 11.8916 2.96706L10.6027 2.23057C10.3383 2.07948 10.039 2 9.73447 2H8.75003V0.75ZM1.69523 9.22719C1.9801 9.36201 2.41307 9.5 3.00002 9.5C3.58698 9.5 4.01994 9.36201 4.30482 9.22719L3.00002 6.32764L1.69523 9.22719ZM11.6952 9.22719C11.9801 9.36201 12.4131 9.5 13 9.5C13.587 9.5 14.0199 9.36201 14.3048 9.22719L13 6.32764L11.6952 9.22719Z'
+ fill: 'currentcolor'
})
]
)
diff --git a/generate/atom/icon/link.js b/generate/atom/icon/link.js
index dfddd0b821f8..63149623c786 100644
--- a/generate/atom/icon/link.js
+++ b/generate/atom/icon/link.js
@@ -1,22 +1,25 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = link
-
-function link() {
+/**
+ * @returns {Element}
+ */
+export function link() {
return s(
'svg.icon',
{
ariaHidden: 'true',
+ height: 18,
+ role: 'img',
viewBox: [0, 0, 16, 16],
- focusable: false,
- width: 18,
- height: 18
+ width: 18
},
s('path', {
- fill: 'currentcolor',
- d: 'M7.775 3.275C7.64252 3.41717 7.57039 3.60522 7.57382 3.79952C7.57725 3.99382 7.65596 4.1792 7.79337 4.31662C7.93079 4.45403 8.11617 4.53274 8.31047 4.53617C8.50477 4.5396 8.69282 4.46748 8.835 4.335L10.085 3.085C10.2708 2.89918 10.4914 2.75177 10.7342 2.65121C10.977 2.55064 11.2372 2.49888 11.5 2.49888C11.7628 2.49888 12.023 2.55064 12.2658 2.65121C12.5086 2.75177 12.7292 2.89918 12.915 3.085C13.1008 3.27082 13.2482 3.49142 13.3488 3.7342C13.4493 3.97699 13.5011 4.23721 13.5011 4.5C13.5011 4.76279 13.4493 5.023 13.3488 5.26579C13.2482 5.50857 13.1008 5.72917 12.915 5.915L10.415 8.415C10.2292 8.60095 10.0087 8.74847 9.76588 8.84911C9.52308 8.94976 9.26283 9.00157 9 9.00157C8.73716 9.00157 8.47691 8.94976 8.23411 8.84911C7.99132 8.74847 7.77074 8.60095 7.585 8.415C7.44282 8.28252 7.25477 8.21039 7.06047 8.21382C6.86617 8.21725 6.68079 8.29596 6.54337 8.43337C6.40596 8.57079 6.32725 8.75617 6.32382 8.95047C6.32039 9.14477 6.39252 9.33282 6.525 9.475C6.85001 9.80004 7.23586 10.0579 7.66052 10.2338C8.08518 10.4097 8.54034 10.5002 9 10.5002C9.45965 10.5002 9.91481 10.4097 10.3395 10.2338C10.7641 10.0579 11.15 9.80004 11.475 9.475L13.975 6.975C14.6314 6.31858 15.0002 5.4283 15.0002 4.5C15.0002 3.57169 14.6314 2.68141 13.975 2.025C13.3186 1.36858 12.4283 0.999817 11.5 0.999817C10.5717 0.999817 9.68141 1.36858 9.02499 2.025L7.775 3.275ZM3.085 12.915C2.89904 12.7292 2.75152 12.5087 2.65088 12.2659C2.55023 12.0231 2.49842 11.7628 2.49842 11.5C2.49842 11.2372 2.55023 10.9769 2.65088 10.7341C2.75152 10.4913 2.89904 10.2707 3.085 10.085L5.585 7.585C5.77074 7.39904 5.99132 7.25152 6.23411 7.15088C6.47691 7.05023 6.73716 6.99842 7 6.99842C7.26283 6.99842 7.52308 7.05023 7.76588 7.15088C8.00867 7.25152 8.22925 7.39904 8.415 7.585C8.55717 7.71748 8.74522 7.7896 8.93952 7.78617C9.13382 7.78274 9.3192 7.70403 9.45662 7.56662C9.59403 7.4292 9.67274 7.24382 9.67617 7.04952C9.6796 6.85522 9.60748 6.66717 9.475 6.525C9.14999 6.19995 8.76413 5.94211 8.33947 5.7662C7.91481 5.59029 7.45965 5.49974 7 5.49974C6.54034 5.49974 6.08518 5.59029 5.66052 5.7662C5.23586 5.94211 4.85001 6.19995 4.525 6.525L2.025 9.02499C1.36858 9.68141 0.999817 10.5717 0.999817 11.5C0.999817 12.4283 1.36858 13.3186 2.025 13.975C2.68141 14.6314 3.57169 15.0002 4.5 15.0002C5.4283 15.0002 6.31858 14.6314 6.975 13.975L8.225 12.725C8.35748 12.5828 8.4296 12.3948 8.42617 12.2005C8.42274 12.0062 8.34403 11.8208 8.20662 11.6834C8.0692 11.546 7.88382 11.4672 7.68952 11.4638C7.49522 11.4604 7.30717 11.5325 7.165 11.665L5.915 12.915C5.72925 13.1009 5.50867 13.2485 5.26588 13.3491C5.02308 13.4498 4.76283 13.5016 4.5 13.5016C4.23716 13.5016 3.97691 13.4498 3.73411 13.3491C3.49132 13.2485 3.27074 13.1009 3.085 12.915Z'
+ d: 'M7.775 3.275C7.64252 3.41717 7.57039 3.60522 7.57382 3.79952C7.57725 3.99382 7.65596 4.1792 7.79337 4.31662C7.93079 4.45403 8.11617 4.53274 8.31047 4.53617C8.50477 4.5396 8.69282 4.46748 8.835 4.335L10.085 3.085C10.2708 2.89918 10.4914 2.75177 10.7342 2.65121C10.977 2.55064 11.2372 2.49888 11.5 2.49888C11.7628 2.49888 12.023 2.55064 12.2658 2.65121C12.5086 2.75177 12.7292 2.89918 12.915 3.085C13.1008 3.27082 13.2482 3.49142 13.3488 3.7342C13.4493 3.97699 13.5011 4.23721 13.5011 4.5C13.5011 4.76279 13.4493 5.023 13.3488 5.26579C13.2482 5.50857 13.1008 5.72917 12.915 5.915L10.415 8.415C10.2292 8.60095 10.0087 8.74847 9.76588 8.84911C9.52308 8.94976 9.26283 9.00157 9 9.00157C8.73716 9.00157 8.47691 8.94976 8.23411 8.84911C7.99132 8.74847 7.77074 8.60095 7.585 8.415C7.44282 8.28252 7.25477 8.21039 7.06047 8.21382C6.86617 8.21725 6.68079 8.29596 6.54337 8.43337C6.40596 8.57079 6.32725 8.75617 6.32382 8.95047C6.32039 9.14477 6.39252 9.33282 6.525 9.475C6.85001 9.80004 7.23586 10.0579 7.66052 10.2338C8.08518 10.4097 8.54034 10.5002 9 10.5002C9.45965 10.5002 9.91481 10.4097 10.3395 10.2338C10.7641 10.0579 11.15 9.80004 11.475 9.475L13.975 6.975C14.6314 6.31858 15.0002 5.4283 15.0002 4.5C15.0002 3.57169 14.6314 2.68141 13.975 2.025C13.3186 1.36858 12.4283 0.999817 11.5 0.999817C10.5717 0.999817 9.68141 1.36858 9.02499 2.025L7.775 3.275ZM3.085 12.915C2.89904 12.7292 2.75152 12.5087 2.65088 12.2659C2.55023 12.0231 2.49842 11.7628 2.49842 11.5C2.49842 11.2372 2.55023 10.9769 2.65088 10.7341C2.75152 10.4913 2.89904 10.2707 3.085 10.085L5.585 7.585C5.77074 7.39904 5.99132 7.25152 6.23411 7.15088C6.47691 7.05023 6.73716 6.99842 7 6.99842C7.26283 6.99842 7.52308 7.05023 7.76588 7.15088C8.00867 7.25152 8.22925 7.39904 8.415 7.585C8.55717 7.71748 8.74522 7.7896 8.93952 7.78617C9.13382 7.78274 9.3192 7.70403 9.45662 7.56662C9.59403 7.4292 9.67274 7.24382 9.67617 7.04952C9.6796 6.85522 9.60748 6.66717 9.475 6.525C9.14999 6.19995 8.76413 5.94211 8.33947 5.7662C7.91481 5.59029 7.45965 5.49974 7 5.49974C6.54034 5.49974 6.08518 5.59029 5.66052 5.7662C5.23586 5.94211 4.85001 6.19995 4.525 6.525L2.025 9.02499C1.36858 9.68141 0.999817 10.5717 0.999817 11.5C0.999817 12.4283 1.36858 13.3186 2.025 13.975C2.68141 14.6314 3.57169 15.0002 4.5 15.0002C5.4283 15.0002 6.31858 14.6314 6.975 13.975L8.225 12.725C8.35748 12.5828 8.4296 12.3948 8.42617 12.2005C8.42274 12.0062 8.34403 11.8208 8.20662 11.6834C8.0692 11.546 7.88382 11.4672 7.68952 11.4638C7.49522 11.4604 7.30717 11.5325 7.165 11.665L5.915 12.915C5.72925 13.1009 5.50867 13.2485 5.26588 13.3491C5.02308 13.4498 4.76283 13.5016 4.5 13.5016C4.23716 13.5016 3.97691 13.4498 3.73411 13.3491C3.49132 13.2485 3.27074 13.1009 3.085 12.915Z',
+ fill: 'currentcolor'
})
)
}
diff --git a/generate/atom/icon/npm.js b/generate/atom/icon/npm.js
index d68898998ce5..023e74d9a0e1 100644
--- a/generate/atom/icon/npm.js
+++ b/generate/atom/icon/npm.js
@@ -1,24 +1,26 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = npm
-
-function npm() {
+/**
+ * @returns {Element}
+ */
+export function npm() {
return s(
'svg.icon',
{
ariaLabel: 'npm',
- viewBox: [3.8, 3.8, 19.7, 19.7],
- width: 18,
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [3.8, 3.8, 19.7, 19.7],
+ width: 18
},
[
s('path', {
- fill: 'currentcolor',
- d:
- 'M5.8 21.75h7.86l.01-11.77h3.92l-.01 11.78h3.93l.01-15.7-15.7-.02-.02 15.71z'
+ d: 'M5.8 21.75h7.86l.01-11.77h3.92l-.01 11.78h3.93l.01-15.7-15.7-.02-.02 15.71z',
+ fill: 'currentcolor'
})
]
)
diff --git a/generate/atom/icon/oc.js b/generate/atom/icon/oc.js
index ab36ded05545..6337705e8c23 100644
--- a/generate/atom/icon/oc.js
+++ b/generate/atom/icon/oc.js
@@ -1,28 +1,29 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = oc
-
-function oc() {
+/**
+ * @returns {Element}
+ */
+export function oc() {
return s(
'svg.icon',
{
- ariaLabel: 'Downloads',
- viewBox: [0, 0, 40, 40],
- width: 18,
+ ariaLabel: 'OpenCollective',
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [0, 0, 40, 40],
+ width: 18
},
s('path', {
- fill: 'currentcolor',
- d:
- 'M32.779 19.921a13.09 13.09 0 01-1.989 6.938l5.13 5.151c2.512-3.364 4.082-7.569 4.082-12.089 0-4.52-1.57-8.725-4.083-12.09l-5.129 5.152c1.256 1.997 1.989 4.31 1.989 6.938z'
+ d: 'M32.779 19.921a13.09 13.09 0 01-1.989 6.938l5.13 5.151c2.512-3.364 4.082-7.569 4.082-12.089 0-4.52-1.57-8.725-4.083-12.09l-5.129 5.152c1.256 1.997 1.989 4.31 1.989 6.938z',
+ fill: 'currentcolor'
}),
s('path', {
- fill: 'currentcolor',
- d:
- 'M20.014 32.746c-7.014 0-12.771-5.782-12.771-12.825 0-7.043 5.757-12.825 12.77-12.825 2.618 0 4.92.736 6.91 2.102l5.129-5.15c-3.35-2.524-7.537-4.1-12.038-4.1C9.022-.053.02 8.882.02 20.025.02 31.17 9.022 40 20.014 40c4.605 0 8.793-1.577 12.142-4.1l-5.129-5.151c-1.989 1.261-4.396 1.997-7.013 1.997z'
+ d: 'M20.014 32.746c-7.014 0-12.771-5.782-12.771-12.825 0-7.043 5.757-12.825 12.77-12.825 2.618 0 4.92.736 6.91 2.102l5.129-5.15c-3.35-2.524-7.537-4.1-12.038-4.1C9.022-.053.02 8.882.02 20.025.02 31.17 9.022 40 20.014 40c4.605 0 8.793-1.577 12.142-4.1l-5.129-5.151c-1.989 1.261-4.396 1.997-7.013 1.997z',
+ fill: 'currentcolor'
})
)
}
diff --git a/generate/atom/icon/score.js b/generate/atom/icon/score.js
index cf8a220aa1e7..aa3183c5f1d0 100644
--- a/generate/atom/icon/score.js
+++ b/generate/atom/icon/score.js
@@ -1,24 +1,27 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = score
-
-function score() {
+/**
+ * @returns {Element}
+ */
+export function score() {
return s(
'svg.icon',
{
ariaLabel: 'Score',
- viewBox: [0, 0, 16, 16],
- width: 18,
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [0, 0, 16, 16],
+ width: 18
},
[
s('path', {
- fill: 'currentcolor',
+ d: 'M7.99789 14.5001C10.8304 14.5001 12.9971 12.5193 12.9971 10C12.9971 8.53654 12.3174 7.80948 11.1193 6.61667C11.1071 6.60453 11.0949 6.59236 11.0826 6.58014C10.0696 5.57183 8.7824 4.29061 8.24911 2.14559C7.92718 2.40211 7.61813 2.72476 7.38529 3.09924C6.95273 3.79496 6.7637 4.67919 7.33879 5.82934C7.81231 6.77637 8.00841 8.11294 7.06066 9.06069C6.45006 9.67129 5.51641 9.90115 4.65812 9.69385C4.1002 9.55909 3.61121 9.25672 3.22215 8.81981C3.08407 9.16747 3.00001 9.57013 3 10.0001C2.99994 12.5298 5.1636 14.5001 7.99789 14.5001ZM9.5332 0.752514C9.49562 0.340008 9.16001 0.00931669 8.76889 0.145686C7.03463 0.750359 4.34051 3.18696 5.99715 6.50017C6.34142 7.1887 6.28164 7.71839 6 8.00003C5.58104 8.41899 4.45998 8.4869 3.95925 7.16847C3.78678 6.71435 3.30098 6.40593 2.92501 6.71353C2.03625 7.44067 1.50003 8.70216 1.5 10C1.49992 13.5121 4.49789 16.0001 7.99789 16.0001C11.4979 16.0001 14.4971 13.5 14.4971 10C14.4971 7.86282 13.3699 6.74064 12.1862 5.56222C10.9968 4.37809 9.7504 3.13717 9.5332 0.752514Z',
fillRule: 'evenodd',
- d: 'M7.99789 14.5001C10.8304 14.5001 12.9971 12.5193 12.9971 10C12.9971 8.53654 12.3174 7.80948 11.1193 6.61667C11.1071 6.60453 11.0949 6.59236 11.0826 6.58014C10.0696 5.57183 8.7824 4.29061 8.24911 2.14559C7.92718 2.40211 7.61813 2.72476 7.38529 3.09924C6.95273 3.79496 6.7637 4.67919 7.33879 5.82934C7.81231 6.77637 8.00841 8.11294 7.06066 9.06069C6.45006 9.67129 5.51641 9.90115 4.65812 9.69385C4.1002 9.55909 3.61121 9.25672 3.22215 8.81981C3.08407 9.16747 3.00001 9.57013 3 10.0001C2.99994 12.5298 5.1636 14.5001 7.99789 14.5001ZM9.5332 0.752514C9.49562 0.340008 9.16001 0.00931669 8.76889 0.145686C7.03463 0.750359 4.34051 3.18696 5.99715 6.50017C6.34142 7.1887 6.28164 7.71839 6 8.00003C5.58104 8.41899 4.45998 8.4869 3.95925 7.16847C3.78678 6.71435 3.30098 6.40593 2.92501 6.71353C2.03625 7.44067 1.50003 8.70216 1.5 10C1.49992 13.5121 4.49789 16.0001 7.99789 16.0001C11.4979 16.0001 14.4971 13.5 14.4971 10C14.4971 7.86282 13.3699 6.74064 12.1862 5.56222C10.9968 4.37809 9.7504 3.13717 9.5332 0.752514Z'
+ fill: 'currentcolor'
})
]
)
diff --git a/generate/atom/icon/st.js b/generate/atom/icon/st.js
deleted file mode 100644
index a504c83a8a9a..000000000000
--- a/generate/atom/icon/st.js
+++ /dev/null
@@ -1,23 +0,0 @@
-'use strict'
-
-var s = require('hastscript/svg')
-
-module.exports = st
-
-function st() {
- return s(
- 'svg.icon',
- {
- ariaLabel: 'Spectrum',
- viewBox: [0, 0, 32, 32],
- width: 18,
- height: 18,
- role: 'img'
- },
- s('path', {
- fill: 'currentcolor',
- d:
- 'M6 14.5A1.5 1.5 0 007.5 16H9a7 7 0 017 7v1.5a1.5 1.5 0 001.5 1.5h7a1.5 1.5 0 001.5-1.5V23c0-9.389-7.611-17-17-17H7.5A1.5 1.5 0 006 7.5v7z'
- })
- )
-}
diff --git a/generate/atom/icon/stars.js b/generate/atom/icon/stars.js
index 6da78c2a0686..7c020679df66 100644
--- a/generate/atom/icon/stars.js
+++ b/generate/atom/icon/stars.js
@@ -1,23 +1,26 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = stars
-
-function stars() {
+/**
+ * @returns {Element}
+ */
+export function stars() {
return s(
'svg.icon',
{
ariaLabel: 'Stars',
- viewBox: [0, 0, 16, 16],
- width: 18,
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [0, 0, 16, 16],
+ width: 18
},
s('path', {
- fill: 'currentcolor',
+ d: 'M8.00001 0.25C8.14003 0.24991 8.27727 0.289014 8.39621 0.362887C8.51515 0.43676 8.61103 0.542452 8.67301 0.668L10.555 4.483L14.765 5.095C14.9035 5.11511 15.0336 5.17355 15.1405 5.26372C15.2475 5.35388 15.3272 5.47218 15.3704 5.60523C15.4137 5.73829 15.4189 5.8808 15.3854 6.01665C15.352 6.1525 15.2812 6.27628 15.181 6.374L12.135 9.344L12.854 13.536C12.8777 13.6739 12.8624 13.8157 12.8097 13.9454C12.757 14.0751 12.6691 14.1874 12.5559 14.2697C12.4427 14.352 12.3087 14.401 12.1691 14.4111C12.0295 14.4212 11.8899 14.3921 11.766 14.327L8.00001 12.347L4.23401 14.327C4.1102 14.392 3.97068 14.4211 3.8312 14.411C3.69173 14.4009 3.55785 14.352 3.44469 14.2699C3.33154 14.1877 3.2436 14.0755 3.19083 13.946C3.13805 13.8165 3.12252 13.6749 3.14601 13.537L3.86601 9.343L0.818012 6.374C0.717578 6.27632 0.646511 6.15247 0.612864 6.01647C0.579216 5.88047 0.584334 5.73777 0.627636 5.60453C0.670939 5.47129 0.750695 5.35284 0.857868 5.26261C0.96504 5.17238 1.09534 5.11397 1.23401 5.094L5.44401 4.483L7.32701 0.668C7.38899 0.542452 7.48488 0.43676 7.60382 0.362887C7.72276 0.289014 7.86 0.24991 8.00001 0.25ZM8.00001 2.695L6.61501 5.5C6.56123 5.6089 6.4818 5.70311 6.38356 5.77453C6.28531 5.84595 6.17119 5.89244 6.05101 5.91L2.95401 6.36L5.19401 8.544C5.28116 8.62886 5.34637 8.73365 5.384 8.84933C5.42163 8.96501 5.43056 9.0881 5.41001 9.208L4.88201 12.292L7.65101 10.836C7.75864 10.7794 7.87842 10.7499 8.00001 10.7499C8.12161 10.7499 8.24138 10.7794 8.34901 10.836L11.119 12.292L10.589 9.208C10.5685 9.0881 10.5774 8.96501 10.615 8.84933C10.6527 8.73365 10.7179 8.62886 10.805 8.544L13.045 6.361L9.94901 5.911C9.82883 5.89344 9.71471 5.84695 9.61647 5.77553C9.51822 5.70411 9.4388 5.6099 9.38501 5.501L8.00001 2.694V2.695Z',
fillRule: 'evenodd',
- d: 'M8.00001 0.25C8.14003 0.24991 8.27727 0.289014 8.39621 0.362887C8.51515 0.43676 8.61103 0.542452 8.67301 0.668L10.555 4.483L14.765 5.095C14.9035 5.11511 15.0336 5.17355 15.1405 5.26372C15.2475 5.35388 15.3272 5.47218 15.3704 5.60523C15.4137 5.73829 15.4189 5.8808 15.3854 6.01665C15.352 6.1525 15.2812 6.27628 15.181 6.374L12.135 9.344L12.854 13.536C12.8777 13.6739 12.8624 13.8157 12.8097 13.9454C12.757 14.0751 12.6691 14.1874 12.5559 14.2697C12.4427 14.352 12.3087 14.401 12.1691 14.4111C12.0295 14.4212 11.8899 14.3921 11.766 14.327L8.00001 12.347L4.23401 14.327C4.1102 14.392 3.97068 14.4211 3.8312 14.411C3.69173 14.4009 3.55785 14.352 3.44469 14.2699C3.33154 14.1877 3.2436 14.0755 3.19083 13.946C3.13805 13.8165 3.12252 13.6749 3.14601 13.537L3.86601 9.343L0.818012 6.374C0.717578 6.27632 0.646511 6.15247 0.612864 6.01647C0.579216 5.88047 0.584334 5.73777 0.627636 5.60453C0.670939 5.47129 0.750695 5.35284 0.857868 5.26261C0.96504 5.17238 1.09534 5.11397 1.23401 5.094L5.44401 4.483L7.32701 0.668C7.38899 0.542452 7.48488 0.43676 7.60382 0.362887C7.72276 0.289014 7.86 0.24991 8.00001 0.25ZM8.00001 2.695L6.61501 5.5C6.56123 5.6089 6.4818 5.70311 6.38356 5.77453C6.28531 5.84595 6.17119 5.89244 6.05101 5.91L2.95401 6.36L5.19401 8.544C5.28116 8.62886 5.34637 8.73365 5.384 8.84933C5.42163 8.96501 5.43056 9.0881 5.41001 9.208L4.88201 12.292L7.65101 10.836C7.75864 10.7794 7.87842 10.7499 8.00001 10.7499C8.12161 10.7499 8.24138 10.7794 8.34901 10.836L11.119 12.292L10.589 9.208C10.5685 9.0881 10.5774 8.96501 10.615 8.84933C10.6527 8.73365 10.7179 8.62886 10.805 8.544L13.045 6.361L9.94901 5.911C9.82883 5.89344 9.71471 5.84695 9.61647 5.77553C9.51822 5.70411 9.4388 5.6099 9.38501 5.501L8.00001 2.694V2.695Z'
+ fill: 'currentcolor'
})
)
}
diff --git a/generate/atom/icon/tw.js b/generate/atom/icon/tw.js
deleted file mode 100644
index 0bce05f65ea0..000000000000
--- a/generate/atom/icon/tw.js
+++ /dev/null
@@ -1,23 +0,0 @@
-'use strict'
-
-var s = require('hastscript/svg')
-
-module.exports = twitter
-
-function twitter() {
- return s(
- 'svg.icon',
- {
- ariaLabel: 'Twitter',
- viewBox: [0, 0, 18, 14],
- width: 18,
- height: 18,
- role: 'img'
- },
- s('path', {
- fill: 'currentcolor',
- d:
- 'M18 1.684l-1.687 1.684v.28c0 .307-.05.602-.123.886-.04 2.316-.777 5.387-3.816 7.81C6.404 17.115 0 12.907 0 12.907c5.063 0 5.063-1.684 5.063-1.684-1.126 0-3.376-2.243-3.376-2.243.563.56 1.689 0 1.689 0C.56 7.295.56 5.61.56 5.61c.563.561 1.689 0 1.689 0C-.563 3.368 1.124.561 1.124.561 1.687 3.368 9 4.49 9 4.49l.093-.046A6.637 6.637 0 0 1 9 3.368C9 1.353 10.636 0 12.656 0c1.112 0 2.094.506 2.765 1.286l.329-.163L17.437 0l-1.122 2.245L18 1.684z'
- })
- )
-}
diff --git a/generate/atom/icon/verified.js b/generate/atom/icon/verified.js
index 96ee0554441d..503f9cf47d82 100644
--- a/generate/atom/icon/verified.js
+++ b/generate/atom/icon/verified.js
@@ -1,23 +1,26 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var s = require('hastscript/svg')
+import {s} from 'hastscript'
-module.exports = verified
-
-function verified() {
+/**
+ * @returns {Element}
+ */
+export function verified() {
return s(
'svg.icon',
{
ariaLabel: 'Verified',
- viewBox: [0, 0, 16, 16],
- width: 18,
height: 18,
- role: 'img'
+ role: 'img',
+ viewBox: [0, 0, 16, 16],
+ width: 18
},
s('path', {
- fill: 'currentcolor',
+ d: 'M9.58507 0.51936C8.64181 -0.17312 7.3582 -0.17312 6.41493 0.51936L5.48738 1.2003C5.33429 1.31269 5.1563 1.38642 4.96858 1.4152L3.8312 1.58958C2.67456 1.76691 1.76691 2.67456 1.58958 3.8312L1.4152 4.96858C1.38642 5.1563 1.31269 5.33429 1.2003 5.48738L0.51936 6.41493C-0.17312 7.3582 -0.17312 8.6418 0.51936 9.58507L1.2003 10.5126C1.31269 10.6657 1.38642 10.8437 1.4152 11.0314L1.58958 12.1688C1.76691 13.3254 2.67456 14.2331 3.8312 14.4104L4.96858 14.5848C5.1563 14.6136 5.33429 14.6873 5.48738 14.7997L6.41493 15.4806C7.3582 16.1731 8.6418 16.1731 9.58507 15.4806L10.5126 14.7997C10.6657 14.6873 10.8437 14.6136 11.0314 14.5848L12.1688 14.4104C13.3254 14.2331 14.2331 13.3254 14.4104 12.1688L14.5848 11.0314C14.6136 10.8437 14.6873 10.6657 14.7997 10.5126L15.4806 9.58507C16.1731 8.6418 16.1731 7.3582 15.4806 6.41493L14.7997 5.48738C14.6873 5.33429 14.6136 5.1563 14.5848 4.96858L14.4104 3.8312C14.2331 2.67456 13.3254 1.76691 12.1688 1.58958L11.0314 1.4152C10.8437 1.38642 10.6657 1.31269 10.5126 1.2003L9.58507 0.51936ZM7.3026 1.72851C7.71762 1.42383 8.28238 1.42383 8.6974 1.72851L9.62494 2.40945C9.9729 2.66489 10.3774 2.83246 10.8041 2.89787L11.9415 3.07225C12.4504 3.15027 12.8497 3.54962 12.9277 4.05852L13.1021 5.19589C13.1675 5.62256 13.3351 6.0271 13.5906 6.37506L14.2715 7.3026C14.5762 7.71762 14.5762 8.28238 14.2715 8.6974L13.5906 9.62494C13.3351 9.9729 13.1675 10.3774 13.1021 10.8041L12.9277 11.9415C12.8497 12.4504 12.4504 12.8497 11.9415 12.9277L10.8041 13.1021C10.3774 13.1675 9.9729 13.3351 9.62494 13.5906L8.6974 14.2715C8.28238 14.5762 7.71762 14.5762 7.3026 14.2715L6.37506 13.5906C6.0271 13.3351 5.62256 13.1675 5.19589 13.1021L4.05852 12.9277C3.54962 12.8497 3.15027 12.4504 3.07225 11.9415L2.89787 10.8041C2.83246 10.3774 2.66489 9.9729 2.40945 9.62494L1.72851 8.6974C1.42383 8.28238 1.42383 7.71762 1.72851 7.3026L2.40945 6.37505C2.66489 6.0271 2.83246 5.62256 2.89787 5.19589L3.07225 4.05852C3.15027 3.54962 3.54962 3.15027 4.05852 3.07225L5.19589 2.89787C5.62256 2.83246 6.0271 2.66489 6.37505 2.40945L7.3026 1.72851ZM11.2803 6.78033C11.5732 6.48744 11.5732 6.01256 11.2803 5.71967C10.9874 5.42678 10.5126 5.42678 10.2197 5.71967L7 8.93934L5.78033 7.71967C5.48744 7.42678 5.01256 7.42678 4.71967 7.71967C4.42678 8.01256 4.42678 8.48744 4.71967 8.78033L6.46967 10.5303C6.76256 10.8232 7.23744 10.8232 7.53033 10.5303L11.2803 6.78033Z',
fillRule: 'evenodd',
- d: 'M9.58507 0.51936C8.64181 -0.17312 7.3582 -0.17312 6.41493 0.51936L5.48738 1.2003C5.33429 1.31269 5.1563 1.38642 4.96858 1.4152L3.8312 1.58958C2.67456 1.76691 1.76691 2.67456 1.58958 3.8312L1.4152 4.96858C1.38642 5.1563 1.31269 5.33429 1.2003 5.48738L0.51936 6.41493C-0.17312 7.3582 -0.17312 8.6418 0.51936 9.58507L1.2003 10.5126C1.31269 10.6657 1.38642 10.8437 1.4152 11.0314L1.58958 12.1688C1.76691 13.3254 2.67456 14.2331 3.8312 14.4104L4.96858 14.5848C5.1563 14.6136 5.33429 14.6873 5.48738 14.7997L6.41493 15.4806C7.3582 16.1731 8.6418 16.1731 9.58507 15.4806L10.5126 14.7997C10.6657 14.6873 10.8437 14.6136 11.0314 14.5848L12.1688 14.4104C13.3254 14.2331 14.2331 13.3254 14.4104 12.1688L14.5848 11.0314C14.6136 10.8437 14.6873 10.6657 14.7997 10.5126L15.4806 9.58507C16.1731 8.6418 16.1731 7.3582 15.4806 6.41493L14.7997 5.48738C14.6873 5.33429 14.6136 5.1563 14.5848 4.96858L14.4104 3.8312C14.2331 2.67456 13.3254 1.76691 12.1688 1.58958L11.0314 1.4152C10.8437 1.38642 10.6657 1.31269 10.5126 1.2003L9.58507 0.51936ZM7.3026 1.72851C7.71762 1.42383 8.28238 1.42383 8.6974 1.72851L9.62494 2.40945C9.9729 2.66489 10.3774 2.83246 10.8041 2.89787L11.9415 3.07225C12.4504 3.15027 12.8497 3.54962 12.9277 4.05852L13.1021 5.19589C13.1675 5.62256 13.3351 6.0271 13.5906 6.37506L14.2715 7.3026C14.5762 7.71762 14.5762 8.28238 14.2715 8.6974L13.5906 9.62494C13.3351 9.9729 13.1675 10.3774 13.1021 10.8041L12.9277 11.9415C12.8497 12.4504 12.4504 12.8497 11.9415 12.9277L10.8041 13.1021C10.3774 13.1675 9.9729 13.3351 9.62494 13.5906L8.6974 14.2715C8.28238 14.5762 7.71762 14.5762 7.3026 14.2715L6.37506 13.5906C6.0271 13.3351 5.62256 13.1675 5.19589 13.1021L4.05852 12.9277C3.54962 12.8497 3.15027 12.4504 3.07225 11.9415L2.89787 10.8041C2.83246 10.3774 2.66489 9.9729 2.40945 9.62494L1.72851 8.6974C1.42383 8.28238 1.42383 7.71762 1.72851 7.3026L2.40945 6.37505C2.66489 6.0271 2.83246 5.62256 2.89787 5.19589L3.07225 4.05852C3.15027 3.54962 3.54962 3.15027 4.05852 3.07225L5.19589 2.89787C5.62256 2.83246 6.0271 2.66489 6.37505 2.40945L7.3026 1.72851ZM11.2803 6.78033C11.5732 6.48744 11.5732 6.01256 11.2803 5.71967C10.9874 5.42678 10.5126 5.42678 10.2197 5.71967L7 8.93934L5.78033 7.71967C5.48744 7.42678 5.01256 7.42678 4.71967 7.71967C4.42678 8.01256 4.42678 8.48744 4.71967 8.78033L6.46967 10.5303C6.76256 10.8232 7.23744 10.8232 7.53033 10.5303L11.2803 6.78033Z'
+ fill: 'currentcolor'
})
)
}
diff --git a/generate/atom/macro/block.js b/generate/atom/macro/block.js
index de7e71117d64..5588b0daafce 100644
--- a/generate/atom/macro/block.js
+++ b/generate/atom/macro/block.js
@@ -1,13 +1,19 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = block
-
-function block(main, footer) {
- return h(
- 'li',
- {className: footer ? ['nl-root'] : []},
- [].concat(main, footer ? h('.nl-foot', footer) : [])
- )
+/**
+ * @param {Array | ElementContent} main
+ * @param {Array | ElementContent | undefined} [footer]
+ * @returns {Element}
+ */
+export function block(main, footer) {
+ /** @type {Array} */
+ const children = []
+ if (Array.isArray(main)) children.push(...main)
+ else if (main) children.push(main)
+ if (footer) children.push(h('.nl-foot', {}, footer))
+ return h('li', {className: footer ? ['nl-root'] : []}, children)
}
diff --git a/generate/atom/macro/list.js b/generate/atom/macro/list.js
index 3a75df807292..7f59cf9a1214 100644
--- a/generate/atom/macro/list.js
+++ b/generate/atom/macro/list.js
@@ -1,19 +1,57 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-module.exports = list
+/**
+ * @template T
+ * @callback Map
+ * @param {T} name
+ * @returns {Array | ElementContent}
+ */
-function list(names, map, options) {
- var {max, more, trail} = options || {}
- var values = names
- var total = values.length
- var children
+/**
+ * @callback More
+ * @param {number} count
+ * @param {number} total
+ * @returns {ElementContent}
+ */
- if (max && total >= max) {
+/**
+ * @typedef Options
+ * @property {number | undefined} [max]
+ * @property {More | undefined} [more]
+ * @property {ElementContent | undefined} [trail]
+ */
+
+/**
+ * @template T
+ * @param {ReadonlyArray} names
+ * @param {Map} map
+ * @param {Options | undefined} [options]
+ * @returns {Array}
+ */
+export function list(names, map, options) {
+ let {max, more, trail} = options || {}
+ let values = names
+ const total = values.length
+
+ if (more && max && total >= max) {
trail = more(total - (max - 1), total)
values = names.slice(0, max - 1)
}
- children = values.map(map)
+ /** @type {Array} */
+ const children = []
+
+ for (const d of values) {
+ const result = map(d)
+
+ if (Array.isArray(result)) {
+ children.push(...result)
+ } else {
+ children.push(result)
+ }
+ }
if (trail) {
children.push(trail)
diff --git a/generate/atom/micro/description.js b/generate/atom/micro/description.js
index e27fab4748bb..32a0e7ff67da 100644
--- a/generate/atom/micro/description.js
+++ b/generate/atom/micro/description.js
@@ -1,10 +1,14 @@
-'use strict'
+/**
+ * @import {Element, Parents} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = description
-
-function description(value, rich) {
- var val = rich ? rich.children : value
- return h('li.ellipsis.content', val)
+/**
+ * @param {string | undefined} value
+ * @param {Parents | undefined} [rich]
+ * @returns {Element}
+ */
+export function description(value, rich) {
+ return h('li.ellipsis.content', {}, rich ? rich.children : value)
}
diff --git a/generate/atom/micro/downloads.js b/generate/atom/micro/downloads.js
index c1cbcdcd3189..9de03b64c873 100644
--- a/generate/atom/micro/downloads.js
+++ b/generate/atom/micro/downloads.js
@@ -1,17 +1,27 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
-var fmt = require('../../util/fmt-compact')
-var icon = require('../icon/downloads')
+import {h} from 'hastscript'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {downloads as icon} from '../icon/downloads.js'
-module.exports = downloads
+/**
+ * @param {number | undefined} value
+ * @param {string | undefined} [name]
+ * @returns {Array | ElementContent}
+ */
+export function downloads(value, name) {
+ if (!value) {
+ return []
+ }
-function downloads(value, name) {
- var node = [icon(), ' ', fmt(value)]
+ /** @type {Array | ElementContent} */
+ let node = [icon(), ' ', fmtCompact(value)]
if (name) {
node = h('a.tap-target', {href: 'https://www.npmtrends.com/' + name}, node)
}
- return h('li', node)
+ return h('li', {}, node)
}
diff --git a/generate/atom/micro/esm.js b/generate/atom/micro/esm.js
deleted file mode 100644
index 11a496a5368e..000000000000
--- a/generate/atom/micro/esm.js
+++ /dev/null
@@ -1,19 +0,0 @@
-'use strict'
-
-var h = require('hastscript')
-
-module.exports = esm
-
-function esm(value, name) {
- var node = value ? 'ESM' : 'CJS'
-
- if (name) {
- node = h(
- 'a.tap-target',
- {href: 'https://bundlephobia.com/result?p=' + name},
- node
- )
- }
-
- return typeof value === 'boolean' ? h('li', node) : ''
-}
diff --git a/generate/atom/micro/gh.js b/generate/atom/micro/gh.js
index 19466eb9d365..782093a47b67 100644
--- a/generate/atom/micro/gh.js
+++ b/generate/atom/micro/gh.js
@@ -1,16 +1,21 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var h = require('hastscript')
-var icon = require('../icon/gh')
+import {h} from 'hastscript'
+import {gh as icon} from '../icon/gh.js'
-module.exports = github
-
-function github(name) {
- var node = icon()
+/**
+ *
+ * @param {string} name
+ * @returns {Element}
+ */
+export function gh(name) {
+ let node = icon()
if (name) {
node = h('a.tap-target', {href: 'https://github.com/' + name}, node)
}
- return h('li', node)
+ return h('li', {}, node)
}
diff --git a/generate/atom/micro/graph.js b/generate/atom/micro/graph.js
deleted file mode 100644
index 77ef1d42bddd..000000000000
--- a/generate/atom/micro/graph.js
+++ /dev/null
@@ -1,26 +0,0 @@
-'use strict'
-
-var h = require('hastscript')
-var fmt = require('../../util/fmt-compact')
-
-module.exports = graph
-
-function graph(dependencies, dependents, name) {
- var uses = [h('span.label', 'Dependencies: '), fmt(dependencies || 0)]
- var by = [h('span.label', 'Dependents: '), fmt(dependents || 0)]
-
- if (name) {
- uses = h(
- 'a.tap-target',
- {href: 'https://www.npmjs.com/package/' + name},
- uses
- )
- by = h(
- 'a.tap-target',
- {href: 'https://www.npmjs.com/browse/depended/' + name},
- by
- )
- }
-
- return h('li', [].concat(uses, h('span.lowlight.separator', '·'), by))
-}
diff --git a/generate/atom/micro/gzip.js b/generate/atom/micro/gzip.js
index 7610c070c93f..3acabe1b4bfa 100644
--- a/generate/atom/micro/gzip.js
+++ b/generate/atom/micro/gzip.js
@@ -1,12 +1,18 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var h = require('hastscript')
-var fmt = require('../../util/fmt-bytes')
+import {h} from 'hastscript'
+import {fmtBytes} from '../../util/fmt-bytes.js'
-module.exports = gzip
-
-function gzip(value, name) {
- var node = fmt(value)
+/**
+ * @param {number | undefined} value
+ * @param {string | undefined} [name]
+ * @returns {Array | ElementContent}
+ */
+export function gzip(value, name) {
+ /** @type {Element | string} */
+ let node = fmtBytes(value)
if (name) {
node = h(
@@ -16,5 +22,5 @@ function gzip(value, name) {
)
}
- return value ? h('li', node) : ''
+ return value ? h('li', {}, node) : []
}
diff --git a/generate/atom/micro/license.js b/generate/atom/micro/license.js
index faee9d2aa19b..98e6a3a583c7 100644
--- a/generate/atom/micro/license.js
+++ b/generate/atom/micro/license.js
@@ -1,18 +1,24 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var spdx = require('spdx-license-list')
-var h = require('hastscript')
-var icon = require('../icon/license')
+import spdxLicenseList from 'spdx-license-list'
+import {h} from 'hastscript'
+import {license as icon} from '../icon/license.js'
-module.exports = license
-
-function license(value) {
- var url = value in spdx ? spdx[value].url : null
- var node = value ? [icon(), ' ', value] : ''
+/**
+ * @param {string | undefined} value
+ * @returns {Array | ElementContent}
+ */
+export function license(value) {
+ const url =
+ value && value in spdxLicenseList ? spdxLicenseList[value].url : undefined
+ /** @type {Array | Element | string} */
+ let node = value ? [icon(), ' ', value] : ''
if (url) {
node = h('a.tap-target', {href: url}, node)
}
- return node ? h('li', node) : ''
+ return node ? h('li', {}, node) : []
}
diff --git a/generate/atom/micro/npm.js b/generate/atom/micro/npm.js
index 2b478e1b2dc4..95150d33fc13 100644
--- a/generate/atom/micro/npm.js
+++ b/generate/atom/micro/npm.js
@@ -1,20 +1,24 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
-var icon = require('../icon/npm')
+import {h} from 'hastscript'
+import {npm as icon} from '../icon/npm.js'
-module.exports = npm
-
-var base = 'https://www.npmjs.com/'
-
-function npm(name) {
- var node = icon()
- var href
+/**
+ * @param {string} name
+ * @returns {ElementContent}
+ */
+export function npm(name) {
+ let node = icon()
if (name) {
- href = base + (name.charAt(0) === '~' ? '' : 'package/') + name
+ const href =
+ 'https://www.npmjs.com/' +
+ (name.charAt(0) === '~' ? '' : 'package/') +
+ name
node = h('a.tap-target', {href}, node)
}
- return h('li', node)
+ return h('li', {}, node)
}
diff --git a/generate/atom/micro/oc.js b/generate/atom/micro/oc.js
index d91d366ef7a2..aa82d3b14014 100644
--- a/generate/atom/micro/oc.js
+++ b/generate/atom/micro/oc.js
@@ -1,16 +1,20 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
-var icon = require('../icon/oc')
+import {h} from 'hastscript'
+import {oc as icon} from '../icon/oc.js'
-module.exports = oc
-
-function oc(name) {
- var node = icon()
+/**
+ * @param {string} name
+ * @returns {ElementContent}
+ */
+export function oc(name) {
+ let node = icon()
if (name) {
node = h('a.tap-target', {href: 'https://opencollective.com/' + name}, node)
}
- return h('li', node)
+ return h('li', {}, node)
}
diff --git a/generate/atom/micro/score.js b/generate/atom/micro/score.js
index a3fb7cfea7d7..99b8b675a0d0 100644
--- a/generate/atom/micro/score.js
+++ b/generate/atom/micro/score.js
@@ -1,14 +1,22 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
-var fmt = require('../../util/fmt-percent')
-var color = require('../../util/score-color')
-var icon = require('../icon/score')
+import {h} from 'hastscript'
+import {fmtPercent} from '../../util/fmt-percent.js'
+import {scoreColor} from '../../util/score-color.js'
+import {score as icon} from '../icon/score.js'
-module.exports = score
-
-function score(value) {
+/**
+ * @param {number} value
+ * @returns {Array | ElementContent}
+ */
+export function score(value) {
return value
- ? h('li', {style: 'color:' + color(value)}, [icon(), ' ', fmt(value)])
- : ''
+ ? h('li', {style: 'color:' + scoreColor(value)}, [
+ icon(),
+ ' ',
+ fmtPercent(value)
+ ])
+ : []
}
diff --git a/generate/atom/micro/stars.js b/generate/atom/micro/stars.js
index d6fd16f841f1..22d7b8890830 100644
--- a/generate/atom/micro/stars.js
+++ b/generate/atom/micro/stars.js
@@ -1,13 +1,19 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
-var fmt = require('../../util/fmt-compact')
-var icon = require('../icon/stars')
+import {h} from 'hastscript'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {stars as icon} from '../icon/stars.js'
-module.exports = stars
-
-function stars(value, name) {
- var node = [icon(), ' ', fmt(value)]
+/**
+ * @param {number} value
+ * @param {string | undefined} [name]
+ * @returns {ElementContent}
+ */
+export function stars(value, name) {
+ /** @type {Array | ElementContent} */
+ let node = [icon(), ' ', fmtCompact(value)]
if (name) {
node = h(
@@ -17,5 +23,5 @@ function stars(value, name) {
)
}
- return h('li', node)
+ return h('li', {}, node)
}
diff --git a/generate/atom/micro/tag.js b/generate/atom/micro/tag.js
index 5218101bcde3..e65aeafaec55 100644
--- a/generate/atom/micro/tag.js
+++ b/generate/atom/micro/tag.js
@@ -1,19 +1,25 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var h = require('hastscript')
-var fmt = require('../../util/fmt-compact')
+import {h} from 'hastscript'
+import {fmtCompact} from '../../util/fmt-compact.js'
-module.exports = tag
-
-function tag(label, count, href) {
- var nodes = [label]
+/**
+ * @param {string} label
+ * @param {number | undefined} [count]
+ * @param {string | undefined} [href]
+ * @returns {Element}
+ */
+export function tag(label, count, href) {
+ /** @type {Array} */
+ const nodes = [label]
if (count) {
- nodes.push(' ', h('span.count', fmt(count)))
+ nodes.push(' ', h('span.count', {}, fmtCompact(count)))
}
- return h(
- 'li.inline-block',
- href ? h('a.tag', {href}, nodes) : h('span.tag', nodes)
- )
+ return h('li.inline-block', [
+ href ? h('a.tag', {href}, nodes) : h('span.tag', {}, nodes)
+ ])
}
diff --git a/generate/atom/micro/tw.js b/generate/atom/micro/tw.js
deleted file mode 100644
index 326ac7217acb..000000000000
--- a/generate/atom/micro/tw.js
+++ /dev/null
@@ -1,16 +0,0 @@
-'use strict'
-
-var h = require('hastscript')
-var icon = require('../icon/tw')
-
-module.exports = twitter
-
-function twitter(name) {
- var node = icon()
-
- if (name) {
- node = h('a.tap-target', {href: 'https://twitter.com/' + name}, node)
- }
-
- return h('li', node)
-}
diff --git a/generate/atom/micro/url.js b/generate/atom/micro/url.js
index c80ff9bc7d1b..084bd154d611 100644
--- a/generate/atom/micro/url.js
+++ b/generate/atom/micro/url.js
@@ -1,16 +1,23 @@
-'use strict'
+/**
+ * @import {ElementContent, Properties} from 'hast'
+ */
-var h = require('hastscript')
-var fmt = require('../../util/fmt-url')
-var icon = require('../icon/link')
+import {h} from 'hastscript'
+import {fmtUrl} from '../../util/fmt-url.js'
+import {link as icon} from '../icon/link.js'
-module.exports = url
-
-function url(value) {
- return value
- ? h(
- 'li.ellipsis',
- h('a.tap-target', {href: value}, [icon(), ' ', fmt(value)])
- )
- : ''
+/**
+ * @param {string} value
+ * @param {Properties | undefined} [linkProperties]
+ * @returns {ElementContent}
+ */
+export function url(value, linkProperties) {
+ return h(
+ 'li.ellipsis',
+ h('a.tap-target', {...linkProperties, href: value}, [
+ icon(),
+ ' ',
+ fmtUrl(value)
+ ])
+ )
}
diff --git a/generate/atom/micro/verified.js b/generate/atom/micro/verified.js
index 1a171a37e0ce..cfd2d37207e9 100644
--- a/generate/atom/micro/verified.js
+++ b/generate/atom/micro/verified.js
@@ -1,11 +1,17 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
-var collective = require('../../util/constant-collective')
-var icon = require('../icon/verified')
+import {h} from 'hastscript'
+import {constantCollective} from '../../util/constant-collective.js'
+import {verified as icon} from '../icon/verified.js'
-module.exports = verified
-
-function verified(name) {
- return collective.includes(name.split('/')[0]) ? h('li', icon()) : ''
+/**
+ * @param {string} name
+ * @returns {Array | ElementContent}
+ */
+export function verified(name) {
+ return constantCollective.includes(name.split('/')[0])
+ ? h('li', {}, icon())
+ : []
}
diff --git a/generate/component/article/detail.js b/generate/component/article/detail.js
index d2a1fde443e4..f3a9dfb127a2 100644
--- a/generate/component/article/detail.js
+++ b/generate/component/article/detail.js
@@ -1,9 +1,13 @@
-'use strict'
+/**
+ * @import {Element, Parents} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = detail
-
-function detail(article) {
- return h('.content.article', article.children)
+/**
+ * @param {Parents} article
+ * @returns {Element}
+ */
+export function detail(article) {
+ return h('.content.article', {}, article.children)
}
diff --git a/generate/component/article/helper-sort.js b/generate/component/article/helper-sort.js
index 6f47ba16730e..6ee59c45397f 100644
--- a/generate/component/article/helper-sort.js
+++ b/generate/component/article/helper-sort.js
@@ -1,14 +1,28 @@
-'use strict'
+/**
+ * @import {VFile} from 'vfile'
+ */
-var sort = require('../../util/sort').asc
+import {asc} from '../../util/sort.js'
-module.exports = sorter
-
-// Sort articles by index.
-function sorter(data) {
- return sort(data, score)
+/**
+ * Sort articles by index.
+ *
+ * @param {ReadonlyArray} data
+ * @returns {Array}
+ */
+export function helperSort(data) {
+ return asc(data, score)
}
+/**
+ * @param {VFile} d
+ * @returns {number}
+ */
function score(d) {
- return (d.data.matter || {}).index || Infinity
+ const matter = d.data.matter || {}
+ const published =
+ typeof matter.published === 'string'
+ ? new Date(matter.published)
+ : matter.published || undefined
+ return matter.index || published?.getTime() || Number.POSITIVE_INFINITY
}
diff --git a/generate/component/article/item.js b/generate/component/article/item.js
index 7533f2cf8ac9..28420b261f68 100644
--- a/generate/component/article/item.js
+++ b/generate/component/article/item.js
@@ -1,43 +1,55 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {VFile} from 'vfile'
+ */
-var h = require('hastscript')
-var card = require('../../atom/card/item')
-var tag = require('../../atom/micro/tag')
+import assert from 'node:assert/strict'
+import {h} from 'hastscript'
+import {item as card} from '../../atom/card/item.js'
+import {tag} from '../../atom/micro/tag.js'
-module.exports = item
+/**
+ * @param {VFile} d
+ * @returns {Element}
+ */
+export function item(d) {
+ const {matter, meta} = d.data
+ const data = {...matter, ...meta}
+ const {authorGithub, author, description, pathname, tags, title} = data
-var base = 'https://twitter.com/'
+ assert(pathname)
-function item(d) {
- var {matter, meta} = d.data
- var data = {...matter, ...meta}
- var {title, description, author, authorTwitter, tags, pathname} = data
+ let authorDisplay = h('span.ellipsis', {}, author)
- author = h('span.ellipsis', author)
-
- if (authorTwitter) {
- author = h('a.row', {href: base + authorTwitter}, [
+ if (authorGithub) {
+ authorDisplay = h('a.row', {href: 'https://github.com/' + authorGithub}, [
h('.thumbnail', {
role: 'presentation',
style:
- 'background-image:url(https://twitter-avatar.now.sh/' +
- authorTwitter +
- ')'
+ 'background-image:url(https://github.com/' +
+ authorGithub +
+ '.png?size=64)'
}),
- author
+ authorDisplay
])
}
+ /** @type {Array} */
+ const results = []
+
+ if (tags) {
+ for (const d of tags) {
+ results.push(tag(d))
+ }
+ }
+
return card(
pathname,
h('.column', [
- h('h3.ellipsis', title),
- h('p.double-ellipsis', description || ''),
- h(
- 'ol.row.ellipsis',
- (tags || []).map(d => tag(d))
- )
+ h('h3.ellipsis', {}, title),
+ h('p.double-ellipsis', {}, description || ''),
+ h('ol.row.ellipsis', {}, results)
]),
- h('li.row', author)
+ h('li.row', {}, authorDisplay)
)
}
diff --git a/generate/component/article/list.js b/generate/component/article/list.js
index e6d187591cb3..362d56a8d63f 100644
--- a/generate/component/article/list.js
+++ b/generate/component/article/list.js
@@ -1,14 +1,37 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {VFile} from 'vfile'
+ * @import {Options} from '../../atom/macro/list.js'
+ */
-var cards = require('../../atom/card/list')
-var item = require('./item')
-var more = require('./more')
+/**
+ * @typedef Metadata
+ * @property {string | undefined} [description]
+ * @property {ReadonlyArray | undefined} [entries]
+ * @property {string} origin
+ * @property {string} pathname
+ * @property {string | undefined} [slug]
+ * @property {Array | undefined} [tags]
+ * @property {string | undefined} [title]
+ */
-module.exports = list
+import {list as cards} from '../../atom/card/list.js'
+import {item} from './item.js'
+import {more} from './more.js'
-function list(section, d, options) {
+/**
+ * @param {string} href
+ * @param {Array} d
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(href, d, options) {
return cards(d, item, {more: map, ...options})
+ /**
+ * @param {number} rest
+ * @returns {Element}
+ */
function map(rest) {
- return more(section, rest)
+ return more(href, rest)
}
}
diff --git a/generate/component/article/more.js b/generate/component/article/more.js
index aa828eb2b1f5..c1bc12380613 100644
--- a/generate/component/article/more.js
+++ b/generate/component/article/more.js
@@ -1,16 +1,20 @@
-'use strict'
+/**
+ * @import {Metadata} from './list.js'
+ */
-var card = require('../../atom/card/more')
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
+import {more as card} from '../../atom/card/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
-module.exports = more
-
-function more(section, rest) {
- return card(section.pathname, [
+/**
+ * @param {string} href
+ * @param {number} rest
+ */
+export function more(href, rest) {
+ return card(href, [
'See ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'article', other: 'articles'})
+ fmtPlural(rest, {one: 'article', other: 'articles'})
])
}
diff --git a/generate/component/case/byline.js b/generate/component/case/byline.js
index 1dbfdd431e16..95d94b20b55a 100644
--- a/generate/component/case/byline.js
+++ b/generate/component/case/byline.js
@@ -1,10 +1,13 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = byline
-
-function byline() {
+/**
+ * @returns {Element}
+ */
+export function byline() {
return h('p', [
'Thousands of interesting projects are made with unified, mixing and ',
'matching building blocks together. ',
diff --git a/generate/component/case/item.js b/generate/component/case/item.js
index b70385f72aa9..cecaf7da7efc 100644
--- a/generate/component/case/item.js
+++ b/generate/component/case/item.js
@@ -1,31 +1,46 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var h = require('hastscript')
-var ghBadge = require('../../atom/micro/gh')
-var urlLine = require('../../atom/micro/url')
-var card = require('../../atom/card/item')
+/**
+ * @typedef Metadata
+ * @property {string | undefined} gh
+ * @property {string} short
+ * @property {string} src
+ * @property {string} title
+ * @property {string} url
+ */
-module.exports = item
+import assert from 'node:assert/strict'
+import {h} from 'hastscript'
+import {item as card} from '../../atom/card/item.js'
+import {gh as ghBadge} from '../../atom/micro/gh.js'
+import {url as urlLine} from '../../atom/micro/url.js'
-function item(d) {
- var {title, short, url, gh, src} = d
- var footer = []
+/**
+ *
+ * @param {Metadata} d
+ * @returns {Element}
+ */
+export function item(d) {
+ const {gh, short, src, title, url} = d
+ /** @type {Array} */
+ const footer = []
if (gh) {
footer.push(ghBadge(gh))
}
- if (url) {
- footer.push(urlLine(url))
- }
+ assert(url)
+ footer.push(urlLine(url))
return card(
url,
[
- h('.screen', h('img', {src, alt: ''})),
+ h('.screen', {}, h('img', {alt: '', src})),
h('.column', [
- h('h3.row', [h('span.ellipsis', title)]),
- h('p.double-ellipsis', short)
+ h('h3.row', [h('span.ellipsis', {}, title)]),
+ h('p.double-ellipsis', {}, short)
])
],
footer
diff --git a/generate/component/case/list.js b/generate/component/case/list.js
index 99ea09a7b7b5..99434378780d 100644
--- a/generate/component/case/list.js
+++ b/generate/component/case/list.js
@@ -1,11 +1,18 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {Options} from '../../atom/macro/list.js'
+ * @import {Metadata} from './item.js'
+ */
-var cards = require('../../atom/card/list')
-var item = require('./item')
-var more = require('./more')
+import {list as cards} from '../../atom/card/list.js'
+import {item} from './item.js'
+import {more} from './more.js'
-module.exports = list
-
-function list(d, options) {
+/**
+ * @param {ReadonlyArray} d
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(d, options) {
return cards(d, item, {more, ...options})
}
diff --git a/generate/component/case/more.js b/generate/component/case/more.js
index c3ed47c1e428..b1681c5d9157 100644
--- a/generate/component/case/more.js
+++ b/generate/component/case/more.js
@@ -1,16 +1,20 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var card = require('../../atom/card/more')
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
+import {more as card} from '../../atom/card/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
-module.exports = more
-
-function more(rest) {
+/**
+ * @param {number} rest
+ * @returns {Element}
+ */
+export function more(rest) {
return card('/community/case/', [
'See ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'case', other: 'cases'})
+ fmtPlural(rest, {one: 'case', other: 'cases'})
])
}
diff --git a/generate/component/keyword/detail.js b/generate/component/keyword/detail.js
index de5dc4b4345d..bcb59980420c 100644
--- a/generate/component/keyword/detail.js
+++ b/generate/component/keyword/detail.js
@@ -1,23 +1,29 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var box = require('../../atom/box/more')
-var sort = require('../package/helper-sort')
-var list = require('../package/list')
+import {h} from 'hastscript'
+import {more} from '../../atom/box/more.js'
+import {helperSort} from '../package/helper-sort.js'
+import {list} from '../package/list.js'
-module.exports = detail
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Array}
+ */
+export function detail(data, d) {
+ const {packagesByKeyword} = data
-function detail(data, d) {
- var {packagesByKeyword} = data
-
- var trail = box('https://www.npmjs.com/search?q=keywords:' + d, [
+ const trail = more('https://www.npmjs.com/search?q=keywords:' + d, [
'Find other packages matching ',
- h('span.tag', d),
+ h('span.tag', {}, d),
' on npm'
])
return [
- h('.content', h('h3', ['Packages matching ', d])),
- list(data, sort(data, packagesByKeyword[d]), {trail})
+ h('.content', {}, h('h3', ['Packages matching ', d])),
+ list(data, helperSort(data, packagesByKeyword[d]), {trail})
]
}
diff --git a/generate/component/keyword/helper-filter.js b/generate/component/keyword/helper-filter.js
index b567daebff66..0df19dc20313 100644
--- a/generate/component/keyword/helper-filter.js
+++ b/generate/component/keyword/helper-filter.js
@@ -1,17 +1,30 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-module.exports = filter
+const defaults = 1
-var defaults = 1
+/**
+ * Filter keywords for enough use.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @param {number | undefined} [min]
+ * @returns {Array}
+ */
+export function helperFilter(data, names, min) {
+ const value = min || defaults
+ /** @type {Array} */
+ const results = []
-// Filter keywords for enough use.
-function filter(data, names, min) {
- var {packagesByKeyword} = data
- var value = min || defaults
-
- return names.filter(filter)
-
- function filter(d) {
- return (packagesByKeyword[d] || []).length > value
+ for (const d of names) {
+ if (
+ Object.hasOwn(data.packagesByKeyword, d) &&
+ data.packagesByKeyword[d].length > value
+ ) {
+ results.push(d)
+ }
}
+
+ return results
}
diff --git a/generate/component/keyword/helper-sort.js b/generate/component/keyword/helper-sort.js
index 3431bdf3bc35..c1c9f27781fa 100644
--- a/generate/component/keyword/helper-sort.js
+++ b/generate/component/keyword/helper-sort.js
@@ -1,15 +1,25 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-var sort = require('../../util/sort')
+import {sort} from '../../util/sort.js'
-module.exports = sorter
-
-// Sort keywords by occurrence.
-function sorter(data, names) {
- var {packagesByKeyword} = data
+/**
+ * Sort keywords by occurrence.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @returns {Array}
+ */
+export function helperSort(data, names) {
+ const {packagesByKeyword} = data
return sort(names, score)
+ /**
+ * @param {string} d
+ * @returns {number}
+ */
function score(d) {
return (packagesByKeyword[d] || []).length
}
diff --git a/generate/component/keyword/item-small.js b/generate/component/keyword/item-small.js
index 0d6b92205bb3..c25896bd3fda 100644
--- a/generate/component/keyword/item-small.js
+++ b/generate/component/keyword/item-small.js
@@ -1,11 +1,17 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var tag = require('../../atom/micro/tag')
+import {tag} from '../../atom/micro/tag.js'
-module.exports = item
-
-function item(data, d) {
- var {packagesByKeyword} = data
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {ElementContent}
+ */
+export function itemSmall(data, d) {
+ const {packagesByKeyword} = data
return tag(
d,
diff --git a/generate/component/keyword/item.js b/generate/component/keyword/item.js
index 3edcd6606392..f590851c5a74 100644
--- a/generate/component/keyword/item.js
+++ b/generate/component/keyword/item.js
@@ -1,30 +1,40 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
-var box = require('../../atom/box/more')
-var sort = require('../package/helper-sort')
-var list = require('../package/list')
+import {h} from 'hastscript'
+import {more as box} from '../../atom/box/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
+import {helperSort} from '../package/helper-sort.js'
+import {list} from '../package/list.js'
-module.exports = item
-
-function item(data, d) {
- var {packagesByKeyword} = data
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Array}
+ */
+export function item(data, d) {
+ const {packagesByKeyword} = data
return [
- h('.content', h('h3', d)),
- list(data, sort(data, packagesByKeyword[d]), {max: 3, more})
+ h('.content', {}, h('h3', {}, d)),
+ list(data, helperSort(data, packagesByKeyword[d]), {max: 3, more})
]
+ /**
+ * @param {number} rest
+ * @returns {ElementContent}
+ */
function more(rest) {
return box('/explore/keyword/' + d + '/', [
'Explore ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'package', other: 'packages'}),
+ fmtPlural(rest, {one: 'package', other: 'packages'}),
' matching ',
- h('span.tag', d)
+ h('span.tag', {}, d)
])
}
}
diff --git a/generate/component/keyword/list-small.js b/generate/component/keyword/list-small.js
index 5de8e1bc01c5..e4c08518cae9 100644
--- a/generate/component/keyword/list-small.js
+++ b/generate/component/keyword/list-small.js
@@ -1,14 +1,23 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var item = require('./item-small')
+import {h} from 'hastscript'
+import {itemSmall} from './item-small.js'
-module.exports = list
+/**
+ * @param {Data} data
+ * @param {ReadonlyArray} list
+ * @returns {Element}
+ */
+export function listSmall(data, list) {
+ /** @type {Array} */
+ const results = []
-function list(data, d) {
- return h('.block', h('ol.flow', d.map(map)))
-
- function map(d) {
- return item(data, d)
+ for (const d of list) {
+ results.push(itemSmall(data, d))
}
+
+ return h('.block', {}, h('ol.flow', {}, results))
}
diff --git a/generate/component/keyword/list.js b/generate/component/keyword/list.js
index 935b8d016e1b..a20af58d659a 100644
--- a/generate/component/keyword/list.js
+++ b/generate/component/keyword/list.js
@@ -1,13 +1,25 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ * @import {Options} from '../../atom/macro/list.js'
+ */
-var list = require('../../atom/macro/list')
-var item = require('./item')
+import {list as macroList} from '../../atom/macro/list.js'
+import {item} from './item.js'
-module.exports = keywords
-
-function keywords(data, d, options) {
- return list(d, map, options)
+/**
+ * @param {Data} data
+ * @param {ReadonlyArray} d
+ * @param {Options | undefined} [options]
+ * @returns {Array}
+ */
+export function list(data, d, options) {
+ return macroList(d, map, options)
+ /**
+ * @param {string} d
+ * @returns {Array}
+ */
function map(d) {
return item(data, d)
}
diff --git a/generate/component/keyword/search-empty.js b/generate/component/keyword/search-empty.js
index f0776f647304..0808a17a17cd 100644
--- a/generate/component/keyword/search-empty.js
+++ b/generate/component/keyword/search-empty.js
@@ -1,10 +1,16 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = empty
-
-function empty(data, query) {
+/**
+ * @param {Data} data
+ * @param {string} query
+ * @returns {ElementContent}
+ */
+export function searchEmpty(data, query) {
return h('p.content', [
'We couldn’t find any keywords matching “',
query,
diff --git a/generate/component/keyword/search-preview.js b/generate/component/keyword/search-preview.js
index 2c3338561168..3930d41c151f 100644
--- a/generate/component/keyword/search-preview.js
+++ b/generate/component/keyword/search-preview.js
@@ -1,10 +1,13 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = preview
-
-function preview() {
+/**
+ * @returns {ElementContent}
+ */
+export function searchPreview() {
return h('p.content', [
'Explore the packages in the ecosystem by ',
h('a', {href: '/explore/keyword/'}, 'keyword'),
diff --git a/generate/component/keyword/search-results.js b/generate/component/keyword/search-results.js
index 1f874847008e..224427187579 100644
--- a/generate/component/keyword/search-results.js
+++ b/generate/component/keyword/search-results.js
@@ -1,3 +1 @@
-'use strict'
-
-module.exports = require('./list-small')
+export {listSmall as searchResults} from './list-small.js'
diff --git a/generate/component/member/byline.js b/generate/component/member/byline.js
index 96ce5a6eaf69..6cfab991ca9c 100644
--- a/generate/component/member/byline.js
+++ b/generate/component/member/byline.js
@@ -1,20 +1,22 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = byline
+const slug = 'unifiedjs/collective'
-var slug = 'unifiedjs/collective'
-var collective = 'https://github.com/' + slug + '/'
-
-function byline() {
+/**
+ * @returns {Element}
+ */
+export function byline() {
return h('p', [
'The unified collective is a federated system of organizations, ',
'consisting in turn of projects, governed by the team members ',
'steering them. ',
'The members govern the collective through a consensus seeking ',
'decision making model, described in detail at ',
- h('a', {href: collective}, h('code', slug)),
+ h('a', {href: 'https://github.com/' + slug + '/'}, h('code', {}, slug)),
'. '
])
}
diff --git a/generate/component/member/helper-sort.js b/generate/component/member/helper-sort.js
index fb60450100a4..1c90c48978d1 100644
--- a/generate/component/member/helper-sort.js
+++ b/generate/component/member/helper-sort.js
@@ -1,32 +1,47 @@
-'use strict'
-
-var sort = require('../../util/sort')
-
-module.exports = sorter
-
-var collective = {true: 4, false: 1}
-var roles = {releaser: 3, merger: 2, maintainer: 2}
-
-// Sort humans by “influence”.
-function sorter(data, d) {
- var scores = {}
-
- data.teams.forEach(team)
+/**
+ * @import {Human} from '../../../data/humans.js'
+ * @import {Role} from '../../../data/teams.js'
+ * @import {CommunityData} from '../../index.js'
+ */
+
+import {sort} from '../../util/sort.js'
+
+const collective = {false: 1, true: 4}
+/** @type {Partial>} */
+const roles = {maintainer: 2, merger: 2, releaser: 3}
+
+/**
+ * Sort humans by “influence”.
+ *
+ * @param {CommunityData} data
+ * @param {ReadonlyArray} d
+ * @returns {Array}
+ */
+export function helperSort(data, d) {
+ /** @type {Record} */
+ const scores = {}
+
+ for (const team of data.teams) {
+ const members = team.humans
+
+ for (const [d, role] of Object.entries(members)) {
+ const active = Boolean(team.collective && role === 'maintainer')
+
+ scores[d] =
+ (scores[d] || 0) +
+ (roles[role] || 1) *
+ // Note: ternary is just for TS, JS works fine without it.
+ collective[active ? 'true' : 'false']
+ }
+ }
return sort(d, score)
+ /**
+ * @param {Human} d
+ * @returns {number}
+ */
function score(d) {
return scores[d.github]
}
-
- function team(team) {
- var members = team.humans
-
- Object.keys(members).forEach(d => {
- var role = members[d]
- var active = Boolean(team.collective && role === 'maintainer')
-
- scores[d] = (scores[d] || 0) + (roles[role] || 1) * collective[active]
- })
- }
}
diff --git a/generate/component/member/item.js b/generate/component/member/item.js
index 145533ef4235..f2d459bc4e6e 100644
--- a/generate/component/member/item.js
+++ b/generate/component/member/item.js
@@ -1,19 +1,26 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ * @import {Human} from '../../../data/humans.js'
+ * @import {CommunityData} from '../../index.js'
+ */
-var h = require('hastscript')
-var ghBadge = require('../../atom/micro/gh')
-var npmBadge = require('../../atom/micro/npm')
-var urlLine = require('../../atom/micro/url')
-var card = require('../../atom/card/item')
+import {h} from 'hastscript'
+import {item as card} from '../../atom/card/item.js'
+import {gh as ghBadge} from '../../atom/micro/gh.js'
+import {npm as npmBadge} from '../../atom/micro/npm.js'
+import {url as urlLine} from '../../atom/micro/url.js'
-module.exports = item
+const base = 'https://github.com/'
-var base = 'https://github.com/'
-
-function item(data, d) {
- var {name, github, npm, url} = d
- var footer = [ghBadge(github)]
- var memberships = []
+/**
+ * @param {CommunityData} data
+ * @param {Human} d
+ * @returns {Element}
+ */
+export function item(data, d) {
+ const {github, name, npm, url} = d
+ /** @type {Array} */
+ const footer = [ghBadge(github)]
if (npm) {
footer.push(npmBadge('~' + npm))
@@ -23,16 +30,18 @@ function item(data, d) {
footer.push(urlLine(url))
}
- data.teams.forEach(team => {
- var role = team.humans[github]
+ /** @type {Array} */
+ const memberships = []
+
+ for (const team of data.teams) {
+ const role = team.humans[github]
+
if (role && role !== 'contributor') {
- memberships.push(h(team.collective ? 'b' : 'span', team.name))
+ const node = h(team.collective ? 'b' : 'span', team.name)
+ if (memberships.length > 0) memberships.push(', ')
+ memberships.push(node)
}
- })
-
- var roles = memberships.flatMap((d, i, all) => {
- return [d, i === all.length - 1 ? '' : ', ']
- })
+ }
return card(
base + github,
@@ -42,13 +51,13 @@ function item(data, d) {
role: 'presentation',
style: 'background-image:url(' + base + github + '.png?size=64)'
}),
- h('span.ellipsis', name)
+ h('span.ellipsis', {}, name)
]),
h(
'p.double-ellipsis',
- roles.length === 0
+ memberships.length === 0
? 'Contributor'
- : [h('span.label', 'Teams: ')].concat(roles)
+ : [h('span.label', 'Teams: '), ...memberships]
)
]),
footer
diff --git a/generate/component/member/list.js b/generate/component/member/list.js
index 18ab4364962a..48d89ad94f60 100644
--- a/generate/component/member/list.js
+++ b/generate/component/member/list.js
@@ -1,14 +1,27 @@
-'use strict'
+/**
+ * @import {Human} from '../../../data/humans.js'
+ * @import {Element} from 'hast'
+ * @import {Options} from '../../atom/macro/list.js'
+ * @import {CommunityData} from '../../index.js'
+ */
-var cards = require('../../atom/card/list')
-var sort = require('./helper-sort')
-var item = require('./item')
-var more = require('./more')
+import {list as cards} from '../../atom/card/list.js'
+import {helperSort} from './helper-sort.js'
+import {item} from './item.js'
+import {more} from './more.js'
-module.exports = list
-
-function list(data, d, options) {
- return cards(sort(data, d), map, {more, ...options})
+/**
+ * @param {CommunityData} data
+ * @param {ReadonlyArray} d
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(data, d, options) {
+ return cards(helperSort(data, d), map, {more, ...options})
+ /**
+ * @param {Human} d
+ * @returns {Element}
+ */
function map(d) {
return item(data, d)
}
diff --git a/generate/component/member/more.js b/generate/component/member/more.js
index 37be275385bd..dd675154e995 100644
--- a/generate/component/member/more.js
+++ b/generate/component/member/more.js
@@ -1,16 +1,20 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
-var card = require('../../atom/card/more')
+import {more as card} from '../../atom/card/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
-module.exports = more
-
-function more(rest) {
+/**
+ * @param {number} rest
+ * @returns {ElementContent}
+ */
+export function more(rest) {
return card('/community/member/', [
'See ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'member', other: 'members'})
+ fmtPlural(rest, {one: 'member', other: 'members'})
])
}
diff --git a/generate/component/owner/detail.js b/generate/component/owner/detail.js
index 5572370b1498..1ba78272d844 100644
--- a/generate/component/owner/detail.js
+++ b/generate/component/owner/detail.js
@@ -1,22 +1,28 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var box = require('../../atom/box/more')
-var list = require('../project/list')
-var sort = require('../project/helper-sort')
+import {h} from 'hastscript'
+import {more} from '../../atom/box/more.js'
+import {list} from '../project/list.js'
+import {helperSort} from '../project/helper-sort.js'
-module.exports = detail
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Array}
+ */
+export function detail(data, d) {
+ const {projectsByOwner} = data
-function detail(data, d) {
- var {projectsByOwner} = data
-
- var trail = box(
+ const trail = more(
'https://github.com/search?o=desc&s=stars&type=Repositories&q=user:' + d,
['Find other projects by owner @', d, ' on GitHub']
)
return [
- h('.content', h('h3', ['Projects by owner @', d])),
- list(data, sort(data, projectsByOwner[d]), {trail})
+ h('.content', {}, h('h3', ['Projects by owner @', d])),
+ list(data, helperSort(data, projectsByOwner[d]), {trail})
]
}
diff --git a/generate/component/package/detail.js b/generate/component/package/detail.js
index fe250c396a81..4ecc0a56d012 100644
--- a/generate/component/package/detail.js
+++ b/generate/component/package/detail.js
@@ -1,9 +1,15 @@
-'use strict'
+/**
+ * @import {Element, Root} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = detail
-
-function detail(data, d, tree) {
- return h('.content.readme', tree)
+/**
+ * @param {unknown} data
+ * @param {unknown} d
+ * @param {Root} tree
+ * @returns {Element}
+ */
+export function detail(data, d, tree) {
+ return h('.content.readme', {}, tree)
}
diff --git a/generate/component/package/head.js b/generate/component/package/head.js
index fd3eaf574edd..b273d8f9f5ae 100644
--- a/generate/component/package/head.js
+++ b/generate/component/package/head.js
@@ -1,35 +1,45 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var description = require('../../atom/micro/description')
-var downloads = require('../../atom/micro/downloads')
-var esm = require('../../atom/micro/esm')
-var github = require('../../atom/micro/gh')
-var graph = require('../../atom/micro/graph')
-var gzip = require('../../atom/micro/gzip')
-var license = require('../../atom/micro/license')
-var npm = require('../../atom/micro/npm')
-var score = require('../../atom/micro/score')
-var stars = require('../../atom/micro/stars')
-var verified = require('../../atom/micro/verified')
-var keywords = require('../keyword/list-small')
-var filter = require('../keyword/helper-filter')
-var sort = require('../keyword/helper-sort')
+import assert from 'node:assert/strict'
+import {h} from 'hastscript'
+import {description} from '../../atom/micro/description.js'
+import {downloads} from '../../atom/micro/downloads.js'
+import {gh} from '../../atom/micro/gh.js'
+import {gzip} from '../../atom/micro/gzip.js'
+import {license} from '../../atom/micro/license.js'
+import {npm} from '../../atom/micro/npm.js'
+import {score} from '../../atom/micro/score.js'
+import {stars} from '../../atom/micro/stars.js'
+import {verified} from '../../atom/micro/verified.js'
+import {listSmall} from '../keyword/list-small.js'
+import {helperFilter} from '../keyword/helper-filter.js'
+import {helperSort} from '../keyword/helper-sort.js'
-module.exports = head
+/**
+ * @param {Data} data
+ * @param {string} id
+ * @returns {Array}
+ */
+export function head(data, id) {
+ const {packageByName, projectByRepo} = data
+ const d = packageByName[id]
+ const project = projectByRepo[d.repo]
+ const [owner, projectName] = d.repo.split('/')
+ let [scope, packageName] =
+ /** @type {[scope: string | undefined, name: string | undefined]} */ (
+ id.split('/')
+ )
-function head(data, id) {
- var {projectByRepo, packageByName} = data
- var d = packageByName[id]
- var project = projectByRepo[d.repo]
- var [owner, projectName] = d.repo.split('/')
- var [scope, pkgName] = id.split('/')
-
- if (!pkgName) {
- pkgName = scope
- scope = null
+ if (!packageName) {
+ packageName = scope
+ scope = undefined
}
+ assert(packageName)
+
return [
h('.row-l.column-nl', [
h('hgroup.column', [
@@ -43,32 +53,33 @@ function head(data, id) {
h('span.x-hide-l.medlight.label', 'Package: '),
scope ? h('a', {href: '/explore/package/' + scope}, scope) : '',
scope ? h('span.lowlight.separator', '/') : '',
- h('a', {href: '/explore/package/' + id}, pkgName),
+ h('a', {href: '/explore/package/' + id}, packageName),
d.latest
- ? [h('span.lowlight.separator', '@'), h('span.medlight', d.latest)]
+ ? [
+ h('span.lowlight.separator', '@'),
+ h('span.medlight', {}, d.latest)
+ ]
: ''
])
]),
h('ol.flex.column.ellipsis-l', [
- graph(d.dependencies, d.dependents, id),
description(d.description, d.descriptionRich)
]),
h('.column', [
h('ol.row.justify-end-l', [
score(d.score),
verified(d.repo),
- license(d.license),
+ d.license ? license(d.license) : undefined,
stars(project.stars, d.repo),
- github(d.repo)
+ gh(d.repo)
]),
h('ol.row.justify-end-l', [
gzip(d.gzip, id),
- esm(d.esm, id),
downloads(d.downloads, id),
npm(id)
])
])
]),
- keywords(data, filter(data, sort(data, d.keywords)))
+ listSmall(data, helperFilter(data, helperSort(data, d.keywords)))
]
}
diff --git a/generate/component/package/helper-sort.js b/generate/component/package/helper-sort.js
index 15594ddf0fc9..a98a08158861 100644
--- a/generate/component/package/helper-sort.js
+++ b/generate/component/package/helper-sort.js
@@ -1,15 +1,25 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-var sort = require('../../util/sort')
+import {sort} from '../../util/sort.js'
-module.exports = sorter
-
-// Sort packages by score.
-function sorter(data, names) {
- var {packageByName} = data
+/**
+ * Sort packages by score.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @returns {Array}
+ */
+export function helperSort(data, names) {
+ const {packageByName} = data
return sort(names, score)
+ /**
+ * @param {string} d
+ * @returns {number}
+ */
function score(d) {
return packageByName[d].score || 0
}
diff --git a/generate/component/package/item.js b/generate/component/package/item.js
index 152c7e66793e..dfeee9315933 100644
--- a/generate/component/package/item.js
+++ b/generate/component/package/item.js
@@ -1,24 +1,30 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var score = require('../../atom/micro/score')
-var verified = require('../../atom/micro/verified')
-var downloads = require('../../atom/micro/downloads')
-var gzip = require('../../atom/micro/gzip')
-var box = require('../../atom/box/item')
+import {h} from 'hastscript'
+import {downloads} from '../../atom/micro/downloads.js'
+import {gzip} from '../../atom/micro/gzip.js'
+import {score} from '../../atom/micro/score.js'
+import {verified} from '../../atom/micro/verified.js'
+import {item as box} from '../../atom/box/item.js'
-module.exports = item
-
-function item(data, name) {
- var {packageByName} = data
- var d = packageByName[name]
- var value = d.descriptionRich ? d.descriptionRich.children : d.description
+/**
+ * @param {Data} data
+ * @param {string} name
+ * @returns {ElementContent}
+ */
+export function item(data, name) {
+ const {packageByName} = data
+ const d = packageByName[name]
+ const value = d.descriptionRich ? d.descriptionRich.children : d.description
return box(
'/explore/package/' + name + '/',
h('.column', [
- h('h4', name),
- h('.content.double-ellipsis', value),
+ h('h4', {}, name),
+ h('.content.double-ellipsis', {}, value),
h('ol.row', [
score(d.score),
verified(d.repo),
diff --git a/generate/component/package/list.js b/generate/component/package/list.js
index 4e818c2249e4..e184b7b75e7c 100644
--- a/generate/component/package/list.js
+++ b/generate/component/package/list.js
@@ -1,13 +1,26 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ * @import {Options} from '../../atom/macro/list.js'
+ */
-var boxes = require('../../atom/box/list')
-var item = require('./item')
-var more = require('./more')
+import {list as boxes} from '../../atom/box/list.js'
+import {item} from './item.js'
+import {more} from './more.js'
-module.exports = list
-
-function list(data, names, options) {
+/**
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(data, names, options) {
return boxes(names, map, {more, ...options})
+
+ /**
+ * @param {string} d
+ * @returns {ElementContent}
+ */
function map(d) {
return item(data, d)
}
diff --git a/generate/component/package/more.js b/generate/component/package/more.js
index 7e881d2f613a..2904ff300036 100644
--- a/generate/component/package/more.js
+++ b/generate/component/package/more.js
@@ -1,16 +1,20 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
-var box = require('../../atom/box/more')
+import {more as box} from '../../atom/box/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
-module.exports = more
-
-function more(rest) {
+/**
+ * @param {number} rest
+ * @returns {ElementContent}
+ */
+export function more(rest) {
return box('/explore/package/', [
'See ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'package', other: 'packages'})
+ fmtPlural(rest, {one: 'package', other: 'packages'})
])
}
diff --git a/generate/component/package/search-empty.js b/generate/component/package/search-empty.js
index 04477b977797..f60ef8e36130 100644
--- a/generate/component/package/search-empty.js
+++ b/generate/component/package/search-empty.js
@@ -1,10 +1,16 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = empty
-
-function empty(data, query) {
+/**
+ * @param {Data} data
+ * @param {string} query
+ * @returns {ElementContent}
+ */
+export function searchEmpty(data, query) {
return h('p.content', [
'We couldn’t find any packages matching “',
query,
diff --git a/generate/component/package/search-preview.js b/generate/component/package/search-preview.js
index 8ea8ed51bf4f..84cde0b46584 100644
--- a/generate/component/package/search-preview.js
+++ b/generate/component/package/search-preview.js
@@ -1,21 +1,26 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var more = require('../../atom/box/more')
-var fmt = require('../../util/fmt-compact')
-var pick = require('../../util/pick-random')
-var list = require('./list')
-var sort = require('./helper-sort')
+import {more} from '../../atom/box/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {pickRandom} from '../../util/pick-random.js'
+import {helperSort} from './helper-sort.js'
+import {list} from './list.js'
-module.exports = preview
+/**
+ * @param {Data} data
+ * @returns {ElementContent}
+ */
+export function searchPreview(data) {
+ const {packageByName} = data
+ const names = helperSort(data, Object.keys(packageByName))
+ const d = pickRandom(names.slice(0, 75), 5)
-function preview(data) {
- var {packageByName} = data
- var names = sort(data, Object.keys(packageByName))
- var d = pick(names.slice(0, 75), 5)
-
- var trail = more('/explore/package/', [
+ const trail = more('/explore/package/', [
'Explore the ',
- fmt(names.length),
+ fmtCompact(names.length),
' packages in the ecosystem'
])
diff --git a/generate/component/package/search-results.js b/generate/component/package/search-results.js
index 400c7f4ad0a3..34eaec785b3c 100644
--- a/generate/component/package/search-results.js
+++ b/generate/component/package/search-results.js
@@ -1,3 +1 @@
-'use strict'
-
-module.exports = require('./list')
+export {list as searchResults} from './list.js'
diff --git a/generate/component/project/detail.js b/generate/component/project/detail.js
index ad768cbfd956..c8694a39253c 100644
--- a/generate/component/project/detail.js
+++ b/generate/component/project/detail.js
@@ -1,20 +1,26 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var list = require('../package/list')
-var sort = require('../package/helper-sort')
+import {h} from 'hastscript'
+import {helperSort} from '../package/helper-sort.js'
+import {list} from '../package/list.js'
-module.exports = detail
-
-function detail(data, d) {
- var {packagesByRepo} = data
- var packages = packagesByRepo[d]
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Array}
+ */
+export function detail(data, d) {
+ const {packagesByRepo} = data
+ const packages = packagesByRepo[d] || []
return [
h(
'.content',
h('h3', ['Packages in ', packages.length > 1 ? 'monorepo' : 'project'])
),
- list(data, sort(data, packages))
+ list(data, helperSort(data, packages))
]
}
diff --git a/generate/component/project/head.js b/generate/component/project/head.js
index 9655f41e05db..96c691b35680 100644
--- a/generate/component/project/head.js
+++ b/generate/component/project/head.js
@@ -1,28 +1,32 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var description = require('../../atom/micro/description')
-var downloads = require('../../atom/micro/downloads')
-var esm = require('../../atom/micro/esm')
-var github = require('../../atom/micro/gh')
-var license = require('../../atom/micro/license')
-var score = require('../../atom/micro/score')
-var stars = require('../../atom/micro/stars')
-var url = require('../../atom/micro/url')
-var verified = require('../../atom/micro/verified')
-var topics = require('../topic/list-small')
-var filter = require('../topic/helper-filter')
-var sort = require('../topic/helper-sort')
-var reduceDownloads = require('./helper-reduce-downloads')
-var reduceEsm = require('./helper-reduce-esm')
-var reduceLicense = require('./helper-reduce-license')
-var reduceScore = require('./helper-reduce-score')
+import {h} from 'hastscript'
+import {description} from '../../atom/micro/description.js'
+import {downloads} from '../../atom/micro/downloads.js'
+import {gh} from '../../atom/micro/gh.js'
+import {license} from '../../atom/micro/license.js'
+import {score} from '../../atom/micro/score.js'
+import {stars} from '../../atom/micro/stars.js'
+import {url} from '../../atom/micro/url.js'
+import {verified} from '../../atom/micro/verified.js'
+import {helperFilter} from '../topic/helper-filter.js'
+import {helperSort} from '../topic/helper-sort.js'
+import {listSmall} from '../topic/list-small.js'
+import {helperReduceDownloads} from './helper-reduce-downloads.js'
+import {helperReduceLicense} from './helper-reduce-license.js'
+import {helperReduceScore} from './helper-reduce-score.js'
-module.exports = head
-
-function head(data, repo) {
- var d = data.projectByRepo[repo]
- var [owner, name] = repo.split('/')
+/**
+ * @param {Data} data
+ * @param {string} repo
+ * @returns {Array}
+ */
+export function head(data, repo) {
+ const d = data.projectByRepo[repo]
+ const [owner, name] = repo.split('/')
return [
h('.row-l.column-nl', [
@@ -36,23 +40,22 @@ function head(data, repo) {
])
),
h('ol.flex.column.ellipsis-l', [
- url(d.url),
+ d.url ? url(d.url) : undefined,
description(d.description, d.descriptionRich)
]),
h('.column', [
h('ol.row.justify-end-l', [
- score(reduceScore(data, repo)),
+ score(helperReduceScore(data, repo)),
verified(repo),
- license(reduceLicense(data, repo)),
+ license(helperReduceLicense(data, repo)),
stars(d.stars, repo),
- github(repo)
+ gh(repo)
]),
h('ol.row.justify-end-l', [
- esm(reduceEsm(data, repo)),
- downloads(reduceDownloads(data, repo))
+ downloads(helperReduceDownloads(data, repo))
])
])
]),
- topics(data, filter(data, sort(data, d.topics)))
+ listSmall(data, helperFilter(data, helperSort(data, d.topics)))
]
}
diff --git a/generate/component/project/helper-reduce-downloads.js b/generate/component/project/helper-reduce-downloads.js
index 778270a5d4c2..61e3ac6ea0f9 100644
--- a/generate/component/project/helper-reduce-downloads.js
+++ b/generate/component/project/helper-reduce-downloads.js
@@ -1,13 +1,24 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-module.exports = reduce
+/**
+ *
+ * @param {Data} data
+ * @param {string} repo
+ * @returns {number}
+ */
+export function helperReduceDownloads(data, repo) {
+ const {packageByName, packagesByRepo} = data
-function reduce(data, repo) {
- var {packagesByRepo, packageByName} = data
-
- return packagesByRepo[repo].reduce(sum, 0)
+ return packagesByRepo[repo]?.reduce(sum, 0) || 0
+ /**
+ * @param {number} all
+ * @param {string} d
+ * @returns {number}
+ */
function sum(all, d) {
- return all + packageByName[d].downloads
+ return all + (packageByName[d].downloads || 0)
}
}
diff --git a/generate/component/project/helper-reduce-esm.js b/generate/component/project/helper-reduce-esm.js
deleted file mode 100644
index 5aa51382264d..000000000000
--- a/generate/component/project/helper-reduce-esm.js
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict'
-
-module.exports = reduce
-
-function reduce(data, repo) {
- var {packagesByRepo, packageByName} = data
-
- return packagesByRepo[repo].some(d => packageByName[d].esm)
-}
diff --git a/generate/component/project/helper-reduce-license.js b/generate/component/project/helper-reduce-license.js
index fa4025773b6a..6de9f1a39807 100644
--- a/generate/component/project/helper-reduce-license.js
+++ b/generate/component/project/helper-reduce-license.js
@@ -1,17 +1,30 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-var unique = require('../../util/unique')
+/**
+ * @param {Data} data
+ * @param {string} repo
+ * @returns {string | undefined}
+ */
+export function helperReduceLicense(data, repo) {
+ const {packageByName, packagesByRepo} = data
+ let multi = false
+ /** @type {string | undefined} */
+ let main
-module.exports = reduce
+ for (const d of packagesByRepo[repo]) {
+ const license = packageByName[d].license
-function reduce(data, repo) {
- var {packagesByRepo, packageByName} = data
- var licenses = packagesByRepo[repo]
- .map(d => packageByName[d].license)
- .filter(Boolean)
- .filter(unique)
+ if (!license) continue
- var main = licenses[0] || null
+ if (main && license !== main) {
+ multi = true
+ break
+ }
- return licenses.length > 1 ? '±' + main : main
+ main = license
+ }
+
+ return multi ? '±' + main : main
}
diff --git a/generate/component/project/helper-reduce-score.js b/generate/component/project/helper-reduce-score.js
index e4b57e78c7b0..89e57acd3b06 100644
--- a/generate/component/project/helper-reduce-score.js
+++ b/generate/component/project/helper-reduce-score.js
@@ -1,11 +1,23 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-module.exports = reduce
+/**
+ * @param {Data} data
+ * @param {string} repo
+ * @returns {number}
+ */
+export function helperReduceScore(data, repo) {
+ const {packageByName, packagesByRepo} = data
+ const list = packagesByRepo[repo]
+ let all = 0
-function reduce(data, repo) {
- var {packagesByRepo, packageByName} = data
+ if (list) {
+ for (const d of list) {
+ const score = packageByName[d].score
+ if (score > all) all = score
+ }
+ }
- return packagesByRepo[repo]
- .map(d => packageByName[d].score)
- .reduce((all, d) => (d > all ? d : all), 0)
+ return all
}
diff --git a/generate/component/project/helper-sort.js b/generate/component/project/helper-sort.js
index 6519763efca7..3efc77506ba0 100644
--- a/generate/component/project/helper-sort.js
+++ b/generate/component/project/helper-sort.js
@@ -1,15 +1,25 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-var sort = require('../../util/sort')
-var reduce = require('./helper-reduce-score')
+import {sort} from '../../util/sort.js'
+import {helperReduceScore} from './helper-reduce-score.js'
-module.exports = sorter
-
-// Sort projects by score.
-function sorter(data, names) {
+/**
+ * Sort projects by score.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @returns {Array}
+ */
+export function helperSort(data, names) {
return sort(names, score)
+ /**
+ * @param {string} d
+ * @returns {number}
+ */
function score(d) {
- return reduce(data, d)
+ return helperReduceScore(data, d)
}
}
diff --git a/generate/component/project/item.js b/generate/component/project/item.js
index 62b1874b1d99..82d39bff9a6c 100644
--- a/generate/component/project/item.js
+++ b/generate/component/project/item.js
@@ -1,37 +1,47 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var downloads = require('../../atom/micro/downloads')
-var score = require('../../atom/micro/score')
-var stars = require('../../atom/micro/stars')
-var verified = require('../../atom/micro/verified')
-var box = require('../../atom/box/item')
-var reduceDownloads = require('./helper-reduce-downloads')
-var reduceScore = require('./helper-reduce-score')
+import {h} from 'hastscript'
+import {item as box} from '../../atom/box/item.js'
+import {downloads} from '../../atom/micro/downloads.js'
+import {score} from '../../atom/micro/score.js'
+import {stars} from '../../atom/micro/stars.js'
+import {verified} from '../../atom/micro/verified.js'
+import {helperReduceDownloads} from './helper-reduce-downloads.js'
+import {helperReduceScore} from './helper-reduce-score.js'
-module.exports = item
+/**
+ * @param {Data} data
+ * @param {string} name
+ * @returns {ElementContent}
+ */
+export function item(data, name) {
+ const {packagesByRepo, projectByRepo} = data
+ const d = projectByRepo[name]
+ const names = packagesByRepo[name]
-function item(data, name) {
- var {projectByRepo, packagesByRepo} = data
- var d = projectByRepo[name]
- var names = packagesByRepo[name]
+ if (!names) {
+ return h('p', 'Missing packages: ' + name)
+ }
- var href =
+ const href =
'/explore/' +
(names.length > 1 ? 'project/' + name : 'package/' + names[0]) +
'/'
- var value = d.descriptionRich ? d.descriptionRich.children : d.description
+ const value = d.descriptionRich ? d.descriptionRich.children : d.description
return box(
href,
h('.column', [
- h('h4', name),
- h('.content.double-ellipsis', value),
+ h('h4', {}, name),
+ h('.content.double-ellipsis', {}, value),
h('ol.row', [
- score(reduceScore(data, name)),
+ score(helperReduceScore(data, name)),
verified(name),
- downloads(reduceDownloads(data, name)),
+ downloads(helperReduceDownloads(data, name)),
stars(d.stars)
])
])
diff --git a/generate/component/project/list.js b/generate/component/project/list.js
index 8522d419cc50..e184b7b75e7c 100644
--- a/generate/component/project/list.js
+++ b/generate/component/project/list.js
@@ -1,14 +1,26 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ * @import {Options} from '../../atom/macro/list.js'
+ */
-var boxes = require('../../atom/box/list')
-var item = require('./item')
-var more = require('./more')
+import {list as boxes} from '../../atom/box/list.js'
+import {item} from './item.js'
+import {more} from './more.js'
-module.exports = list
-
-function list(data, names, options) {
+/**
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(data, names, options) {
return boxes(names, map, {more, ...options})
+ /**
+ * @param {string} d
+ * @returns {ElementContent}
+ */
function map(d) {
return item(data, d)
}
diff --git a/generate/component/project/more.js b/generate/component/project/more.js
index 5d78c1f189c6..63edd444d45e 100644
--- a/generate/component/project/more.js
+++ b/generate/component/project/more.js
@@ -1,16 +1,20 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
-var box = require('../../atom/box/more')
+import {more as box} from '../../atom/box/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
-module.exports = more
-
-function more(rest) {
+/**
+ * @param {number} rest
+ * @returns {ElementContent}
+ */
+export function more(rest) {
return box('/explore/project/', [
'See ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'project', other: 'projects'})
+ fmtPlural(rest, {one: 'project', other: 'projects'})
])
}
diff --git a/generate/component/project/search-empty.js b/generate/component/project/search-empty.js
index 9ea4b40e3890..9d7574c8f01e 100644
--- a/generate/component/project/search-empty.js
+++ b/generate/component/project/search-empty.js
@@ -1,10 +1,16 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = empty
-
-function empty(data, query) {
+/**
+ * @param {Data} data
+ * @param {string} query
+ * @returns {ElementContent}
+ */
+export function searchEmpty(data, query) {
return h('p.content', [
'We couldn’t find any projects matching “',
query,
diff --git a/generate/component/project/search-preview.js b/generate/component/project/search-preview.js
index 63bed1cfbea2..417d0da8c242 100644
--- a/generate/component/project/search-preview.js
+++ b/generate/component/project/search-preview.js
@@ -1,21 +1,26 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var more = require('../../atom/box/more')
-var fmt = require('../../util/fmt-compact')
-var pick = require('../../util/pick-random')
-var list = require('./list')
-var sort = require('./helper-sort')
+import {more} from '../../atom/box/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {pickRandom} from '../../util/pick-random.js'
+import {list} from './list.js'
+import {helperSort} from './helper-sort.js'
-module.exports = preview
+/**
+ * @param {Data} data
+ * @returns {ElementContent}
+ */
+export function searchPreview(data) {
+ const {projectByRepo} = data
+ const names = helperSort(data, Object.keys(projectByRepo))
+ const d = pickRandom(names.slice(0, 75), 5)
-function preview(data) {
- var {projectByRepo} = data
- var names = sort(data, Object.keys(projectByRepo))
- var d = pick(names.slice(0, 75), 5)
-
- var trail = more('/explore/project/', [
+ const trail = more('/explore/project/', [
'Explore the ',
- fmt(names.length),
+ fmtCompact(names.length),
' projects in the ecosystem'
])
diff --git a/generate/component/project/search-results.js b/generate/component/project/search-results.js
index 400c7f4ad0a3..34eaec785b3c 100644
--- a/generate/component/project/search-results.js
+++ b/generate/component/project/search-results.js
@@ -1,3 +1 @@
-'use strict'
-
-module.exports = require('./list')
+export {list as searchResults} from './list.js'
diff --git a/generate/component/release/explore-preview.js b/generate/component/release/explore-preview.js
new file mode 100644
index 000000000000..1f093d6fa3dd
--- /dev/null
+++ b/generate/component/release/explore-preview.js
@@ -0,0 +1,22 @@
+/**
+ * @import {Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
+
+import {more} from '../../atom/box/more.js'
+import {releases} from '../../../data/releases.js'
+import {helperFilter} from './helper-filter.js'
+import {helperSort} from './helper-sort.js'
+import {list} from './list.js'
+
+/**
+ * @param {Data} data
+ * @returns {Element}
+ */
+export function explorePreview(data) {
+ return list(
+ data,
+ helperFilter(data, helperSort(data, releases)).slice(0, 3),
+ {trail: more('/explore/release/', 'Explore recent releases')}
+ )
+}
diff --git a/generate/component/release/helper-filter.js b/generate/component/release/helper-filter.js
new file mode 100644
index 000000000000..5a7f0cc7216d
--- /dev/null
+++ b/generate/component/release/helper-filter.js
@@ -0,0 +1,32 @@
+/**
+ * @import {Data} from '../../data.js'
+ * @import {Release} from '../../../data/releases.js'
+ */
+
+// 60 days.
+const defaults = 60 * 24 * 60 * 60 * 1000
+
+/**
+ * Filter releases for recently published.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} releases
+ * @param {number | undefined} [ms]
+ * @returns {Array}
+ */
+export function helperFilter(data, releases, ms) {
+ const value = Date.now() - (ms || defaults)
+ /** @type {Array} */
+ const results = []
+
+ for (const release of releases) {
+ if (
+ data.projectByRepo[release.repo] &&
+ new Date(release.published).valueOf() > value
+ ) {
+ results.push(release)
+ }
+ }
+
+ return results
+}
diff --git a/generate/component/release/helper-sort.js b/generate/component/release/helper-sort.js
new file mode 100644
index 000000000000..137285425a25
--- /dev/null
+++ b/generate/component/release/helper-sort.js
@@ -0,0 +1,25 @@
+/**
+ * @import {Data} from '../../data.js'
+ * @import {Release} from '../../../data/releases.js'
+ */
+
+import {sort} from '../../util/sort.js'
+
+/**
+ * Sort releases by published.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} releases
+ * @returns {Array}
+ */
+export function helperSort(data, releases) {
+ return sort(releases, score)
+
+ /**
+ * @param {Release} d
+ * @returns {number}
+ */
+ function score(d) {
+ return new Date(d.published).valueOf()
+ }
+}
diff --git a/generate/component/release/item.js b/generate/component/release/item.js
new file mode 100644
index 000000000000..54f152cbef98
--- /dev/null
+++ b/generate/component/release/item.js
@@ -0,0 +1,59 @@
+/**
+ * @import {Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ * @import {Release} from '../../../data/releases.js'
+ */
+
+import {h} from 'hastscript'
+import TimeAgo from 'javascript-time-ago'
+import en from 'javascript-time-ago/locale/en'
+import {constantLocale} from '../../util/constant-locale.js'
+
+const base = 'https://github.com/'
+
+TimeAgo.addDefaultLocale(en)
+const timeAgo = new TimeAgo(constantLocale)
+const dateTime = new Intl.DateTimeFormat(constantLocale, {dateStyle: 'medium'})
+
+/**
+ * @param {Data} data
+ * @param {Release} d
+ * @returns {Element}
+ */
+export function item(data, d) {
+ const {packagesByRepo} = data
+ const {published, repo, tag} = d
+ const [owner, project] = repo.split('/')
+ const url = base + repo + '/releases/tag/' + encodeURIComponent(tag)
+
+ return h('li.block.release', [
+ h('h3', [
+ h('a', {href: '/explore/project/' + owner}, owner),
+ h('span.lowlight.separator', '/'),
+ h(
+ 'a',
+ {
+ href:
+ '/explore/' +
+ (!packagesByRepo[repo] || packagesByRepo[repo].length > 1
+ ? 'project/' + repo
+ : 'package/' + packagesByRepo[repo][0]) +
+ '/'
+ },
+ project
+ ),
+ h('span.lowlight.separator', '@'),
+ h('a', {href: url}, tag.replace(/v(\d+(?:\.\d+){2})/i, '$1')),
+ h('span.lowlight.separator', '·'),
+ h(
+ 'time.medlight',
+ {dateTime: published, title: dateTime.format(new Date(published))},
+ timeAgo.format(new Date(published))
+ )
+ ]),
+ h(
+ '.content',
+ d.descriptionRich ? d.descriptionRich.children : h('p', {}, d.description)
+ )
+ ])
+}
diff --git a/generate/component/release/list.js b/generate/component/release/list.js
new file mode 100644
index 000000000000..245af4209d16
--- /dev/null
+++ b/generate/component/release/list.js
@@ -0,0 +1,29 @@
+/**
+ * @import {Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ * @import {Options} from '../../atom/macro/list.js'
+ * @import {Release} from '../../../data/releases.js'
+ */
+
+import {h} from 'hastscript'
+import {list as macroList} from '../../atom/macro/list.js'
+import {item} from './item.js'
+import {more} from './more.js'
+
+/**
+ * @param {Data} data
+ * @param {ReadonlyArray} releases
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(data, releases, options) {
+ return h('ol.releases', {}, macroList(releases, map, {more, ...options}))
+
+ /**
+ * @param {Release} d
+ * @returns {Element}
+ */
+ function map(d) {
+ return item(data, d)
+ }
+}
diff --git a/generate/component/release/more.js b/generate/component/release/more.js
new file mode 100644
index 000000000000..7ba75e7f0c59
--- /dev/null
+++ b/generate/component/release/more.js
@@ -0,0 +1,20 @@
+/**
+ * @import {ElementContent} from 'hast'
+ */
+
+import {more as card} from '../../atom/card/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
+
+/**
+ * @param {number} rest
+ * @returns {ElementContent}
+ */
+export function more(rest) {
+ return card('/community/release/', [
+ 'See ',
+ fmtCompact(rest),
+ ' other ',
+ fmtPlural(rest, {one: 'release', other: 'releases'})
+ ])
+}
diff --git a/generate/component/scope/detail.js b/generate/component/scope/detail.js
index f7250e551127..3285e2475e05 100644
--- a/generate/component/scope/detail.js
+++ b/generate/component/scope/detail.js
@@ -1,16 +1,22 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var list = require('../package/list')
-var sort = require('../package/helper-sort')
+import {h} from 'hastscript'
+import {helperSort} from '../package/helper-sort.js'
+import {list} from '../package/list.js'
-module.exports = detail
-
-function detail(data, d) {
- var {packagesByScope} = data
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Array}
+ */
+export function detail(data, d) {
+ const {packagesByScope} = data
return [
- h('.content', h('h3', ['Packages in scope ', d])),
- list(data, sort(data, packagesByScope[d]))
+ h('.content', {}, h('h3', ['Packages in scope ', d])),
+ list(data, helperSort(data, packagesByScope[d]))
]
}
diff --git a/generate/component/sponsor/byline.js b/generate/component/sponsor/byline.js
index 7ba811a65bb2..0ff89546f4f6 100644
--- a/generate/component/sponsor/byline.js
+++ b/generate/component/sponsor/byline.js
@@ -1,19 +1,24 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = byline
-
-var oc = 'http://opencollective.com/unified'
-
-function byline() {
+/**
+ * @returns {Element}
+ */
+export function byline() {
return h('p', [
'Maintaining the collective, developing new projects, keeping everything ',
'fast and secure, and helping users, is a lot of work. ',
'Financial support lets the team spend more time maintaining existing ',
'projects and developing new ones. ',
'To support unified, become a sponsor or backer on ',
- h('a', {href: oc}, 'OpenCollective'),
+ h('a', {href: 'https://github.com/sponsors/unifiedjs'}, 'GitHub'),
+ ', ',
+ h('a', {href: 'https://thanks.dev'}, h('code', 'thanks.dev')),
+ ', or ',
+ h('a', {href: 'http://opencollective.com/unified'}, 'OpenCollective'),
'.'
])
}
diff --git a/generate/component/sponsor/helper-sort.js b/generate/component/sponsor/helper-sort.js
new file mode 100644
index 000000000000..443e6b86a93b
--- /dev/null
+++ b/generate/component/sponsor/helper-sort.js
@@ -0,0 +1,24 @@
+/**
+ * @import {SponsorRaw as GhSponsor} from '../../../crawl/github-sponsors.js'
+ * @import {Sponsor as OcSponsor} from '../../../crawl/opencollective.js'
+ */
+
+import {sort} from '../../util/sort.js'
+
+/**
+ * Sort sponsors by `total`.
+ *
+ * @param {ReadonlyArray} sponsors
+ * @returns {Array}
+ */
+export function helperSort(sponsors) {
+ return sort(sponsors, score)
+
+ /**
+ * @param {GhSponsor | OcSponsor} d
+ * @returns {number}
+ */
+ function score(d) {
+ return d.total
+ }
+}
diff --git a/generate/component/sponsor/item.js b/generate/component/sponsor/item.js
index 80f4c163f04b..efd7619db65d 100644
--- a/generate/component/sponsor/item.js
+++ b/generate/component/sponsor/item.js
@@ -1,45 +1,49 @@
-'use strict'
-
-var h = require('hastscript')
-var ocBadge = require('../../atom/micro/oc')
-var ghBadge = require('../../atom/micro/gh')
-var twitterBadge = require('../../atom/micro/tw')
-var urlLine = require('../../atom/micro/url')
-var card = require('../../atom/card/item')
-
-module.exports = item
-
-var base = 'http://opencollective.com/'
-
-function item(d) {
- var {name, description, image, oc, github, twitter, url, gold} = d
- var className = gold ? ['gold'] : []
- var footer = [ocBadge(oc)]
-
- if (github) {
- footer.push(ghBadge(github))
+/**
+ * @import {ElementContent, Element} from 'hast'
+ * @import {SponsorRaw as GhSponsor} from '../../../crawl/github-sponsors.js'
+ * @import {Sponsor as OcSponsor} from '../../../crawl/opencollective.js'
+ */
+
+import {h} from 'hastscript'
+import {item as card} from '../../atom/card/item.js'
+import {gh as ghBadge} from '../../atom/micro/gh.js'
+import {oc as ocBadge} from '../../atom/micro/oc.js'
+import {url as urlLine} from '../../atom/micro/url.js'
+
+const gh = 'https://github.com/'
+const oc = 'https://opencollective.com/'
+
+/**
+ * @param {GhSponsor | OcSponsor} d
+ * @returns {Element}
+ */
+export function item(d) {
+ /** @type {Array} */
+ const footer = []
+
+ if ('oc' in d && d.oc) {
+ footer.push(ocBadge(d.oc))
}
- if (twitter) {
- footer.push(twitterBadge(twitter))
+ if (d.github) {
+ footer.push(ghBadge(d.github))
}
- if (url) {
- footer.push(urlLine(url))
+ if (d.url) {
+ footer.push(urlLine(d.url, {rel: ['nofollow', 'sponsored']}))
}
return card(
- base + oc,
+ 'oc' in d ? oc + d.oc : gh + d.github,
h('.column', [
h('h3.row', [
h('.thumbnail', {
- className,
role: 'presentation',
- style: 'background-image:url(' + image + ')'
+ style: 'background-image:url(' + d.image + ')'
}),
- h('span.ellipsis', name)
+ h('span.ellipsis', {}, d.name || d.github)
]),
- h('p.double-ellipsis', description)
+ d.description ? h('p.double-ellipsis', {}, d.description) : []
]),
footer
)
diff --git a/generate/component/sponsor/list.js b/generate/component/sponsor/list.js
index 99ea09a7b7b5..82b1b2a125a4 100644
--- a/generate/component/sponsor/list.js
+++ b/generate/component/sponsor/list.js
@@ -1,11 +1,19 @@
-'use strict'
+/**
+ * @import {Element} from 'hast'
+ * @import {SponsorRaw as GhSponsor} from '../../../crawl/github-sponsors.js'
+ * @import {Sponsor as OcSponsor} from '../../../crawl/opencollective.js'
+ * @import {Options} from '../../atom/macro/list.js'
+ */
-var cards = require('../../atom/card/list')
-var item = require('./item')
-var more = require('./more')
+import {list as cards} from '../../atom/card/list.js'
+import {item} from './item.js'
+import {more} from './more.js'
-module.exports = list
-
-function list(d, options) {
+/**
+ * @param {ReadonlyArray} d
+ * @param {Options | undefined} [options]
+ * @returns {Element}
+ */
+export function list(d, options) {
return cards(d, item, {more, ...options})
}
diff --git a/generate/component/sponsor/more.js b/generate/component/sponsor/more.js
index 610cb87b0de4..94cb8f56e8f0 100644
--- a/generate/component/sponsor/more.js
+++ b/generate/component/sponsor/more.js
@@ -1,16 +1,20 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var card = require('../../atom/card/more')
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
+import {more as card} from '../../atom/card/more.js'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
-module.exports = more
-
-function more(rest) {
+/**
+ * @param {number} rest
+ * @returns {ElementContent}
+ */
+export function more(rest) {
return card('/community/sponsor/', [
'See ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'sponsor', other: 'sponsors'})
+ fmtPlural(rest, {one: 'sponsor', other: 'sponsors'})
])
}
diff --git a/generate/component/topic/detail.js b/generate/component/topic/detail.js
index b5cdb6ef6d49..ff9b91d30744 100644
--- a/generate/component/topic/detail.js
+++ b/generate/component/topic/detail.js
@@ -1,23 +1,29 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var box = require('../../atom/box/more')
-var list = require('../project/list')
-var sort = require('../project/helper-sort')
+import {h} from 'hastscript'
+import {more} from '../../atom/box/more.js'
+import {helperSort} from '../project/helper-sort.js'
+import {list} from '../project/list.js'
-module.exports = detail
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Array}
+ */
+export function detail(data, d) {
+ const {projectsByTopic} = data
-function detail(data, d) {
- var {projectsByTopic} = data
-
- var trail = box('https://github.com/topics/' + d, [
+ const trail = more('https://github.com/topics/' + d, [
'Find other projects matching ',
- h('span.tag', d),
+ h('span.tag', {}, d),
' on GitHub'
])
return [
- h('.content', h('h3', ['Projects matching ', d])),
- list(data, sort(data, projectsByTopic[d]), {trail})
+ h('.content', {}, h('h3', ['Projects matching ', d])),
+ list(data, helperSort(data, projectsByTopic[d]), {trail})
]
}
diff --git a/generate/component/topic/helper-filter.js b/generate/component/topic/helper-filter.js
index 48026689e878..6de7f139e7b7 100644
--- a/generate/component/topic/helper-filter.js
+++ b/generate/component/topic/helper-filter.js
@@ -1,17 +1,29 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-module.exports = filter
+const defaults = 1
-var defaults = 1
+/**
+ * Filter topics for enough use.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @param {number | undefined} [min]
+ * @returns {Array}
+ */
+export function helperFilter(data, names, min) {
+ const value = min || defaults
+ /** @type {Array} */
+ const results = []
-// Filter topics for enough use.
-function filter(data, names, min) {
- var {projectsByTopic} = data
- var value = min || defaults
+ for (const d of names) {
+ const projects = data.projectsByTopic[d]
- return names.filter(filter)
-
- function filter(d) {
- return (projectsByTopic[d] || []).length > value
+ if (projects && projects.length > value) {
+ results.push(d)
+ }
}
+
+ return results
}
diff --git a/generate/component/topic/helper-sort.js b/generate/component/topic/helper-sort.js
index c0ea33fc899a..95361f92c0a9 100644
--- a/generate/component/topic/helper-sort.js
+++ b/generate/component/topic/helper-sort.js
@@ -1,15 +1,25 @@
-'use strict'
+/**
+ * @import {Data} from '../../data.js'
+ */
-var sort = require('../../util/sort')
+import {sort} from '../../util/sort.js'
-module.exports = sorter
-
-// Sort topics by occurrence.
-function sorter(data, names) {
- var {projectsByTopic} = data
+/**
+ * Sort topics by occurrence.
+ *
+ * @param {Data} data
+ * @param {ReadonlyArray} names
+ * @returns {Array}
+ */
+export function helperSort(data, names) {
+ const {projectsByTopic} = data
return sort(names, score)
+ /**
+ * @param {string} d
+ * @returns {number}
+ */
function score(d) {
return (projectsByTopic[d] || []).length
}
diff --git a/generate/component/topic/item-small.js b/generate/component/topic/item-small.js
index deb94de34ad1..167373e62645 100644
--- a/generate/component/topic/item-small.js
+++ b/generate/component/topic/item-small.js
@@ -1,11 +1,17 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var tag = require('../../atom/micro/tag')
+import {tag} from '../../atom/micro/tag.js'
-module.exports = item
-
-function item(data, d) {
- var {projectsByTopic} = data
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {ElementContent}
+ */
+export function itemSmall(data, d) {
+ const {projectsByTopic} = data
return tag(d, (projectsByTopic[d] || []).length, '/explore/topic/' + d + '/')
}
diff --git a/generate/component/topic/item.js b/generate/component/topic/item.js
index 6c589d043584..c95086551d7f 100644
--- a/generate/component/topic/item.js
+++ b/generate/component/topic/item.js
@@ -1,30 +1,40 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var compact = require('../../util/fmt-compact')
-var plural = require('../../util/fmt-plural')
-var box = require('../../atom/box/more')
-var list = require('../project/list')
-var sort = require('../project/helper-sort')
+import {h} from 'hastscript'
+import {fmtCompact} from '../../util/fmt-compact.js'
+import {fmtPlural} from '../../util/fmt-plural.js'
+import {more as box} from '../../atom/box/more.js'
+import {helperSort} from '../project/helper-sort.js'
+import {list} from '../project/list.js'
-module.exports = item
-
-function item(data, d) {
- var {projectsByTopic} = data
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Array}
+ */
+export function item(data, d) {
+ const {projectsByTopic} = data
return [
- h('.content', h('h3', d)),
- list(data, sort(data, projectsByTopic[d]), {max: 3, more})
+ h('.content', {}, h('h3', {}, d)),
+ list(data, helperSort(data, projectsByTopic[d]), {max: 3, more})
]
+ /**
+ * @param {number} rest
+ * @returns {ElementContent}
+ */
function more(rest) {
return box('/explore/topic/' + d + '/', [
'Explore ',
- compact(rest),
+ fmtCompact(rest),
' other ',
- plural(rest, {one: 'project', other: 'projects'}),
+ fmtPlural(rest, {one: 'project', other: 'projects'}),
' matching ',
- h('span.tag', d)
+ h('span.tag', {}, d)
])
}
}
diff --git a/generate/component/topic/list-small.js b/generate/component/topic/list-small.js
index c3932dc65a80..e4c08518cae9 100644
--- a/generate/component/topic/list-small.js
+++ b/generate/component/topic/list-small.js
@@ -1,14 +1,23 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
-var item = require('./item-small')
+import {h} from 'hastscript'
+import {itemSmall} from './item-small.js'
-module.exports = topics
+/**
+ * @param {Data} data
+ * @param {ReadonlyArray} list
+ * @returns {Element}
+ */
+export function listSmall(data, list) {
+ /** @type {Array} */
+ const results = []
-function topics(data, d) {
- return h('.block', h('ol.flow', d.map(map)))
-
- function map(d) {
- return item(data, d)
+ for (const d of list) {
+ results.push(itemSmall(data, d))
}
+
+ return h('.block', {}, h('ol.flow', {}, results))
}
diff --git a/generate/component/topic/list.js b/generate/component/topic/list.js
index 47219ccefab3..53b0ff9647ba 100644
--- a/generate/component/topic/list.js
+++ b/generate/component/topic/list.js
@@ -1,12 +1,25 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ * @import {Options} from '../../atom/macro/list.js'
+ */
-var list = require('../../atom/macro/list')
-var item = require('./item')
+import {list as macros} from '../../atom/macro/list.js'
+import {item} from './item.js'
-module.exports = topics
+/**
+ * @param {Data} data
+ * @param {ReadonlyArray} d
+ * @param {Options | undefined} [options]
+ * @returns {Array}
+ */
+export function list(data, d, options) {
+ return macros(d, map, options)
-function topics(data, d, options) {
- return list(d, map, options)
+ /**
+ * @param {string} d
+ * @returns {Array}
+ */
function map(d) {
return item(data, d)
}
diff --git a/generate/component/topic/search-empty.js b/generate/component/topic/search-empty.js
index 03800e780ad8..c6de522ab867 100644
--- a/generate/component/topic/search-empty.js
+++ b/generate/component/topic/search-empty.js
@@ -1,10 +1,16 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../../data.js'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = empty
-
-function empty(data, query) {
+/**
+ * @param {Data} data
+ * @param {string} query
+ * @returns {ElementContent}
+ */
+export function searchEmpty(data, query) {
return h('p.content', [
'We couldn’t find any topics matching “',
query,
diff --git a/generate/component/topic/search-preview.js b/generate/component/topic/search-preview.js
index b4788e644763..cef9ccb95725 100644
--- a/generate/component/topic/search-preview.js
+++ b/generate/component/topic/search-preview.js
@@ -1,10 +1,13 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = preview
-
-function preview() {
+/**
+ * @returns {ElementContent}
+ */
+export function searchPreview() {
return h('p.content', [
'Explore the projects in the ecosystem by ',
h('a', {href: '/explore/topic/'}, 'topic'),
diff --git a/generate/component/topic/search-results.js b/generate/component/topic/search-results.js
index 1f874847008e..224427187579 100644
--- a/generate/component/topic/search-results.js
+++ b/generate/component/topic/search-results.js
@@ -1,3 +1 @@
-'use strict'
-
-module.exports = require('./list-small')
+export {listSmall as searchResults} from './list-small.js'
diff --git a/generate/data.js b/generate/data.js
index e112d7f047e9..c8cfc1aca7f4 100644
--- a/generate/data.js
+++ b/generate/data.js
@@ -1,34 +1,48 @@
-var projects = require('../data/projects')
-var packages = require('../data/packages')
+/**
+ * @import {Package} from '../data/packages.js'
+ * @import {Project} from '../data/projects.js'
+ */
-var data = {
- projectByRepo: {},
+/**
+ * @typedef {typeof data} Data
+ */
+
+import {packages} from '../data/packages.js'
+import {projects} from '../data/projects.js'
+
+export const data = {
+ /** @type {Record} */
packageByName: {},
- projectsByOwner: {},
+ /** @type {Record>} */
+ packagesByKeyword: {},
+ /** @type {Record>} */
packagesByRepo: {},
+ /** @type {Record>} */
packagesByScope: {},
- packagesByKeyword: {},
+ /** @type {Record} */
+ projectByRepo: {},
+ /** @type {Record>} */
+ projectsByOwner: {},
+ /** @type {Record>} */
projectsByTopic: {}
}
-module.exports = data
-
-projects.forEach(d => {
- var {repo, topics} = d
+for (const d of projects) {
+ const {repo, topics} = d
data.projectByRepo[repo] = d
index(data.projectsByOwner, repo.split('/')[0], repo)
- topics.forEach(d => {
+ for (const d of topics) {
index(data.projectsByTopic, d, repo)
- })
-})
+ }
+}
-packages.forEach(p => {
- var {name, repo, keywords} = p
- var pos = name.indexOf('/')
- var scope = pos === -1 ? null : p.name.slice(0, pos)
+for (const p of packages) {
+ const {keywords, name, repo} = p
+ const pos = name.indexOf('/')
+ const scope = pos === -1 ? undefined : p.name.slice(0, pos)
data.packageByName[name] = p
@@ -39,12 +53,26 @@ packages.forEach(p => {
}
if (keywords) {
- keywords.forEach(d => {
+ for (const d of keywords) {
index(data.packagesByKeyword, d, name)
- })
+ }
}
-})
+}
+/**
+ * @template T
+ * @param {Record>} object
+ * @param {string} key
+ * @param {T} value
+ * @returns {undefined}
+ */
function index(object, key, value) {
- ;(object[key] || (object[key] = [])).push(value)
+ let list = object[key]
+
+ if (!list) {
+ list = []
+ object[key] = list
+ }
+
+ list.push(value)
}
diff --git a/generate/index.js b/generate/index.js
index 9808c4706902..f00919aad4c2 100644
--- a/generate/index.js
+++ b/generate/index.js
@@ -1,261 +1,614 @@
-'use strict'
-
-var fs = require('fs')
-var {join, basename, extname} = require('path')
-var yaml = require('js-yaml')
-var glob = require('glob')
-var matter = require('vfile-matter')
-var all = require('p-all')
-var vfile = require('to-vfile')
-var report = require('vfile-reporter')
-var sponsors = require('../data/sponsors')
-var humans = require('../data/humans')
-var teams = require('../data/teams')
-var data = require('./data')
-var pipeline = require('./pipeline/main')
-var articlePipeline = require('./pipeline/article')
-var readmePipeline = require('./pipeline/readme')
-var descriptionPipeline = require('./pipeline/description')
-var article = require('./page/article')
-var articles = require('./page/articles')
-var cases = require('./page/cases')
-var community = require('./page/community')
-var explore = require('./page/explore')
-var home = require('./page/home')
-var keyword = require('./page/keyword')
-var keywords = require('./page/keywords')
-var learn = require('./page/learn')
-var members = require('./page/members')
-var owner = require('./page/owner')
-var pkg = require('./page/package')
-var packages = require('./page/packages')
-var project = require('./page/project')
-var projects = require('./page/projects')
-var scope = require('./page/scope')
-var sponsor = require('./page/sponsors')
-var topic = require('./page/topic')
-var topics = require('./page/topics')
-
-var users = yaml.safeLoad(fs.readFileSync(join('doc', 'showcase.yml')))
-
-var tasks = []
+/**
+ * @import {Root} from 'hast'
+ * @import {DataMap} from 'vfile'
+ * @import {Entry as FeedEntry} from 'xast-util-feed'
+ * @import {Entry as SitemapEntry} from 'xast-util-sitemap'
+ * @import {SponsorRaw as GhSponsor} from '../crawl/github-sponsors.js'
+ * @import {Sponsor as OcSponsor} from '../crawl/opencollective.js'
+ * @import {Human} from '../data/humans.js'
+ * @import {Release} from '../data/releases.js'
+ * @import {Team} from '../data/teams.js'
+ * @import {Metadata} from './component/article/list.js'
+ *
+ * @typedef CommunityData
+ * @property {ReadonlyArray} humans
+ * @property {ReadonlyArray} sponsors
+ * @property {ReadonlyArray} teams
+ * @property {ReadonlyArray} users
+ *
+ * @typedef Page
+ * @property {VFile} file
+ * @property {Root} tree
+ *
+ * @typedef ShowcaseUser
+ * @property {string} gh
+ * @property {string} short
+ * @property {string} src
+ * @property {string} title
+ * @property {string} url
+ */
+
+import assert from 'node:assert/strict'
+import fs from 'node:fs/promises'
+import {glob} from 'glob'
+import {fromHtml} from 'hast-util-from-html'
+import {isElement} from 'hast-util-is-element'
+import {sanitize} from 'hast-util-sanitize'
+import {select} from 'hast-util-select'
+import {toHtml} from 'hast-util-to-html'
+import {urlAttributes} from 'html-url-attributes'
+import {read, write} from 'to-vfile'
+import {visit} from 'unist-util-visit'
+import {matter} from 'vfile-matter'
+import {reporter} from 'vfile-reporter'
+import {VFile} from 'vfile'
+import {rss} from 'xast-util-feed'
+import {sitemap} from 'xast-util-sitemap'
+import {toXml} from 'xast-util-to-xml'
+import yaml from 'yaml'
+import {humans} from '../data/humans.js'
+import {releases as dataReleases} from '../data/releases.js'
+import {sponsors as ocSponsors} from '../data/opencollective.js'
+import {teams} from '../data/teams.js'
+import {article as articlePipeline} from './pipeline/article.js'
+import {description as descriptionPipeline} from './pipeline/description.js'
+import {main as pipeline} from './pipeline/main.js'
+import {readme as readmePipeline} from './pipeline/readme.js'
+import {release as createReleasePipeline} from './pipeline/release.js'
+import {articles} from './page/articles.js'
+import {article} from './page/article.js'
+import {cases} from './page/cases.js'
+import {community} from './page/community.js'
+import {explore} from './page/explore.js'
+import {home} from './page/home.js'
+import {keywords} from './page/keywords.js'
+import {keyword} from './page/keyword.js'
+import {learn} from './page/learn.js'
+import {members} from './page/members.js'
+import {owner} from './page/owner.js'
+import {packages} from './page/packages.js'
+import {renderPackage} from './page/package.js'
+import {projects} from './page/projects.js'
+import {project} from './page/project.js'
+import {releases} from './page/releases.js'
+import {scope} from './page/scope.js'
+import {sponsor} from './page/sponsors.js'
+import {topics} from './page/topics.js'
+import {topic} from './page/topic.js'
+import {data} from './data.js'
+
+const ghSponsors = /** @type {Array} */ (
+ JSON.parse(
+ await fs.readFile(
+ new URL('../data/github-sponsors.json', import.meta.url),
+ 'utf8'
+ )
+ )
+)
+
+const sponsors = [...ocSponsors, ...ghSponsors]
+
+const origin = 'https://unifiedjs.com'
+
+const users = /** @type {Array} */ (
+ yaml.parse(
+ await fs.readFile(new URL('../doc/showcase.yml', import.meta.url), 'utf8')
+ )
+)
+
+/** @type {Array<() => Page | Promise>} */
+const tasks = []
// Render descriptions
-expandDescription(data.projectByRepo)
-expandDescription(data.packageByName)
-
-var entries = glob.sync('doc/learn/**/*.md').map(input => {
- var file = matter(vfile.readSync(input))
- var slug = basename(input, extname(input))
- var {group, tags} = file.data.matter
-
- file.data.meta = {
- type: 'article',
- tags: [].concat(group || [], tags || []),
- pathname: ['', 'learn', group, slug, ''].join('/')
+await expandDescription(data.projectByRepo)
+await expandDescription(data.packageByName)
+await expandReleases(dataReleases)
+
+const input = await glob('doc/learn/**/*.md')
+/** @type {Array} */
+const entries = []
+
+for (const d of input) {
+ const file = await read(d)
+
+ matter(file)
+ const slug = file.stem
+ assert(slug)
+ let meta = file.data.meta
+
+ if (!meta) {
+ meta = {}
+ file.data.meta = meta
}
- return file
-})
+ assert(file.data.matter)
+ const {group, tags} = file.data.matter
+ assert(group)
+ meta.type = 'article'
+ meta.tags = [group]
+ if (tags) meta.tags.push(...tags)
+ meta.origin = origin
+ meta.pathname = ['', 'learn', group, slug, ''].join('/')
-var sections = [
+ entries.push(file)
+}
+
+const minidata = [
{
- slug: 'guide',
- title: 'Guides',
description:
- 'Learn unified through articles, each telling a story, that walks through how to complete a certain task'
+ 'Learn unified through articles, each telling a story, that walks through how to complete a certain task',
+ slug: 'guide',
+ title: 'Guides'
},
{
- slug: 'recipe',
- title: 'Recipes',
description:
- 'Learn unified through byte-sized articles, that stand on their own, explaining how to complete a small, specific, focussed task'
+ 'Learn unified through byte-sized articles, that stand on their own, explaining how to complete a small, specific, focussed task',
+ slug: 'recipe',
+ title: 'Recipes'
}
-].map(d => {
- var {slug} = d
- return {
+]
+
+/** @type {Array} */
+const sections = []
+
+for (const d of minidata) {
+ const {slug} = d
+ /** @type {Array} */
+ const groupEntries = []
+
+ for (const d of entries) {
+ if (
+ d.data.matter &&
+ !d.data.matter.archive &&
+ d.data.matter.group === slug
+ ) {
+ groupEntries.push(d)
+ }
+ }
+
+ sections.push({
...d,
- tags: [slug, 'learn'],
+ entries: groupEntries,
+ origin,
pathname: '/learn/' + slug + '/',
- entries: entries.filter(d => d.data.matter.group === slug)
+ tags: [slug, 'learn']
+ })
+}
+
+page(
+ function () {
+ return home({...data, articles: entries, humans, sponsors, teams, users})
+ },
+ {
+ description:
+ 'Content as structured data: unified compiles content and provides hundreds of packages to work with content',
+ origin,
+ pathname: '/'
}
-})
+)
-page(() => home({...data, articles: entries, sponsors, users}), {
- description:
- 'Content as structured data: unified compiles content and provides hundreds of packages to work with content',
- pathname: '/'
-})
+for (const file of entries) {
+ tasks.push(async function () {
+ const inputTree = articlePipeline.parse(file)
+ const outputTree = await articlePipeline.run(inputTree, file)
-entries.forEach(file => {
- tasks.push(() =>
- articlePipeline
- .run(articlePipeline.parse(file), file)
- .then(tree => ({tree: article(tree, file), file}))
- )
-})
+ return {file, tree: article(outputTree, file)}
+ })
+}
-sections.forEach(section => {
- var {title, description, tags, pathname, entries} = section
- var meta = {title, description, tags, pathname}
- page(() => articles(meta, entries), meta)
-})
+for (const section of sections) {
+ const {description, entries, pathname, tags, title} = section
+ const meta = {
+ description,
+ origin,
+ pathname,
+ tags,
+ title
+ }
+ assert(entries)
+ page(function () {
+ return articles(meta, entries)
+ }, meta)
+}
-page(() => learn(sections), {
- title: 'Learn',
- tags: ['learn', 'recipe', 'guide', 'tutorial'],
- description: 'Learn unified through guides and recipes',
- pathname: '/learn/'
-})
+page(
+ function () {
+ return learn(sections)
+ },
+ {
+ description: 'Learn unified through guides and recipes',
+ origin,
+ pathname: '/learn/',
+ tags: ['learn', 'recipe', 'guide', 'tutorial'],
+ title: 'Learn'
+ }
+)
-page(() => explore(data), {
- title: 'Explore',
- description: 'Explore the unified ecosystem',
- pathname: '/explore/'
-})
+page(
+ function () {
+ return explore(data)
+ },
+ {
+ description: 'Explore the unified ecosystem',
+ origin,
+ pathname: '/explore/',
+ title: 'Explore'
+ }
+)
-page(() => keywords(data), {
- title: 'Keywords - Explore',
- description: 'Explore packages in the unified ecosystem by keyword',
- pathname: '/explore/keyword/'
-})
+page(
+ function () {
+ return keywords(data)
+ },
+ {
+ description: 'Explore packages in the unified ecosystem by keyword',
+ origin,
+ pathname: '/explore/keyword/',
+ title: 'Keywords - Explore'
+ }
+)
+
+for (const d of Object.keys(data.packagesByKeyword)) {
+ page(
+ function () {
+ return keyword(data, d)
+ },
+ {
+ description:
+ 'Explore packages in the unified ecosystem with the “' +
+ d +
+ '” keyword',
+ origin,
+ pathname: '/explore/keyword/' + d + '/',
+ title: d + ' - Keywords'
+ }
+ )
+}
-Object.keys(data.packagesByKeyword).forEach(d => {
- page(() => keyword(data, d), {
- title: d + ' - Keywords',
- description:
- 'Explore packages in the unified ecosystem with the “' + d + '” keyword',
- pathname: '/explore/keyword/' + d + '/'
- })
-})
+for (const d of Object.keys(data.packagesByScope)) {
+ page(
+ function () {
+ return scope(data, d)
+ },
+ {
+ description:
+ 'Explore packages in the unified ecosystem in the “' + d + '” scope',
+ origin,
+ pathname: '/explore/package/' + d + '/',
+ title: d + ' - Scope'
+ }
+ )
+}
-Object.keys(data.packagesByScope).forEach(d => {
- page(() => scope(data, d), {
- title: d + ' - Scope',
- description:
- 'Explore packages in the unified ecosystem in the “' + d + '” scope',
- pathname: '/explore/package/' + d + '/'
- })
-})
+page(
+ function () {
+ return topics(data)
+ },
+ {
+ description: 'Explore projects in the unified ecosystem by topic',
+ origin,
+ pathname: '/explore/topic/',
+ title: 'Topics - Explore'
+ }
+)
+
+for (const d of Object.keys(data.projectsByTopic)) {
+ page(
+ function () {
+ return topic(data, d)
+ },
+ {
+ description:
+ 'Explore projects in the unified ecosystem with the “' + d + '” topic',
+ origin,
+ pathname: '/explore/topic/' + d + '/',
+ title: d + ' - Topics'
+ }
+ )
+}
-page(() => topics(data), {
- title: 'Topics - Explore',
- description: 'Explore projects in the unified ecosystem by topic',
- pathname: '/explore/topic/'
-})
+for (const d of Object.keys(data.projectsByOwner)) {
+ page(
+ function () {
+ return owner(data, d)
+ },
+ {
+ description: 'Explore projects in the unified ecosystem by “@' + d + '”',
+ origin,
+ pathname: '/explore/project/' + d + '/',
+ title: '@' + d + ' - Owner'
+ }
+ )
+}
-Object.keys(data.projectsByTopic).forEach(d => {
- page(() => topic(data, d), {
- title: d + ' - Topics',
- description:
- 'Explore projects in the unified ecosystem with the “' + d + '” topic',
- pathname: '/explore/topic/' + d + '/'
- })
-})
+page(
+ function () {
+ return packages(data)
+ },
+ {
+ description: 'Explore all packages in the unified ecosystem',
+ origin,
+ pathname: '/explore/package/',
+ title: 'Packages - Explore'
+ }
+)
-Object.keys(data.projectsByOwner).forEach(d => {
- page(() => owner(data, d), {
- title: '@' + d + ' - Owner',
- description: 'Explore projects in the unified ecosystem by “@' + d + '”',
- pathname: '/explore/project/' + d + '/'
- })
-})
+page(
+ function () {
+ return projects(data)
+ },
+ {
+ description: 'Explore all projects in the unified ecosystem',
+ origin,
+ pathname: '/explore/project/',
+ title: 'Projects - Explore'
+ }
+)
-page(() => packages(data), {
- title: 'Packages - Explore',
- description: 'Explore all packages in the unified ecosystem',
- pathname: '/explore/package/'
-})
+page(
+ function () {
+ return releases(data)
+ },
+ {
+ description: 'Explore recent releases in the unified ecosystem',
+ origin,
+ pathname: '/explore/release/',
+ title: 'Releases - Explore'
+ }
+)
+
+for (const [d, p] of Object.entries(data.projectByRepo)) {
+ const {description, topics} = p
+
+ page(
+ function () {
+ return project(data, d)
+ },
+ {
+ description,
+ origin,
+ pathname: '/explore/project/' + d + '/',
+ tags: [...topics],
+ title: d
+ }
+ )
+}
-page(() => projects(data), {
- title: 'Projects - Explore',
- description: 'Explore all projects in the unified ecosystem',
- pathname: '/explore/project/'
-})
+for (const [d, pack] of Object.entries(data.packageByName)) {
+ const {description, keywords, readmeName, repo} = pack
+ const input = new URL('../data/readme/' + readmeName, import.meta.url)
+ const pathname = '/explore/package/' + d + '/'
-Object.keys(data.projectByRepo).forEach(d => {
- var {description, topics} = data.projectByRepo[d]
+ tasks.push(async function () {
+ const file = await read(input)
+ const meta = {description, origin, pathname, tags: keywords, title: d}
- page(() => project(data, d), {
- title: d,
- description,
- tags: topics,
- pathname: '/explore/project/' + d + '/'
- })
-})
+ file.data = {meta, repo}
+ const inputTree = readmePipeline.parse(file)
+ const outputTree = await readmePipeline.run(inputTree, file)
-Object.keys(data.packageByName).forEach(d => {
- var pack = data.packageByName[d]
- var {description, readmeName, repo, manifestBase, keywords} = pack
- var input = join('data', 'readme', readmeName)
- var pathname = '/explore/package/' + d + '/'
+ return {file, tree: renderPackage(data, d, outputTree)}
+ })
+}
- tasks.push(() =>
- vfile.read(input).then(file => {
- var meta = {title: d, description, pathname, tags: keywords}
+page(
+ function () {
+ return community({humans, sponsors, teams, users})
+ },
+ {
+ description: 'Get involved, meet the team, and support us',
+ origin,
+ pathname: '/community/',
+ title: 'Community'
+ }
+)
- file.data = {meta, repo, dirname: manifestBase}
+page(
+ function () {
+ return members({humans, sponsors, teams, users})
+ },
+ {
+ description: 'Meet the team maintaining unified',
+ origin,
+ pathname: '/community/member/',
+ title: 'Team - Community'
+ }
+)
- return readmePipeline
- .run(readmePipeline.parse(file), file)
- .then(tree => ({tree: pkg(data, d, tree), file}))
- })
- )
-})
+page(
+ function () {
+ return sponsor(sponsors)
+ },
+ {
+ description: 'Support unified by becoming a sponsor',
+ origin,
+ pathname: '/community/sponsor/',
+ title: 'Sponsor - Community'
+ }
+)
-page(() => community({teams, humans, sponsors, users}), {
- title: 'Community',
- description: 'Get involved, meet the team, and support us',
- pathname: '/community/'
-})
+page(
+ function () {
+ return cases(users)
+ },
+ {
+ description: 'Showcase of interesting use cases of unified',
+ origin,
+ pathname: '/community/case/',
+ title: 'Showcase - Community'
+ }
+)
+
+/** @type {Array} */
+const sitemapEntries = []
+/** @type {Array} */
+const learnFiles = []
+
+for (const render of tasks) {
+ const {tree, file} = await render()
+ file.value = pipeline.stringify(await pipeline.run(tree, file), file)
+ await write(file)
+ console.error(reporter(file))
+ const meta = file.data.meta || {}
+ const matter = file.data.matter || {}
+ const pathname = matter.pathname || meta.pathname
+ const modified = matter.modified || meta.modified
+ assert(pathname)
+ sitemapEntries.push({url: new URL(pathname, origin).href, modified})
+
+ if (!matter.archive && matter.group) {
+ learnFiles.push(file)
+ }
+}
-page(() => members({teams, humans}), {
- title: 'Team - Community',
- description: 'Meet the team maintaining unified',
- pathname: '/community/member/'
+await fs.writeFile(
+ new URL('../build/CNAME', import.meta.url),
+ new URL(origin).host + '\n'
+)
+
+console.error('✔ `/CNAME`')
+
+await fs.writeFile(
+ new URL('../build/robots.txt', import.meta.url),
+ [
+ 'User-agent: *',
+ 'Allow: /',
+ 'Sitemap: ' + new URL('sitemap.xml', origin).href,
+ ''
+ ].join('\n')
+)
+
+console.error('✔ `/robots.txt`')
+
+await fs.writeFile(
+ new URL('../build/sitemap.xml', import.meta.url),
+ toXml(sitemap(sitemapEntries))
+)
+
+console.error('✔ `/sitemap.xml`')
+
+learnFiles.sort(function (a, b) {
+ assert(a.data.matter?.published)
+ assert(b.data.matter?.published)
+ return (
+ new Date(b.data.matter.published).valueOf() -
+ new Date(a.data.matter.published).valueOf()
+ )
})
-page(() => sponsor(sponsors), {
- title: 'Sponsor - Community',
- description: 'Support unified by becoming a sponsor',
- pathname: '/community/sponsor/'
-})
+const newestLearnFiles = learnFiles.slice(0, 10)
+/** @type {Array} */
+const learnEntries = []
+
+for (const file of newestLearnFiles) {
+ const tree = fromHtml(file.value)
+ const body = select('main', tree)
+ assert(body)
+ const fragment = sanitize(body)
+
+ const {matter, meta} = file.data
+ assert(matter)
+ assert(meta)
+ assert(meta.pathname)
+ const base = new URL(meta.pathname, origin)
+
+ visit(fragment, 'element', function (node, index, parent) {
+ // Make URLs absolute.
+ for (const property in node.properties) {
+ if (
+ Object.hasOwn(urlAttributes, property) &&
+ isElement(node, urlAttributes[property]) &&
+ node.properties[property] !== null &&
+ node.properties[property] !== undefined
+ ) {
+ node.properties[property] = new URL(
+ String(node.properties[property]),
+ base
+ ).href
+ }
+ }
+
+ if (parent && typeof index === 'number') {
+ // Drop empty spans, left from syntax highlighting.
+ if (
+ node.tagName === 'span' &&
+ Object.keys(node.properties).length === 0
+ ) {
+ parent.children.splice(index, 1, ...node.children)
+ return index
+ }
+
+ // Drop tooltips from twoslash.
+ if (
+ node.tagName === 'div' &&
+ typeof node.properties.id === 'string' &&
+ node.properties.id.startsWith('user-content-rehype-twoslash')
+ ) {
+ parent.children.splice(index, 1)
+ return index
+ }
+ }
+ })
-page(() => cases(users), {
- title: 'Showcase - Community',
- description: 'Showcase of interesting use cases of unified',
- pathname: '/community/case/'
-})
+ learnEntries.push({
+ author: matter.author,
+ descriptionHtml: toHtml(fragment),
+ description: matter.description,
+ modified: matter.modified,
+ published: matter.published,
+ title: matter.title,
+ url: base.href
+ })
+}
-var promises = tasks.map(fn => () => {
- return Promise.resolve(fn())
- .then(({tree, file}) =>
- pipeline.run(tree, file).then(tree => ({tree, file}))
+await fs.writeFile(
+ new URL('../build/rss.xml', import.meta.url),
+ toXml(
+ rss(
+ {
+ feedUrl: new URL('rss.xml', origin).href,
+ lang: 'en',
+ title: 'unified - learn',
+ url: origin
+ },
+ learnEntries
)
- .then(({tree, file}) => {
- file.contents = pipeline.stringify(tree, file)
- return file
- })
- .then(file => vfile.write(file).then(() => file))
- .then(done, done)
-
- function done(x) {
- console.log(report(x))
- }
-})
-
-all(promises, {concurrency: 50})
-
-function page(fn, meta) {
- tasks.push(() => {
- return {tree: fn(), file: vfile({data: {meta}})}
+ ) + '\n'
+)
+
+console.error('✔ `/rss.xml`')
+
+/**
+ *
+ * @param {() => Root} render
+ * @param {DataMap['meta']} meta
+ * @returns {undefined}
+ */
+function page(render, meta) {
+ tasks.push(function () {
+ return {file: new VFile({data: {meta}}), tree: render()}
})
}
-function expandDescription(map) {
- Object.keys(map).forEach(id => {
- var d = map[id]
- var tree = descriptionPipeline.parse(d.description)
- d.descriptionRich = descriptionPipeline.runSync(tree)
- })
+/**
+ * @template {{description?: string, descriptionRich?: Root}} T
+ * @param {Record} map
+ * @returns {Promise}
+ */
+async function expandDescription(map) {
+ for (const d of Object.values(map)) {
+ const tree = descriptionPipeline.parse(d.description)
+ d.descriptionRich = await descriptionPipeline.run(tree)
+ }
+}
+
+/**
+ * @param {ReadonlyArray} releases
+ * @returns {Promise}
+ */
+async function expandReleases(releases) {
+ for (const d of releases) {
+ const pipeline = createReleasePipeline(d)
+ d.descriptionRich = await pipeline.run(pipeline.parse(d.description))
+ }
}
diff --git a/generate/molecule/breadcrumbs.js b/generate/molecule/breadcrumbs.js
index ee366c925ff1..2ee54a7c742c 100644
--- a/generate/molecule/breadcrumbs.js
+++ b/generate/molecule/breadcrumbs.js
@@ -1,47 +1,67 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = line
+const slash = '/'
-var slash = '/'
-
-var overwrites = {
- learn: 'Learn',
- guide: ['Guides', 'Guide'],
- recipe: ['Recipes', 'Recipe'],
+/** @type {Record} */
+const overwrites = {
+ case: 'Cases',
+ community: 'Community',
explore: 'Explore',
keyword: ['Keywords', 'Keyword'],
- topic: ['Topics', 'Topic'],
+ guide: ['Guides', 'Guide'],
+ learn: 'Learn',
+ member: 'Members',
package: ['Packages', 'Package'],
project: ['Projects', 'Project'],
- community: 'Community',
+ recipe: ['Recipes', 'Recipe'],
+ release: 'Releases',
sponsor: 'Sponsors',
- case: 'Cases',
- member: 'Members'
+ topic: ['Topics', 'Topic']
}
-function line(filepath, title) {
- return filepath
- .split(slash)
- .filter(Boolean)
- .flatMap(map)
+/**
+ * @param {string} filepath
+ * @param {string | null | undefined} [title]
+ * @returns {Array}
+ */
+export function breadcrumbs(filepath, title) {
+ const parts = filepath.split(slash).filter(Boolean)
+ let index = -1
+ /** @type {Array} */
+ const results = []
- function map(d, i, data) {
- var last = data.length - 1 === i
- var components = data.slice(0, i + 1)
- var href = slash + components.join(slash) + slash
- var node = h('a', {href}, word(last && title ? title : d, last))
+ while (++index < parts.length) {
+ const part = parts[index]
+ const last = parts.length - 1 === index
+ const components = parts.slice(0, index + 1)
+ const href = slash + components.join(slash) + slash
+ let node = h('a', {href}, word(last && title ? title : part, last))
if (last) {
- node = h('span.content', node)
+ node.properties.rel = ['canonical']
+ node = h('span.content', {}, node)
}
- return [node, last ? '' : h('span.lowlight.separator', '/')]
+ results.push(node)
+
+ if (!last) {
+ results.push(h('span.lowlight.separator', '/'))
+ }
}
+
+ return results
}
+/**
+ * @param {string} d
+ * @param {boolean} last
+ * @returns {string}
+ */
function word(d, last) {
- var value = d in overwrites ? overwrites[d] : d
+ const value = d in overwrites ? overwrites[d] : d
return typeof value === 'string' ? value : value[last ? 0 : 1]
}
diff --git a/generate/molecule/footer.js b/generate/molecule/footer.js
index 94adb4d29865..57adf095fc0a 100644
--- a/generate/molecule/footer.js
+++ b/generate/molecule/footer.js
@@ -1,26 +1,26 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = footer
-
-function footer() {
+/** @returns {ElementContent} */
+export function footer() {
return h('footer.container', [
h('nav.row-l.flex', [
h('ol.row.flex.x-show-l', [
- h('li', h('a.unified', {href: '/'}, [h('span.hl', 'uni'), 'fied'])),
- h('li', h('a', {href: '/explore/'}, 'Explore')),
- h('li', h('a', {href: '/learn/'}, 'Learn')),
- h('li', h('a', {href: '/community/'}, 'Community'))
+ h('li', {}, h('a.unified', {href: '/'}, [h('span.hl', 'uni'), 'fied'])),
+ h('li', {}, h('a', {href: '/explore/'}, 'Explore')),
+ h('li', {}, h('a', {href: '/learn/'}, 'Learn')),
+ h('li', {}, h('a', {href: '/community/'}, 'Community'))
]),
h('ol.row.justify-end-l', [
- h('li', h('a', {href: 'https://spectrum.chat/unified'}, 'Spectrum')),
+ h('li', {}, h('a', {href: '/rss.xml'}, 'RSS Feed')),
h(
'li',
h('a', {href: 'https://opencollective.com/unified'}, 'OpenCollective')
),
- h('li', h('a', {href: 'https://twitter.com/unifiedjs'}, 'Twitter')),
- h('li', h('a', {href: 'https://github.com/unifiedjs'}, 'GitHub'))
+ h('li', {}, h('a', {href: 'https://github.com/unifiedjs'}, 'GitHub'))
])
])
])
diff --git a/generate/molecule/header.js b/generate/molecule/header.js
index a9afa8e1110c..851a0887b468 100644
--- a/generate/molecule/header.js
+++ b/generate/molecule/header.js
@@ -1,31 +1,31 @@
-'use strict'
+/**
+ * @import {ElementContent, Element} from 'hast'
+ */
-var h = require('hastscript')
-var tw = require('../atom/icon/tw')
-var gh = require('../atom/icon/gh')
+import {h} from 'hastscript'
+import {gh} from '../atom/icon/gh.js'
-module.exports = header
+/**
+ * @returns {ElementContent}
+ */
+export function header() {
+ const github = gh()
+
+ enlarge(github)
-function header() {
return h('header.container', [
h('.row-l', [
- h('h1', h('a.unified', {href: '/'}, [h('span.hl', 'uni'), 'fied'])),
+ h('h1', {}, h('a.unified', {href: '/'}, [h('span.hl', 'uni'), 'fied'])),
h('nav.row-l.flex', {ariaLabel: 'Main navigation'}, [
h('ol.row.flex', [
- h('li', h('a', {href: '/explore/'}, 'Explore')),
- h('li', h('a', {href: '/learn/'}, 'Learn')),
- h('li', h('a', {href: '/community/'}, 'Community'))
+ h('li', {}, h('a', {href: '/explore/'}, 'Explore')),
+ h('li', {}, h('a', {href: '/learn/'}, 'Learn')),
+ h('li', {}, h('a', {href: '/community/'}, 'Community'))
]),
h('ol.row.x-show-l.justify-end-l', [
- h('li', [
- h('a', {href: 'https://twitter.com/unifiedjs'}, [
- enlarge(tw()),
- h('span.x-hide-l', 'Twitter')
- ])
- ]),
h('li', [
h('a', {href: 'https://github.com/unifiedjs'}, [
- enlarge(gh()),
+ github,
h('span.x-hide-l', 'GitHub')
])
])
@@ -35,12 +35,13 @@ function header() {
])
}
+/**
+ * @param {Element} node
+ * @returns {undefined}
+ */
function enlarge(node) {
- Object.assign(node.properties, {
- role: 'img',
- width: 24,
- height: 24,
- className: ['icon', 'x-show-l']
- })
- return node
+ node.properties.className = ['icon', 'x-show-l']
+ node.properties.height = 24
+ node.properties.role = 'img'
+ node.properties.width = 24
}
diff --git a/generate/molecule/search.js b/generate/molecule/search.js
index 31eaf41ec545..5fc3732b8a5f 100644
--- a/generate/molecule/search.js
+++ b/generate/molecule/search.js
@@ -1,26 +1,32 @@
-'use strict'
+/**
+ * @import {ElementContent} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var pick = require('pick-random')
-var sortPackages = require('../component/package/helper-sort')
+import {h} from 'hastscript'
+import pickRandom from 'pick-random'
+import {helperSort} from '../component/package/helper-sort.js'
-module.exports = search
-
-function search(data, name) {
- var names = Object.keys(data.packageByName)
- var random = pick(sortPackages(data, names).slice(0, 75))[0]
+/**
+ * @param {Data} data
+ * @param {string} name
+ * @returns {ElementContent}
+ */
+export function search(data, name) {
+ const names = Object.keys(data.packageByName)
+ const random = pickRandom(helperSort(data, names).slice(0, 75))[0]
return h('form.search', {action: '/explore/'}, [
- h('label', {for: name}, 'Search ecosystem:'),
+ h('label', {htmlFor: name}, 'Search ecosystem:'),
h('.row', [
h('input.flex', {
- id: name,
- name,
- type: 'search',
+ autoCapitalize: 'none',
autoComplete: 'off',
autoCorrect: 'off',
- autoCapitalize: 'none',
- placeholder: 'Such as for “' + random + '”'
+ id: name,
+ name,
+ placeholder: 'Such as for “' + random + '”',
+ type: 'search'
}),
h('button', 'Submit')
])
diff --git a/generate/page/article.js b/generate/page/article.js
index 96c0a18b1980..6aba96fc88ad 100644
--- a/generate/page/article.js
+++ b/generate/page/article.js
@@ -1,18 +1,29 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {VFile} from 'vfile'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var detail = require('../component/article/detail')
-var page = require('./page')
+import assert from 'node:assert/strict'
+import {h} from 'hastscript'
+import {detail} from '../component/article/detail.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = article
+/**
+ * @param {Root} tree
+ * @param {VFile} file
+ * @returns {Root}
+ */
+export function article(tree, file) {
+ const {matter, meta} = file.data
+ assert(matter)
+ assert(meta)
+ const {title} = matter
+ const {pathname} = meta
+ assert(pathname)
-function article(tree, file) {
- var {matter, meta} = file.data
- var {title} = matter
- var {pathname} = meta
-
- return page(h('.row-l.column-l', h('h2', breadcrumbs(pathname, title))), [
- detail(tree, file)
- ])
+ return page(
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs(pathname, title))),
+ [detail(tree)]
+ )
}
diff --git a/generate/page/articles.js b/generate/page/articles.js
index 75d137c0f29e..17499ba67585 100644
--- a/generate/page/articles.js
+++ b/generate/page/articles.js
@@ -1,18 +1,28 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {VFile} from 'vfile'
+ * @import {Metadata} from '../component/article/list.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/article/list')
-var sort = require('../component/article/helper-sort')
-var page = require('./page')
+import {h} from 'hastscript'
+import {helperSort} from '../component/article/helper-sort.js'
+import {list} from '../component/article/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = articles
+/**
+ * @param {Metadata} section
+ * @param {ReadonlyArray} articles
+ * @returns {Root}
+ */
+export function articles(section, articles) {
+ const {description, pathname, title} = section
-function articles(section, articles) {
- var {title, pathname, description} = section
-
- return page(h('.row-l.column-l', h('h2', breadcrumbs(pathname, title))), [
- h('.article.content', [h('h3', title), h('p', description)]),
- list(section, sort(articles))
- ])
+ return page(
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs(pathname, title))),
+ [
+ h('.article.content', [h('h3', {}, title), h('p', {}, description)]),
+ list(section.pathname, helperSort(articles))
+ ]
+ )
}
diff --git a/generate/page/cases.js b/generate/page/cases.js
index 82de06ffba41..0f77bacb822a 100644
--- a/generate/page/cases.js
+++ b/generate/page/cases.js
@@ -1,16 +1,21 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Metadata} from '../component/case/item.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/case/list')
-var byline = require('../component/case/byline')
-var page = require('./page')
+import {h} from 'hastscript'
+import {byline} from '../component/case/byline.js'
+import {list} from '../component/case/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = showcase
-
-function showcase(showcase) {
- return page(h('.row-l.column-l', h('h2', breadcrumbs('/community/case/'))), [
- h('.article.content', [h('h3', 'Showcase'), byline()]),
- list(showcase)
- ])
+/**
+ * @param {ReadonlyArray} showcase
+ * @returns {Root}
+ */
+export function cases(showcase) {
+ return page(
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/community/case/'))),
+ [h('.article.content', [h('h3', 'Showcase'), byline()]), list(showcase)]
+ )
}
diff --git a/generate/page/community.js b/generate/page/community.js
index feb94f29e6a1..2018319cd76e 100644
--- a/generate/page/community.js
+++ b/generate/page/community.js
@@ -1,92 +1,101 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {CommunityData} from '../index.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var members = require('../component/member/list')
-var membersByline = require('../component/member/byline')
-var sortMembers = require('../component/member/helper-sort')
-var sponsors = require('../component/sponsor/list')
-var sponsorsByline = require('../component/sponsor/byline')
-var cases = require('../component/case/list')
-var casesByline = require('../component/case/byline')
-var page = require('./page')
+import {h} from 'hastscript'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {byline as casesByline} from '../component/case/byline.js'
+import {list as cases} from '../component/case/list.js'
+import {byline as membersByline} from '../component/member/byline.js'
+import {helperSort as sortMembers} from '../component/member/helper-sort.js'
+import {list as members} from '../component/member/list.js'
+import {byline as sponsorsByline} from '../component/sponsor/byline.js'
+import {list as sponsors} from '../component/sponsor/list.js'
+import {page} from './page.js'
-module.exports = team
+const org = 'https://github.com/unifiedjs'
+const base = org + '/.github/blob/HEAD/'
+const coc = base + 'code-of-conduct.md'
+const support = base + 'support.md'
+const contributing = base + 'contributing.md'
+const security = base + 'security.md'
-var spectrum = 'http://spectrum.chat/unified'
-var twitter = 'https://twitter.com/unifiedjs'
-var org = 'https://github.com/unifiedjs'
-var base = org + '/.github/blob/master/'
-var coc = base + 'code-of-conduct.md'
-var support = base + 'support.md'
-var contributing = base + 'contributing.md'
-var security = base + 'security.md'
-
-function team(data) {
- return page(h('.row-l.column-l', h('h2', breadcrumbs('/community/'))), [
- h('.article.content', [h('h3', 'Showcase'), casesByline()]),
- cases(data.users, {max: 3}),
- h('.article.content', [h('h3', 'Team'), membersByline()]),
- members(data, sortMembers(data, data.humans), {max: 6}),
- h('.article.content', [h('h3', 'Sponsors'), sponsorsByline()]),
- sponsors(data.sponsors, {max: 6}),
- h('.article.content', [
- h('h3', 'Support'),
- h('p', [
- 'There are a couple of places to get support when using unified that ',
- 'are described in this section. ',
- 'Before participating in unified’s communities, please read our ',
- h('a', {href: coc}, 'Code of Conduct'),
- '. ',
- 'We expect that all community members adhere to the guidelines within.'
- ]),
- h('p', [
- 'Questions can be asked on ',
- h('a', {href: spectrum}, 'Spectrum'),
- '. ',
- 'Take a look at ',
- h('a', {href: support}, h('code', 'support.md')),
- ' to find out how to help us help you. ',
- 'It’s important to spend enough time making your question clear yet ',
- 'detailed, because generally speaking, if more time is spent on a ',
- 'quality question, less time is needed to give a good answer.'
- ]),
- h('p', [
- 'Issues can be raised on GitHub in one of the repositories under our ',
- 'organizations. ',
- 'See ',
- h('a', {href: contributing}, h('code', 'contributing.md')),
- ' to find out how to report problems. ',
- 'This document also explains how improve documentation and ',
- 'contribute to unified. '
- ]),
- h('p', [
- 'Security issues can be reported by email and are handled through ',
- 'security advisories on GitHub. ',
- 'See ',
- h('a', {href: security}, h('code', 'security.md')),
- ' to find out how to submit a report. '
+/**
+ * @param {CommunityData} data
+ * @returns {Root}
+ */
+export function community(data) {
+ return page(
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/community/'))),
+ [
+ h('.article.content', [
+ h('p', [
+ 'unified is an open source effort by people volunteering their time. ',
+ 'A team of maintainers among the broader community. '
+ ]),
+ h('h3', 'Sponsors'),
+ sponsorsByline()
]),
- h('h3', 'Learn'),
- h('p', [
- 'To learn about unified, browse our ',
- h('a', {href: '/learn/'}, 'Learn'),
- ' section. ',
- 'This section includes several articles ranging from recipes that ',
- 'complete small, specific tasks, to guides that walk through how ',
- 'to complete bigger tasks. ',
- 'Additionally, the readmes of our projects (available through the ',
- h('a', {href: '/explore/'}, 'Explore'),
- ' section, or on GitHub and npm), describe each project in detail.'
+ sponsors(data.sponsors, {max: 6}),
+ h('.article.content', [h('h3', 'Team'), membersByline()]),
+ members(data, sortMembers(data, data.humans), {max: 6}),
+ h('.article.content', [
+ h('h3', 'Support'),
+ h('p', [
+ 'There are a couple of places to get support when using unified that ',
+ 'are described in this section. ',
+ 'Before participating in unified’s communities, please read our ',
+ h('a', {href: coc}, 'code of conduct'),
+ '. ',
+ 'We expect that all community members adhere to the guidelines within.'
+ ]),
+ h('p', [
+ 'Questions can be asked on GitHub Discussions in each org. ',
+ 'Take a look at ',
+ h('a', {href: support}, h('code', 'support.md')),
+ ' to find out how to help us help you. ',
+ 'It’s important to spend enough time making your question clear yet ',
+ 'detailed, because generally speaking, if more time is spent on a ',
+ 'quality question, less time is needed to give a good answer.'
+ ]),
+ h('p', [
+ 'Issues can be raised on GitHub in one of the repositories under our ',
+ 'organizations. ',
+ 'See ',
+ h('a', {href: contributing}, h('code', 'contributing.md')),
+ ' to find out how to report problems. ',
+ 'This document also explains how improve documentation and ',
+ 'contribute to unified. '
+ ]),
+ h('p', [
+ 'Security issues can be reported by email and are handled through ',
+ 'security advisories on GitHub. ',
+ 'See ',
+ h('a', {href: security}, h('code', 'security.md')),
+ ' to find out how to submit a report. '
+ ]),
+ h('h3', 'Learn'),
+ h('p', [
+ 'To learn about unified, browse our ',
+ h('a', {href: '/learn/'}, 'Learn'),
+ ' section. ',
+ 'This section includes several articles ranging from recipes that ',
+ 'complete small, specific tasks, to guides that walk through how ',
+ 'to complete bigger tasks. ',
+ 'Additionally, the readmes of our projects (available through the ',
+ h('a', {href: '/explore/'}, 'Explore'),
+ ' section, or on GitHub and npm), describe each project in detail.'
+ ]),
+ h('h3', 'News'),
+ h('p', [
+ 'See the ',
+ h('a', {href: '/explore/release/'}, 'Releases'),
+ ' section for recent releases.'
+ ])
]),
- h('h3', 'News'),
- h('p', [
- 'Follow the ',
- h('a', {href: twitter}, h('b', '@unifiedjs')),
- ' account on Twitter for news. ',
- 'You can also tweet at this account with questions or suggestions, ',
- 'or mention it when you made something with unified! '
- ])
- ])
- ])
+ h('.article.content', [h('h3', 'Showcase'), casesByline()]),
+ cases(data.users, {max: 3})
+ ]
+ )
}
diff --git a/generate/page/explore.js b/generate/page/explore.js
index 5386a1318953..db1d06136558 100644
--- a/generate/page/explore.js
+++ b/generate/page/explore.js
@@ -1,24 +1,34 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var pkg = require('../component/package/search-preview')
-var keyword = require('../component/keyword/search-preview')
-var project = require('../component/project/search-preview')
-var topic = require('../component/topic/search-preview')
-var page = require('./page')
+import {h} from 'hastscript'
+import {searchPreview as keywordPreview} from '../component/keyword/search-preview.js'
+import {searchPreview as packagePreview} from '../component/package/search-preview.js'
+import {searchPreview as projectPreview} from '../component/project/search-preview.js'
+import {explorePreview as release} from '../component/release/explore-preview.js'
+import {searchPreview as topicPreview} from '../component/topic/search-preview.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = explore
-
-function explore(data) {
- return page(h('.row-l.column-l', h('h2', breadcrumbs('/explore/'))), [
+/**
+ * @param {Data} data
+ * @returns {Root}
+ */
+export function explore(data) {
+ return page(h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/'))), [
h('#search-root', [
- h('.content', h('h3', 'Packages')),
- h('#root-keyword', keyword(data)),
- h('#root-package', pkg(data)),
- h('.content', h('h3', 'Projects')),
- h('#root-topic', topic(data)),
- h('#root-project', project(data))
+ h('.content', {}, h('h3', 'Packages')),
+ h('#root-keyword', {}, keywordPreview()),
+ h('#root-package', {}, packagePreview(data)),
+ h('.content', {}, h('h3', 'Projects')),
+ h('#root-topic', {}, topicPreview()),
+ h('#root-project', {}, projectPreview(data)),
+ h('#root-release', [
+ h('.content', {}, h('h3', 'Recent releases')),
+ release(data)
+ ])
])
])
}
diff --git a/generate/page/home.js b/generate/page/home.js
index 999b2cdd7e95..c90b485f561c 100644
--- a/generate/page/home.js
+++ b/generate/page/home.js
@@ -1,26 +1,67 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {VFile} from 'vfile'
+ * @import {Data} from '../data.js'
+ * @import {CommunityData} from '../index.js'
+ */
-var h = require('hastscript')
-var block = require('../atom/macro/block')
-var articlesList = require('../component/article/list')
-var articlesSort = require('../component/article/helper-sort')
-var sortPkg = require('../component/package/helper-sort')
-var listPkg = require('../component/package/list')
-var sponsors = require('../component/sponsor/list')
-var cases = require('../component/case/list')
-var compact = require('../util/fmt-compact')
-var pick = require('../util/pick-random')
-var page = require('./page')
+import {h} from 'hastscript'
+import {block} from '../atom/macro/block.js'
+import {helperSort as articlesSort} from '../component/article/helper-sort.js'
+import {list as articlesList} from '../component/article/list.js'
+import {list as cases} from '../component/case/list.js'
+import {helperSort as sortPackage} from '../component/package/helper-sort.js'
+import {list as listPackage} from '../component/package/list.js'
+import {explorePreview as release} from '../component/release/explore-preview.js'
+import {helperFilter as releaseFilter} from '../component/release/helper-filter.js'
+import {list as sponsors} from '../component/sponsor/list.js'
+import {meta} from '../../data/meta.js'
+import {releases as dataReleases} from '../../data/releases.js'
+import {constantCollective} from '../util/constant-collective.js'
+import {fmtCompact} from '../util/fmt-compact.js'
+import {fmtPercent} from '../util/fmt-percent.js'
+import {fmtPlural} from '../util/fmt-plural.js'
+import {pickRandom} from '../util/pick-random.js'
+import {page} from './page.js'
-module.exports = home
+const linux = 3_166_218 // Checked from the `diskUsage` result for `torvalds/linux`
+// on GHs GraphQL API.
+const mobyDick = 1.2 * 1024 * 1024
+// Apparently Gutenberg’s version is 1.2mb.
-function home(data) {
- var {packageByName, projectByRepo} = data
- var names = sortPkg(data, Object.keys(packageByName))
- var repos = Object.keys(projectByRepo)
- var downloads = names.map(d => packageByName[d].downloads).reduce(sum, 0)
- var stars = repos.map(d => projectByRepo[d].stars).reduce(sum, 0)
- var d = pick(names.slice(0, 75), 5)
+/**
+ * @param {CommunityData & Data & {articles: ReadonlyArray}} data
+ * @returns {Root}
+ */
+export function home(data) {
+ const {packageByName, projectByRepo} = data
+ const names = sortPackage(data, Object.keys(packageByName))
+ const repos = Object.keys(projectByRepo)
+ const d = pickRandom(names.slice(0, 75), 5)
+ const closed = meta.issueClosed + meta.prClosed
+ const open = meta.issueOpen + meta.prOpen
+ const applicableReleases = releaseFilter(
+ data,
+ dataReleases,
+ 30 * 24 * 60 * 60 * 1000
+ )
+ let downloads = 0
+ let stars = 0
+ let releases = 0
+
+ for (const d of names) {
+ downloads += packageByName[d].downloads || 0
+ }
+
+ for (const d of repos) {
+ stars += projectByRepo[d].stars || 0
+ }
+
+ for (const d of applicableReleases) {
+ if (constantCollective.includes(d.repo.split('/')[0])) {
+ releases++
+ }
+ }
return page(
[
@@ -44,18 +85,6 @@ function home(data) {
])
],
[
- h('.article.content', [
- h('h2', 'Build'),
- h('p', [
- h('b', 'We provide the building blocks'),
- ': from tiny, focussed, modular utilities to plugins that combine ',
- 'them to perform bigger tasks. ',
- 'And much, much more. ',
- 'You can build on unified, mixing and matching building blocks ',
- 'together, to make all kinds of interesting new things. '
- ])
- ]),
- cases(data.users, {max: 6}),
h('.article.content', [
h('h2', 'Learn'),
h('p', [
@@ -66,40 +95,78 @@ function home(data) {
'make things with unified. '
])
]),
- articlesList({pathname: '/learn/'}, articlesSort(data.articles), {
- max: 6
- }),
+ articlesList('/learn/', articlesSort(data.articles), {max: 6}),
+ h('.article.content', [
+ h('h2', 'Sponsor'),
+ h('p', [
+ 'To support our efforts financially, sponsor us on ',
+ h('a', {href: 'https://github.com/sponsors/unifiedjs'}, 'GitHub'),
+ ', ',
+ h('a', {href: 'https://thanks.dev'}, h('code', 'thanks.dev')),
+ ', or ',
+ h('a', {href: 'http://opencollective.com/unified'}, 'OpenCollective'),
+ '. ',
+ 'This lets us spend more time maintaining our projects and developing new ones. '
+ ])
+ ]),
+ sponsors(data.sponsors, {max: 6}),
h('.article.content', [
h('h2', 'Explore'),
h('p', [
'The ever growing ecosystem that the unified collective provides ',
'today consists of ' + repos.length + ' open source projects, ',
'with a combined ',
- h('strong', compact(stars)),
+ h('strong', fmtCompact(stars)),
' stars on GitHub. ',
+ 'In comparison, the code that the collective maintains is about ',
+ String(Math.floor(meta.size / mobyDick)),
+ ' Moby Dicks or ',
+ String(Math.floor(meta.size / linux)),
+ ' Linuxes. ',
'In the last 30 days, the ' + names.length + ' packages maintained ',
'in those projects were downloaded ',
- h('strong', compact(downloads)),
+ h('strong', fmtCompact(downloads)),
' times from npm. ',
'Much of this is maintained by our teams, yet others are provided ',
'by the community. '
])
]),
- listPkg(data, d, {trail: explore()}),
+ listPackage(data, d, {trail: explore()}),
h('.article.content', [
- h('h2', 'Sponsor'),
+ h('h2', 'Build'),
+ h('p', [
+ h('b', 'We provide the building blocks'),
+ ': from tiny, focussed, modular utilities to plugins that combine ',
+ 'them to perform bigger tasks. ',
+ 'And much, much more. ',
+ 'You can build on unified, mixing and matching building blocks ',
+ 'together, to make all kinds of interesting new things. '
+ ])
+ ]),
+ cases(data.users, {max: 6}),
+ h('.article.content', [
+ h('h2', 'Work'),
h('p', [
'Maintaining the collective, developing new projects, keeping ',
'everything fast and secure, and helping users, is a lot of work. ',
- 'Thankfully, we are backed financially by our sponsors. ',
- 'This allows us to spend more time maintaining our projects and ',
- 'developing new ones. ',
- 'To support our efforts financially, sponsor or back us on ',
- h('a', {href: 'http://opencollective.com/unified'}, 'OpenCollective'),
- '.'
+ 'In total, we’ve closed ',
+ fmtCompact(closed),
+ ' issues/PRs while ',
+ fmtCompact(open),
+ ' are currently open (',
+ fmtPercent(open / (open + closed)),
+ '). ',
+ // Note: data is sometimes missing.
+ releases
+ ? 'In the last 30 days, we’ve cut ' +
+ releases +
+ ' new ' +
+ fmtPlural(releases, {one: 'release', other: 'releases'}) +
+ '.'
+ : undefined
])
]),
- sponsors(data.sponsors, {max: 6})
+ release(data)
]
)
@@ -122,7 +189,3 @@ function home(data) {
)
}
}
-
-function sum(a, b) {
- return a + (b || 0)
-}
diff --git a/generate/page/keyword.js b/generate/page/keyword.js
index 5d7ef3344efc..aa8ed9336e24 100644
--- a/generate/page/keyword.js
+++ b/generate/page/keyword.js
@@ -1,15 +1,21 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var detail = require('../component/keyword/detail')
-var page = require('./page')
+import {h} from 'hastscript'
+import {detail} from '../component/keyword/detail.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = keywords
-
-function keywords(data, d) {
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Root}
+ */
+export function keyword(data, d) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/explore/keyword/' + d))),
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/keyword/' + d))),
detail(data, d)
)
}
diff --git a/generate/page/keywords.js b/generate/page/keywords.js
index af695cd60bb9..eadc07a15043 100644
--- a/generate/page/keywords.js
+++ b/generate/page/keywords.js
@@ -1,17 +1,29 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/keyword/list')
-var filter = require('../component/keyword/helper-filter')
-var sort = require('../component/keyword/helper-sort')
-var page = require('./page')
+import {h} from 'hastscript'
+import {helperFilter} from '../component/keyword/helper-filter.js'
+import {helperSort} from '../component/keyword/helper-sort.js'
+import {list} from '../component/keyword/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = keywords
-
-function keywords(data) {
+/**
+ * @param {Data} data
+ * @returns {Root}
+ */
+export function keywords(data) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/explore/keyword/'))),
- list(data, filter(data, sort(data, Object.keys(data.packagesByKeyword)), 2))
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/keyword/'))),
+ list(
+ data,
+ helperFilter(
+ data,
+ helperSort(data, Object.keys(data.packagesByKeyword)),
+ 2
+ )
+ )
)
}
diff --git a/generate/page/learn.js b/generate/page/learn.js
index 46b1b83ebab1..d05f7b917a29 100644
--- a/generate/page/learn.js
+++ b/generate/page/learn.js
@@ -1,17 +1,33 @@
-'use strict'
+/**
+ * @import {ElementContent, Root} from 'hast'
+ * @import {Metadata} from '../component/article/list.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/article/list')
-var sort = require('../component/article/helper-sort')
-var page = require('./page')
+import {ok as assert} from 'devlop'
+import {h} from 'hastscript'
+import {helperSort} from '../component/article/helper-sort.js'
+import {list} from '../component/article/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = learn
+/**
+ * @param {ReadonlyArray} sections
+ * @returns {Root}
+ */
+export function learn(sections) {
+ /** @type {Array} */
+ const articles = []
-function learn(sections) {
- return page(h('.row-l.column-l', h('h2', breadcrumbs('/learn/'))), [
+ for (const d of sections) {
+ assert(d.entries)
+ articles.push(
+ h('.article.content', [h('h3', {}, d.title), h('p', {}, d.description)]),
+ list(d.pathname, helperSort(d.entries || []))
+ )
+ }
+
+ return page(h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/learn/'))), [
h('.article.content', [
- h('h3', 'Intro'),
h('p', [
'unified is an interface for parsing, inspecting, transforming, and ',
'serializing content through syntax trees. ',
@@ -24,10 +40,7 @@ function learn(sections) {
'through how to complete bigger tasks.'
])
]),
- sections.flatMap(d => [
- h('.article.content', [h('h3', d.title), h('p', d.description)]),
- list(d, sort(d.entries))
- ]),
+ ...articles,
h('.article.content', [
h('h3', 'Explore'),
h('p', [
diff --git a/generate/page/members.js b/generate/page/members.js
index c595dce95ed0..b809dcfdbe59 100644
--- a/generate/page/members.js
+++ b/generate/page/members.js
@@ -1,20 +1,25 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {CommunityData} from '../index.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var byline = require('../component/member/byline')
-var list = require('../component/member/list')
-var sort = require('../component/member/helper-sort')
-var page = require('./page')
+import {h} from 'hastscript'
+import {byline} from '../component/member/byline.js'
+import {helperSort} from '../component/member/helper-sort.js'
+import {list} from '../component/member/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = members
-
-function members(data) {
+/**
+ * @param {CommunityData} data
+ * @returns {Root}
+ */
+export function members(data) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/community/member/'))),
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/community/member/'))),
[
h('.article.content', [h('h3', 'Team'), byline()]),
- list(data, sort(data, data.humans))
+ list(data, helperSort(data, data.humans))
]
)
}
diff --git a/generate/page/owner.js b/generate/page/owner.js
index a125069b4e5b..0d14d544c68b 100644
--- a/generate/page/owner.js
+++ b/generate/page/owner.js
@@ -1,15 +1,21 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var detail = require('../component/owner/detail')
-var page = require('./page')
+import {h} from 'hastscript'
+import {detail} from '../component/owner/detail.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = owner
-
-function owner(data, d) {
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Root}
+ */
+export function owner(data, d) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/explore/project/' + d))),
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/project/' + d))),
detail(data, d)
)
}
diff --git a/generate/page/package.js b/generate/page/package.js
index 7da4de98b21a..d6eccba2809c 100644
--- a/generate/page/package.js
+++ b/generate/page/package.js
@@ -1,11 +1,18 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var head = require('../component/package/head')
-var detail = require('../component/package/detail')
-var page = require('./page')
+import {detail} from '../component/package/detail.js'
+import {head} from '../component/package/head.js'
+import {page} from './page.js'
-module.exports = pkg
-
-function pkg(data, d, tree) {
- return page(head(data, d), detail(data, d, tree))
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @param {Root} tree
+ * @returns {Root}
+ */
+export function renderPackage(data, d, tree) {
+ return page(head(data, d), [detail(data, d, tree)])
}
diff --git a/generate/page/packages.js b/generate/page/packages.js
index 29f9079858e3..34a44328d61e 100644
--- a/generate/page/packages.js
+++ b/generate/page/packages.js
@@ -1,16 +1,24 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/package/list')
-var sort = require('../component/package/helper-sort')
-var page = require('./page')
+import {h} from 'hastscript'
+import {helperSort} from '../component/package/helper-sort.js'
+import {list} from '../component/package/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = packages
-
-function packages(data) {
- return page(h('.row-l.column-l', h('h2', breadcrumbs('/explore/package'))), [
- h('.content', h('h3', 'All packages')),
- list(data, sort(data, Object.keys(data.packageByName)))
- ])
+/**
+ * @param {Data} data
+ * @returns {Root}
+ */
+export function packages(data) {
+ return page(
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/package'))),
+ [
+ h('.content', {}, h('h3', 'All packages')),
+ list(data, helperSort(data, Object.keys(data.packageByName)))
+ ]
+ )
}
diff --git a/generate/page/page.js b/generate/page/page.js
index 75634ca24626..ab86e31554ca 100644
--- a/generate/page/page.js
+++ b/generate/page/page.js
@@ -1,15 +1,21 @@
-'use strict'
+/**
+ * @import {ElementContent, Root} from 'hast'
+ */
-var h = require('hastscript')
+import {h} from 'hastscript'
-module.exports = page
-
-function page(heading, main) {
- return {
- type: 'root',
- children: [
- heading && heading.length !== 0 ? h('section.container', heading) : '',
- main && main.length !== 0 ? h('main.container', main) : ''
- ]
- }
+/**
+ * @param {Array | ElementContent | undefined} heading
+ * @param {Array | ElementContent | undefined} [main]
+ * @returns {Root}
+ */
+export function page(heading, main) {
+ return h(undefined, [
+ heading && (!Array.isArray(heading) || heading.length > 0)
+ ? h('section.container', {}, heading)
+ : undefined,
+ main && (!Array.isArray(main) || main.length > 0)
+ ? h('main.container', {}, main)
+ : undefined
+ ])
}
diff --git a/generate/page/project.js b/generate/page/project.js
index 788329b8fb9f..476e4c401a0a 100644
--- a/generate/page/project.js
+++ b/generate/page/project.js
@@ -1,11 +1,17 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var head = require('../component/project/head')
-var detail = require('../component/project/detail')
-var page = require('./page')
+import {detail} from '../component/project/detail.js'
+import {head} from '../component/project/head.js'
+import {page} from './page.js'
-module.exports = project
-
-function project(data, d) {
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Root}
+ */
+export function project(data, d) {
return page(head(data, d), detail(data, d))
}
diff --git a/generate/page/projects.js b/generate/page/projects.js
index 5b2af33cc487..a29e32bf510a 100644
--- a/generate/page/projects.js
+++ b/generate/page/projects.js
@@ -1,16 +1,24 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/project/list')
-var sort = require('../component/project/helper-sort')
-var page = require('./page')
+import {h} from 'hastscript'
+import {helperSort} from '../component/project/helper-sort.js'
+import {list} from '../component/project/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = projects
-
-function projects(data) {
- return page(h('.row-l.column-l', h('h2', breadcrumbs('/explore/project/'))), [
- h('.content', h('h3', 'All projects')),
- list(data, sort(data, Object.keys(data.projectByRepo)))
- ])
+/**
+ * @param {Data} data
+ * @returns {Root}
+ */
+export function projects(data) {
+ return page(
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/project/'))),
+ [
+ h('.content', {}, h('h3', 'All projects')),
+ list(data, helperSort(data, Object.keys(data.projectByRepo)))
+ ]
+ )
}
diff --git a/generate/page/releases.js b/generate/page/releases.js
new file mode 100644
index 000000000000..20953847e542
--- /dev/null
+++ b/generate/page/releases.js
@@ -0,0 +1,23 @@
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
+
+import {h} from 'hastscript'
+import {releases as dataReleases} from '../../data/releases.js'
+import {helperFilter} from '../component/release/helper-filter.js'
+import {helperSort} from '../component/release/helper-sort.js'
+import {list} from '../component/release/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
+
+/**
+ * @param {Data} data
+ * @returns {Root}
+ */
+export function releases(data) {
+ return page(
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/release/'))),
+ list(data, helperFilter(data, helperSort(data, dataReleases)))
+ )
+}
diff --git a/generate/page/scope.js b/generate/page/scope.js
index 6cc27534f59f..6cf4be59bd38 100644
--- a/generate/page/scope.js
+++ b/generate/page/scope.js
@@ -1,15 +1,21 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var detail = require('../component/scope/detail')
-var page = require('./page')
+import {h} from 'hastscript'
+import {detail} from '../component/scope/detail.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = scope
-
-function scope(data, d) {
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Root}
+ */
+export function scope(data, d) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/explore/package/' + d))),
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/package/' + d))),
detail(data, d)
)
}
diff --git a/generate/page/sponsors.js b/generate/page/sponsors.js
index dfcd90a1ca05..297f196abf9f 100644
--- a/generate/page/sponsors.js
+++ b/generate/page/sponsors.js
@@ -1,16 +1,26 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {SponsorRaw as GhSponsor} from '../../crawl/github-sponsors.js'
+ * @import {Sponsor as OcSponsor} from '../../crawl/opencollective.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/sponsor/list')
-var byline = require('../component/sponsor/byline')
-var page = require('./page')
+import {h} from 'hastscript'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {byline} from '../component/sponsor/byline.js'
+import {helperSort} from '../component/sponsor/helper-sort.js'
+import {list} from '../component/sponsor/list.js'
+import {page} from './page.js'
-module.exports = sponsor
-
-function sponsor(sponsors) {
+/**
+ * @param {ReadonlyArray} sponsors
+ * @returns {Root}
+ */
+export function sponsor(sponsors) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/community/sponsor/'))),
- [h('.article.content', [h('h3', 'Sponsors'), byline()]), list(sponsors)]
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/community/sponsor/'))),
+ [
+ h('.article.content', [h('h3', 'Sponsors'), byline()]),
+ list(helperSort(sponsors))
+ ]
)
}
diff --git a/generate/page/topic.js b/generate/page/topic.js
index 486077bb0d9d..11af41507ce2 100644
--- a/generate/page/topic.js
+++ b/generate/page/topic.js
@@ -1,15 +1,21 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var detail = require('../component/topic/detail')
-var page = require('./page')
+import {h} from 'hastscript'
+import {detail} from '../component/topic/detail.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = keywords
-
-function keywords(data, d) {
+/**
+ * @param {Data} data
+ * @param {string} d
+ * @returns {Root}
+ */
+export function topic(data, d) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/explore/topic/' + d))),
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/topic/' + d))),
detail(data, d)
)
}
diff --git a/generate/page/topics.js b/generate/page/topics.js
index 6d289f6fe4e7..5583982a2ecd 100644
--- a/generate/page/topics.js
+++ b/generate/page/topics.js
@@ -1,17 +1,25 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {Data} from '../data.js'
+ */
-var h = require('hastscript')
-var breadcrumbs = require('../molecule/breadcrumbs')
-var list = require('../component/topic/list')
-var filter = require('../component/topic/helper-filter')
-var sort = require('../component/topic/helper-sort')
-var page = require('./page')
+import {h} from 'hastscript'
+import {helperFilter} from '../component/topic/helper-filter.js'
+import {helperSort} from '../component/topic/helper-sort.js'
+import {list} from '../component/topic/list.js'
+import {breadcrumbs} from '../molecule/breadcrumbs.js'
+import {page} from './page.js'
-module.exports = keywords
-
-function keywords(data) {
+/**
+ * @param {Data} data
+ * @returns {Root}
+ */
+export function topics(data) {
return page(
- h('.row-l.column-l', h('h2', breadcrumbs('/explore/topic/'))),
- list(data, filter(data, sort(data, Object.keys(data.projectsByTopic)), 2))
+ h('.row-l.column-l', {}, h('h2', {}, breadcrumbs('/explore/topic/'))),
+ list(
+ data,
+ helperFilter(data, helperSort(data, Object.keys(data.projectsByTopic)), 2)
+ )
)
}
diff --git a/generate/pipeline/article.js b/generate/pipeline/article.js
index 28518540c45e..535e952e0de0 100644
--- a/generate/pipeline/article.js
+++ b/generate/pipeline/article.js
@@ -1,43 +1,142 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {PackageJson} from 'type-fest'
+ */
-var unified = require('unified')
-var markdown = require('remark-parse')
-var frontmatter = require('remark-frontmatter')
-var remark2rehype = require('remark-rehype')
-var raw = require('rehype-raw')
-var slug = require('rehype-slug')
-var autolink = require('rehype-autolink-headings')
-var highlight = require('rehype-highlight')
-var pkg = require('../../package.json')
-var rehypeLink = require('../plugin/rehype-link')
-var link = require('../atom/icon/link')
-var rewriteUrls = require('../plugin/rehype-rewrite-urls')
-var abbr = require('../plugin/rehype-abbreviate')
+import assert from 'node:assert/strict'
+import fs from 'node:fs/promises'
+import {fileURLToPath} from 'node:url'
+import etc from '@wooorm/starry-night/etc'
+import sourceGitignore from '@wooorm/starry-night/source.gitignore'
+import sourceRegexpExtended from '@wooorm/starry-night/source.regexp.extended'
+import sourceRegexpPosix from '@wooorm/starry-night/source.regexp.posix'
+import sourceRegexp from '@wooorm/starry-night/source.regexp'
+import sourceSy from '@wooorm/starry-night/source.sy'
+import sourceTsx from '@wooorm/starry-night/source.tsx'
+import {common} from '@wooorm/starry-night'
+import remarkFrontmatter from 'remark-frontmatter'
+import remarkGfm from 'remark-gfm'
+import remarkParse from 'remark-parse'
+import remarkRehype from 'remark-rehype'
+import rehypeAutolinkHeadings from 'rehype-autolink-headings'
+import rehypeInferReadingTimeMeta from 'rehype-infer-reading-time-meta'
+import rehypeRaw from 'rehype-raw'
+import rehypeSlug from 'rehype-slug'
+import rehypeStarryNight from 'rehype-starry-night'
+import rehypeTwoslash from 'rehype-twoslash'
+import {unified} from 'unified'
+import {visit} from 'unist-util-visit'
+import typescript from 'typescript'
+import {link} from '../atom/icon/link.js'
+import rehypeAbbreviate from '../plugin/rehype-abbreviate.js'
+import rehypeLink from '../plugin/rehype-link.js'
+import rehypeRewriteUrls from '../plugin/rehype-rewrite-urls.js'
-var origin = pkg.homepage
+const packageValue = await fs.readFile('package.json', 'utf8')
+/** @type {PackageJson} */
+const packageJson = JSON.parse(packageValue)
+const origin = packageJson.homepage
+assert(typeof origin === 'string')
-module.exports = unified()
- .use(markdown)
- .use(frontmatter)
- .use(remark2rehype, {allowDangerousHTML: true})
- .use(raw)
- .use(highlight, {subset: false, ignoreMissing: true})
- .use(slug)
- .use(autolink, {
+const configPath = typescript.findConfigFile(
+ fileURLToPath(import.meta.url),
+ typescript.sys.fileExists,
+ 'tsconfig.json'
+)
+assert(configPath)
+const commandLine = typescript.getParsedCommandLineOfConfigFile(
+ configPath,
+ undefined,
+ {
+ fileExists: typescript.sys.fileExists,
+ getCurrentDirectory: typescript.sys.getCurrentDirectory,
+ onUnRecoverableConfigFileDiagnostic(x) {
+ console.warn('Unrecoverable diagnostic', x)
+ },
+ readDirectory: typescript.sys.readDirectory,
+ readFile: typescript.sys.readFile,
+ useCaseSensitiveFileNames: typescript.sys.useCaseSensitiveFileNames
+ }
+)
+assert(commandLine)
+
+export const article = unified()
+ .use(remarkParse)
+ .use(remarkGfm)
+ .use(remarkFrontmatter)
+ .use(remarkRehype, {allowDangerousHtml: true})
+ .use(function () {
+ /**
+ * @param {Root} tree
+ * @returns {undefined}
+ */
+ return function (tree) {
+ visit(tree, 'element', function (node) {
+ if (node.tagName === 'code' && node.data?.meta === 'twoslash') {
+ const className = Array.isArray(node.properties.className)
+ ? node.properties.className
+ : (node.properties.className = [])
+ className.push('twoslash')
+ }
+ })
+ }
+ })
+ .use(rehypeRaw)
+ .use(rehypeInferReadingTimeMeta)
+ .use(rehypeStarryNight, {
+ grammars: [
+ ...common,
+ etc,
+ sourceGitignore,
+ sourceRegexpExtended,
+ sourceRegexpPosix,
+ sourceRegexp,
+ sourceSy,
+ sourceTsx
+ ],
+ plainText: ['txt']
+ })
+ .use(rehypeTwoslash, {twoslash: {compilerOptions: commandLine.options}})
+ .use(rehypeSlug)
+ .use(rehypeAutolinkHeadings, {
behavior: 'prepend',
- properties: {ariaLabel: 'Link to self', className: ['anchor']},
- content: link()
+ content: link(),
+ properties: {ariaLabel: 'Link to self', className: ['anchor']}
})
- .use(abbr, {
- AST: 'Abstract syntax tree',
- CLI: 'Command-line interface',
- DOM: 'Document object model',
- HSL: 'Hue, saturation, lightness',
- HTML: 'Hypertext markup language',
- JSX: null,
- MDX: null,
- XML: 'Extensible Markup Language'
+ .use(rehypeAbbreviate, {
+ ignore: [
+ 'ATX',
+ 'ECMAScript',
+ 'ESLint',
+ 'ID',
+ 'ISC',
+ 'JSDoc',
+ 'JSX',
+ 'MDX',
+ 'MIT'
+ ],
+ titles: {
+ API: 'Application programming interface',
+ ARIA: 'Accessible rich internet applications',
+ AST: 'Abstract syntax tree',
+ CDN: 'Content delivery network',
+ CDATUM: 'Character data',
+ CI: 'Continuous integration',
+ CLI: 'Command-line interface',
+ CSS: 'Cascading style sheets',
+ DOM: 'Document object model',
+ DSL: 'Domain-specific language',
+ GFM: 'GitHub flavored markdown',
+ HSL: 'Hue, saturation, lightness',
+ HTML: 'Hypertext markup language',
+ JSON: 'JavaScript object notation',
+ MDN: 'Mozilla developer network',
+ PR: 'Pull request',
+ URL: 'Uniform resource locator',
+ XML: 'Extensible markup language',
+ XSS: 'Cross site scripting'
+ }
})
.use(rehypeLink)
- .use(rewriteUrls, {origin})
+ .use(rehypeRewriteUrls, {origin})
.freeze()
diff --git a/generate/pipeline/description-schema.js b/generate/pipeline/description-schema.js
index 22f2cb239320..0fbe8f3e4072 100644
--- a/generate/pipeline/description-schema.js
+++ b/generate/pipeline/description-schema.js
@@ -1,10 +1,12 @@
-module.exports = {
- strip: ['script'],
+/**
+ * @import {Schema} from 'hast-util-sanitize'
+ */
+
+/** @type {Schema} */
+export const descriptionSchema = {
ancestors: {},
- protocols: {href: ['http', 'https']},
- tagNames: ['code', 'strong', 'b', 'em', 'i', 'strike', 's', 'del', 'a'],
- attributes: {
- a: ['href'],
- '*': ['title']
- }
+ attributes: {'*': ['title'], a: ['href']},
+ protocols: {href: ['https', 'http']},
+ strip: ['script'],
+ tagNames: ['a', 'b', 'code', 'del', 'em', 'i', 'strike', 'strong', 's']
}
diff --git a/generate/pipeline/description.js b/generate/pipeline/description.js
index 18f8b213e54c..4e770b031ee3 100644
--- a/generate/pipeline/description.js
+++ b/generate/pipeline/description.js
@@ -1,19 +1,15 @@
-'use strict'
+import rehypeRaw from 'rehype-raw'
+import rehypeSanitize from 'rehype-sanitize'
+import remarkGemoji from 'remark-gemoji'
+import remarkParse from 'remark-parse'
+import remarkRehype from 'remark-rehype'
+import {unified} from 'unified'
+import {descriptionSchema} from './description-schema.js'
-var unified = require('unified')
-var markdown = require('remark-parse')
-var gemoji = require('remark-gemoji')
-var gemoji2emoji = require('remark-gemoji-to-emoji')
-var remark2rehype = require('remark-rehype')
-var raw = require('rehype-raw')
-var sanitize = require('rehype-sanitize')
-var schema = require('./description-schema')
-
-module.exports = unified()
- .use(markdown)
- .use(gemoji)
- .use(gemoji2emoji)
- .use(remark2rehype, {allowDangerousHTML: true})
- .use(raw)
- .use(sanitize, schema)
+export const description = unified()
+ .use(remarkParse)
+ .use(remarkGemoji)
+ .use(remarkRehype, {allowDangerousHtml: true})
+ .use(rehypeRaw)
+ .use(rehypeSanitize, descriptionSchema)
.freeze()
diff --git a/generate/pipeline/main.js b/generate/pipeline/main.js
index 3f9e31fd45e7..0945250d21a1 100644
--- a/generate/pipeline/main.js
+++ b/generate/pipeline/main.js
@@ -1,97 +1,119 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {VFile} from 'vfile'
+ */
-var path = require('path')
-var mkdirp = require('vfile-mkdirp')
-var unified = require('unified')
-var minify = require('rehype-preset-minify')
-var doc = require('rehype-document')
-var meta = require('rehype-meta')
-var stringify = require('rehype-stringify')
-var wrap = require('../plugin/rehype-wrap')
-var defer = require('../plugin/rehype-defer')
-var pictures = require('../plugin/rehype-pictures')
+import assert from 'node:assert/strict'
+import fs from 'node:fs/promises'
+import rehypeDocument from 'rehype-document'
+import rehypeMeta from 'rehype-meta'
+import rehypeMinifyUrl from 'rehype-minify-url'
+import rehypePresetMinify from 'rehype-preset-minify'
+import rehypePreventFaviconRequest from 'rehype-prevent-favicon-request'
+import rehypeStringify from 'rehype-stringify'
+import {unified} from 'unified'
+import rehypeDefer from '../plugin/rehype-defer.js'
+import rehypePictures from '../plugin/rehype-pictures.js'
+import rehypeWrap from '../plugin/rehype-wrap.js'
// Pipeline that everything goes through.
-module.exports = unified()
- .use(wrap)
- .use(pictures, {base: path.join('build')})
- .use(doc, {
- title: 'unified',
- js: ['/search.js'],
+export const main = unified()
+ .use(rehypeWrap)
+ .use(rehypePictures, {
+ base: new URL('../../build/', import.meta.url),
+ from: 'https://unifiedjs.com'
+ })
+ .use(rehypeDocument, {
+ js: ['/browser.js', '/search.js'],
link: [
- {rel: 'preconnect', href: 'https://github.com'},
- {rel: 'preconnect', href: 'https://images.opencollective.com'},
- {rel: 'preconnect', href: 'https://twitter-avatar.now.sh'},
- {rel: 'preconnect', href: 'https://www.npmjs.com'},
- {rel: 'preconnect', href: 'https://bundlephobia.com'},
- {rel: 'preconnect', href: 'https://www.npmtrends.com'},
- {rel: 'stylesheet', href: '/syntax-light.css'},
- {rel: 'stylesheet', href: '/index.css'},
{
- rel: 'stylesheet',
- href: '/big.css',
- media: '(min-width:36em)'
+ href: '/rss.xml',
+ rel: 'alternate',
+ title: 'unifiedjs.com',
+ type: 'application/rss+xml'
},
+ // We take images from these two, so preconnect asap.
+ {href: 'https://github.com', rel: 'preconnect'},
+ {href: 'https://images.opencollective.com', rel: 'preconnect'},
{
- rel: 'stylesheet',
- href: '/syntax-dark.css',
- media: '(prefers-color-scheme:dark)'
+ href: 'https://esm.sh/@wooorm/starry-night@3/style/both.css',
+ rel: 'stylesheet'
+ },
+ {href: '/index.css', rel: 'stylesheet'},
+ {
+ href: '/big.css',
+ media: '(min-width:36em)',
+ rel: 'stylesheet'
},
{
- rel: 'stylesheet',
href: '/dark.css',
- media: '(prefers-color-scheme:dark)'
+ media: '(prefers-color-scheme:dark)',
+ rel: 'stylesheet'
}
- ]
+ ],
+ title: 'unified'
})
- .use(meta, {
- twitter: true,
- og: true,
+ .use(rehypeMeta, {
+ color: '#0366d6',
copyright: true,
- origin: 'https://unifiedjs.com',
- pathname: '/',
- type: 'website',
- name: 'unified',
- siteTags: ['unified', 'parse', 'stringify', 'process', 'ast', 'transform'],
- siteAuthor: 'unified collective',
- siteTwitter: '@unifiedjs',
image: {
- url: 'https://unifiedjs.com/image/cover-1200.png',
- width: 1200,
+ alt: 'We compile content to syntax trees and syntax trees to content. We also provide hundreds of packages to work on the trees in between. You can build on the unified collective to make all kinds of interesting things.',
height: 690,
- alt:
- 'We compile content to syntax trees and syntax trees to content. We also provide hundreds of packages to work on the trees in between. You can build on the unified collective to make all kinds of interesting things.'
+ url: 'https://unifiedjs.com/image/cover-1200.png',
+ width: 1200
},
- color: '#0366d6'
+ name: 'unified',
+ og: true,
+ siteAuthor: 'unified collective',
+ siteTags: ['ast', 'parse', 'process', 'stringify', 'transform', 'unified'],
+ type: 'website'
})
- .use(defer)
- .use(minify)
+ .use(rehypeDefer)
+ .use(rehypePresetMinify)
+ .use(rehypePreventFaviconRequest)
+ .use(rehypeMinifyUrl)
.use(move)
.use(mkdir)
- .use(stringify)
+ .use(rehypeStringify)
.freeze()
-// Plugin that moves a file’s path to the output location
+/**
+ * Plugin that moves a file’s path to the output location.
+ */
function move() {
- return transform
- function transform(_, file) {
- var {pathname} = file.data.meta
- var parts = pathname.slice(1).split(path.posix.sep)
- var last = parts.pop()
+ /**
+ * @param {Root} _
+ * @param {VFile} file
+ * @returns {undefined}
+ */
+ return function (_, file) {
+ const meta = file.data.meta
+ assert(meta)
+ const pathname = meta.pathname
+ assert(typeof pathname === 'string')
+ const parts = pathname.slice(1).split('/')
+ const last = parts.pop()
parts.unshift('build')
parts.push(last || 'index')
- file.path = parts.join(path.sep)
+ file.path = parts.join('/')
file.extname = '.html'
file.history = [file.path]
}
}
-// Plugin to make sure the directories to a file exist.
+/**
+ * Plugin to make sure the directories to a file exist.
+ */
function mkdir() {
- return transformer
- function transformer(_, file) {
- return mkdirp(file).then(() => {})
+ /**
+ * @param {Root} _
+ * @param {VFile} file
+ * @returns {Promise}
+ */
+ return async function (_, file) {
+ assert(file.dirname)
+ await fs.mkdir(file.dirname, {recursive: true})
}
}
diff --git a/generate/pipeline/readme.js b/generate/pipeline/readme.js
index 31255275e7fd..9a75bfe66828 100644
--- a/generate/pipeline/readme.js
+++ b/generate/pipeline/readme.js
@@ -1,42 +1,49 @@
-'use strict'
+/**
+ * @import {PackageJson} from 'type-fest'
+ */
-var unified = require('unified')
-var merge = require('deepmerge')
-var markdown = require('remark-parse')
-var frontmatter = require('remark-frontmatter')
-var gemoji = require('remark-gemoji')
-var gemoji2emoji = require('remark-gemoji-to-emoji')
-var remark2rehype = require('remark-rehype')
-var raw = require('rehype-raw')
-var slug = require('rehype-slug')
-var autolink = require('rehype-autolink-headings')
-var sanitize = require('rehype-sanitize')
-var highlight = require('rehype-highlight')
-var gh = require('hast-util-sanitize/lib/github')
-var pkg = require('../../package.json')
-var link = require('../atom/icon/link')
-var resolveUrls = require('../plugin/rehype-resolve-urls')
-var rewriteUrls = require('../plugin/rehype-rewrite-urls')
+import assert from 'node:assert/strict'
+import fs from 'node:fs/promises'
+import {all} from '@wooorm/starry-night'
+import rehypeAutolinkHeadings from 'rehype-autolink-headings'
+import rehypeRaw from 'rehype-raw'
+import rehypeSanitize from 'rehype-sanitize'
+import rehypeSlug from 'rehype-slug'
+import rehypeStarryNight from 'rehype-starry-night'
+import remarkFrontmatter from 'remark-frontmatter'
+import remarkGemoji from 'remark-gemoji'
+import remarkGfm from 'remark-gfm'
+import remarkParse from 'remark-parse'
+import remarkRehype from 'remark-rehype'
+import {unified} from 'unified'
+import {link} from '../atom/icon/link.js'
+import rehypeResolveUrls from '../plugin/rehype-resolve-urls.js'
+import rehypeRewriteUrls from '../plugin/rehype-rewrite-urls.js'
-var origin = pkg.homepage
+const packageValue = await fs.readFile('package.json', 'utf8')
+/** @type {PackageJson} */
+const packageJson = JSON.parse(packageValue)
+const origin = packageJson.homepage
+assert(typeof origin === 'string')
-var schema = merge(gh, {attributes: {code: ['className']}})
-
-module.exports = unified()
- .use(markdown)
- .use(frontmatter)
- .use(gemoji)
- .use(gemoji2emoji)
- .use(remark2rehype, {allowDangerousHTML: true})
- .use(raw)
- .use(sanitize, schema)
- .use(highlight, {subset: false, ignoreMissing: true})
- .use(slug)
- .use(autolink, {
+export const readme = unified()
+ .use(remarkParse)
+ .use(remarkGfm)
+ .use(remarkFrontmatter)
+ .use(remarkGemoji)
+ .use(remarkRehype, {allowDangerousHtml: true})
+ .use(rehypeRaw)
+ .use(rehypeSanitize)
+ .use(rehypeStarryNight, {
+ grammars: all,
+ plainText: ['ascii', 'mdx-broken', 'mdx-invalid', 'text', 'txt']
+ })
+ .use(rehypeSlug)
+ .use(rehypeAutolinkHeadings, {
behavior: 'prepend',
- properties: {ariaLabel: 'Link to self', className: ['anchor']},
- content: link()
+ content: link(),
+ properties: {ariaLabel: 'Link to self', className: ['anchor']}
})
- .use(resolveUrls)
- .use(rewriteUrls, {origin})
+ .use(rehypeResolveUrls)
+ .use(rehypeRewriteUrls, {origin})
.freeze()
diff --git a/generate/pipeline/release.js b/generate/pipeline/release.js
new file mode 100644
index 000000000000..f160815d73cc
--- /dev/null
+++ b/generate/pipeline/release.js
@@ -0,0 +1,76 @@
+/**
+ * @import {Root} from 'hast'
+ * @import {PackageJson} from 'type-fest'
+ * @import {BuildVisitor} from 'unist-util-visit'
+ * @import {Release} from '../../data/releases.js'
+ */
+
+import assert from 'node:assert/strict'
+import fs from 'node:fs/promises'
+import {headingRank} from 'hast-util-heading-rank'
+import {shiftHeading} from 'hast-util-shift-heading'
+import remarkGemoji from 'remark-gemoji'
+import remarkGfm from 'remark-gfm'
+import remarkGithub from 'remark-github'
+import remarkParse from 'remark-parse'
+import remarkRehype from 'remark-rehype'
+import rehypeRaw from 'rehype-raw'
+import rehypeSanitize from 'rehype-sanitize'
+import rehypeStarryNight from 'rehype-starry-night'
+import {unified} from 'unified'
+import {visit} from 'unist-util-visit'
+import rehypeResolveUrls from '../plugin/rehype-resolve-urls.js'
+import rehypeRewriteUrls from '../plugin/rehype-rewrite-urls.js'
+
+const packageValue = await fs.readFile('package.json', 'utf8')
+/** @type {PackageJson} */
+const packageJson = JSON.parse(packageValue)
+const origin = packageJson.homepage
+assert(typeof origin === 'string')
+
+/**
+ * @param {Release} d
+ */
+export function release(d) {
+ return unified()
+ .use(remarkParse)
+ .use(remarkGfm)
+ .use(remarkGithub, {repository: d.repo})
+ .use(remarkGemoji)
+ .use(remarkRehype, {allowDangerousHtml: true})
+ .use(rehypeRaw)
+ .use(rehypeSanitize)
+ .use(rehypeStarryNight)
+ .use(rehypeResolveUrls, {object: d.tag, repo: d.repo})
+ .use(rehypeRewriteUrls, {origin})
+ .use(headings)
+ .freeze()
+
+ function headings() {
+ /**
+ * @param {Root} tree
+ * @returns {undefined}
+ */
+ return function (tree) {
+ let depth = 6
+ const goal = 4
+
+ visit(tree, 'element', pre)
+
+ const shift = goal - depth
+
+ if (shift !== 0) {
+ shiftHeading(tree, shift)
+ }
+
+ /** @type {BuildVisitor} */
+ function pre(node) {
+ const rank = headingRank(node)
+
+ if (rank && rank < depth) {
+ depth = rank
+ }
+ }
+ }
+ }
+}
diff --git a/generate/plugin/rehype-abbreviate.js b/generate/plugin/rehype-abbreviate.js
index 338d4d5da97d..7f73489dc1e7 100644
--- a/generate/plugin/rehype-abbreviate.js
+++ b/generate/plugin/rehype-abbreviate.js
@@ -1,47 +1,87 @@
-var h = require('hastscript')
-var findAndReplace = require('hast-util-find-and-replace')
-var singular = require('pluralize').singular
+/**
+ * @import {Element, Properties, Root} from 'hast'
+ * @import {RegExpMatchObject, ReplaceFunction} from 'hast-util-find-and-replace'
+ * @import {VFile} from 'vfile'
+ */
-module.exports = link
+/**
+ * @typedef Options
+ * Configuration.
+ * @property {ReadonlyArray | null | undefined} [ignore]
+ * Abbreviations to ignore.
+ * @property {Readonly>} titles
+ * Abbreviations.
+ */
-var re = /\b([A-Z]\.?[A-Z][\w.]*)\b/g
+import {ok as assert} from 'devlop'
+import {h} from 'hastscript'
+import {defaultIgnore, findAndReplace} from 'hast-util-find-and-replace'
+import pluralize from 'pluralize'
-var ignore = findAndReplace.ignore.concat(['pre', 'code'])
+const re = /\b[A-Z]\.?[A-Z][\w.]*\b/g
-function link(titles) {
- return transform
+/**
+ * @param {Options} options
+ * Configuration.
+ * @returns
+ * Transform.
+ */
+export default function rehypeAbbreviate(options) {
+ assert(options)
+ const titles = options.titles
+ assert(titles)
- function transform(tree, file) {
- var cache = []
+ /**
+ * @param {Root} tree
+ * @param {VFile} file
+ * @returns {undefined}
+ */
+ return function (tree, file) {
+ /** @type {Set} */
+ const cache = new Set()
- findAndReplace(tree, re, replace, {ignore: ignore})
+ findAndReplace(tree, [re, replace], {
+ ignore: [...defaultIgnore, 'code', 'pre']
+ })
- function replace($0) {
- var id = singular($0)
- var first = !cache.includes(id)
- var title = titles[id]
- var props
+ /**
+ * @param {string} $0
+ * @param {RegExpMatchObject} match
+ * @returns {Element | string | false}
+ * @satisfies {ReplaceFunction}
+ */
+ function replace($0, match) {
+ const id = pluralize.singular($0)
- if (title === null) {
- return $0
+ if (options.ignore?.includes(id)) {
+ return false
}
- if (!title) {
- file.message('Missing abbreviation title for `' + id + '`')
- return $0
- }
+ const title = titles[id]
- if (first) {
- cache.push(id)
+ if (!title) {
+ file.message(
+ 'Unexpected abbreviation `' +
+ id +
+ '` w/o description in options, expected it in `ignore` or `titles`',
+ {
+ ancestors: match.stack,
+ source: 'rehype-abbreviate',
+ ruleId: 'missing-title'
+ }
+ )
+ return false
}
- props = {title: title}
+ /** @type {Properties} */
+ const properties = {title}
- if (first) {
- props.className = ['first']
+ if (!cache.has(id)) {
+ cache.add(id)
+ properties.className = ['first']
}
- return h('abbr', props, $0)
+ return h('abbr', properties, $0)
}
}
}
diff --git a/generate/plugin/rehype-defer.js b/generate/plugin/rehype-defer.js
index 409b00ad5ecc..5c063fb08f7b 100644
--- a/generate/plugin/rehype-defer.js
+++ b/generate/plugin/rehype-defer.js
@@ -1,25 +1,38 @@
-'use strict'
+/**
+ * @import {Element, Root} from 'hast'
+ * @import {BuildVisitor} from 'unist-util-visit'
+ */
-var visit = require('unist-util-visit')
+import assert from 'node:assert/strict'
+import {visit} from 'unist-util-visit'
-module.exports = defer
-
-function defer() {
+export default function rehypeDefer() {
return transform
+ /**
+ * @param {Root} tree
+ * @returns {undefined}
+ */
function transform(tree) {
- var scripts = []
- var scope
- var head = null
+ /** @type {Array} */
+ const scripts = []
+ /** @type {Element | undefined} */
+ let head
visit(tree, 'element', visitor)
- scope = head || tree
- scope.children = scope.children.concat(scripts)
+ const scope = head || tree
+ scope.children.push(...scripts)
+ /** @type {BuildVisitor} */
function visitor(node, index, parent) {
if (node.tagName === 'script') {
- if (!node.properties.type || !/module/i.test(node.properties.type)) {
+ assert(parent)
+ assert(typeof index === 'number')
+ if (
+ !node.properties.type ||
+ !/module/i.test(String(node.properties.type))
+ ) {
node.properties.defer = true
}
diff --git a/generate/plugin/rehype-link.js b/generate/plugin/rehype-link.js
index c8a1efb02320..aa8624466399 100644
--- a/generate/plugin/rehype-link.js
+++ b/generate/plugin/rehype-link.js
@@ -1,68 +1,84 @@
-'use strict'
+/**
+ * @import {Root} from 'hast'
+ * @import {FindAndReplaceTuple} from 'hast-util-find-and-replace'
+ */
-var h = require('hastscript')
-var findAndReplace = require('hast-util-find-and-replace')
+import {h} from 'hastscript'
+import {defaultIgnore, findAndReplace} from 'hast-util-find-and-replace'
-module.exports = link
+const replacements = initialise()
-var replacements = initialise()
-
-var ignore = findAndReplace.ignore.concat([
+const ignore = [
+ ...defaultIgnore,
'a',
- 'pre',
'code',
+ 'div', // Twoslash?
'h1',
'h2',
'h3',
'h4',
'h5',
- 'h6'
-])
+ 'h6',
+ 'pre'
+]
-function link() {
+/**
+ * @returns
+ * Transform.
+ */
+export default function rehypeLink() {
return transform
+ /**
+ * @param {Root} tree
+ * @returns {undefined}
+ */
function transform(tree) {
- findAndReplace(tree, replacements, {ignore: ignore})
+ findAndReplace(tree, replacements, {ignore})
}
}
+/**
+ * @returns {Array}
+ */
function initialise() {
- var result = {}
- var dictionary = {
- 'v|file': 'vfile/vfile',
- 'uni|fied': 'unifiedjs/unified',
+ /** @type {Array} */
+ const result = []
+ /** @type {Record} */
+ const dictionary = {
+ 'MD|X': 'mdx-js/mdx',
+ 'dot|ast': 'redotjs/dotast',
+ 'es|ast': 'syntax-tree/esast',
+ 'h|ast': 'syntax-tree/hast',
+ 'md|ast': 'syntax-tree/mdast',
+ 'micro|mark': 'micromark/micromark',
+ 'nl|cst': 'syntax-tree/nlcst',
+ 're|dot': 'redotjs/redot',
+ 're|hype': 'rehypejs/rehype',
're|mark': 'remarkjs/remark',
're|text': 'retextjs/retext',
- 're|hype': 'rehypejs/rehype',
- 're|dot': 'redotjs/redot',
'syntax|-tree': 'syntax-tree',
+ 'uni|fied': 'unifiedjs/unified',
'uni|st': 'syntax-tree/unist',
- 'nl|cst': 'syntax-tree/nlcst',
- 'md|ast': 'syntax-tree/mdast',
- 'h|ast': 'syntax-tree/hast',
- 'x|ast': 'syntax-tree/xast',
- 'dot|ast': 'redotjs/dotast',
- 'MD|X': 'mdx-js/mdx'
+ 'v|file': 'vfile/vfile',
+ 'x|ast': 'syntax-tree/xast'
}
- Object.keys(dictionary).forEach(add)
+ for (const [find, slug] of Object.entries(dictionary)) {
+ const parts = find.split('|')
+ const name = parts.join('')
- return result
-
- function add(find) {
- var parts = find.split('|')
- var name = parts.join('')
- var slug = dictionary[find]
-
- result[name] = replacer
-
- function replacer() {
- return h(
- 'a.' + name.toLowerCase(),
- {href: 'https://github.com/' + slug},
- [h('span.hl', parts[0]), parts[1]]
- )
- }
+ result.push([
+ name,
+ function () {
+ return h(
+ 'a.' + name.toLowerCase(),
+ {href: 'https://github.com/' + slug},
+ [h('span.hl', {}, parts[0]), parts[1]]
+ )
+ }
+ ])
}
+
+ return result
}
diff --git a/generate/plugin/rehype-pictures.js b/generate/plugin/rehype-pictures.js
index 0761859c57f8..5e1dba2116f8 100644
--- a/generate/plugin/rehype-pictures.js
+++ b/generate/plugin/rehype-pictures.js
@@ -1,113 +1,172 @@
-'use strict'
-
-var {join, sep, relative} = require('path')
-var fs = require('fs')
-var vfile = require('to-vfile')
-var sharp = require('sharp')
-var rename = require('vfile-rename')
-var visit = require('unist-util-visit')
-var h = require('hastscript')
-
-module.exports = urls
-
-function urls(options) {
- var sizes = [null, 200, 600, 1200, 2000]
- var formats = ['webp', 'png']
- var mimes = {webp: 'image/webp', png: 'image/png'}
- var modes = ['', '-dark']
- var base = options.base
- var sources = formats
- .flatMap(format =>
- modes.flatMap(mode =>
- sizes.flatMap(size => ({
- stem: {suffix: mode + (size ? '-' + size : '')},
- extname: '.' + format
- }))
- )
- )
- // Remove the default file, w/o mode (light) and w/o size: that’s what we
- // link to already.
- .filter(d => d.stem.suffix !== '')
+/**
+ * @import {ElementContent, Element, Parents, Root} from 'hast'
+ * @import {BuildVisitor} from 'unist-util-visit'
+ * @import {Spec} from 'vfile-rename'
+ */
+
+/**
+ * @typedef Options
+ * Configuration.
+ * @property {URL} base
+ * Base folder.
+ * @property {URL | string} from
+ * Base URL.
+ */
+
+import fs from 'node:fs/promises'
+import {fileURLToPath, pathToFileURL} from 'node:url'
+import {h} from 'hastscript'
+import sharp from 'sharp'
+import {visit} from 'unist-util-visit'
+import {rename} from 'vfile-rename'
+import {VFile} from 'vfile'
+
+/**
+ * @param {Options} options
+ * Configuration.
+ * @returns
+ * Transform.
+ */
+export default function rehypePictures(options) {
+ const sizes = [undefined, 200, 600, 1200, 2000]
+ const mimes = {png: 'image/png', webp: 'image/webp'}
+ const formats = /** @type {Array} */ (Object.keys(mimes))
+ const modes = ['', '-dark']
+ const base = options.base
+ const fromUrl =
+ typeof options.from === 'string' ? new URL(options.from) : options.from
+ /** @type {Array} */
+ const sources = []
+
+ for (const format of formats) {
+ for (const mode of modes) {
+ for (const size of sizes) {
+ const suffix = mode + (size ? '-' + size : '')
+ // Ignore default file, w/o mode (light) and w/o size: that’s what we
+ // link to already.
+ if (!suffix) continue
+ sources.push({
+ extname: '.' + format,
+ stem: {suffix}
+ })
+ }
+ }
+ }
return transform
- function transform(tree) {
- var promises = []
+ /**
+ * @param {Root} tree
+ * @returns {Promise}
+ */
+ async function transform(tree) {
+ /** @type {Array>} */
+ const promises = []
visit(tree, 'element', visitor)
- if (promises.length !== 0) {
- return Promise.all(promises).then(() => {})
- }
+ await Promise.all(promises)
+ /** @type {BuildVisitor} */
function visitor(node, _, parent) {
- var src = (node.tagName === 'img' && node.properties.src) || ''
+ const src = node.tagName === 'img' ? node.properties.src : undefined
- if (!src || src.charAt(0) !== '/') {
+ if (!parent || typeof src !== 'string') {
return
}
promises.push(rewrite(src, node, parent))
- function rewrite(src, node, parent) {
- var resolved = join(base, src.split('/').join(sep))
- var promises = [].concat(
- // See which images exist.
- sources.map(d => {
- var fp = rename(vfile({path: resolved}), d).path
-
- return fs.promises.access(fp, fs.constants.R_OK).then(
- () => fp,
- () => {}
- )
- }),
- // See dimension.
- sharp(resolved).metadata()
- )
-
- return Promise.all(promises).then(res => {
- var info = res.pop()
- var available = res.filter(Boolean)
-
- // Generate the sources, but only if they exist.
- var srcs = formats.flatMap(format =>
- modes.flatMap(mode => {
- var applicable = sizes
- .map(size => {
- var fp = rename(vfile({path: resolved}), {
- stem: {suffix: mode + (size ? '-' + size : '')},
- extname: '.' + format
- }).path
-
- return available.includes(fp) ? [fp, size] : []
- })
- .filter(d => d.length !== 0)
-
- return applicable.length === 0
- ? []
- : h('source', {
- srcSet: applicable.map(
- d =>
- ['/' + relative(base, d[0])] +
- (d[1] ? ' ' + d[1] + 'w' : '')
- ),
- media:
- '(prefers-color-scheme: ' +
- (mode ? 'dark' : 'light') +
- ')',
- type: mimes[format]
- })
- })
+ /**
+ * @param {string} src
+ * @param {Element} node
+ * @param {Parents} parent
+ * @returns {Promise}
+ */
+ async function rewrite(src, node, parent) {
+ const srcUrl = new URL(src, fromUrl)
+ if (srcUrl.origin !== fromUrl.origin) return
+ const localUrl = new URL('.' + srcUrl.pathname, base)
+ /** @type {Set} */
+ const available = new Set()
+ // See which images exist.
+ /** @type {Array>} */
+ const tasks = []
+
+ for (const d of sources) {
+ tasks.push(
+ (async function () {
+ const file = new VFile({path: localUrl})
+ rename(file, d)
+
+ try {
+ await fs.access(file.path, fs.constants.R_OK)
+ } catch {
+ return
+ }
+
+ available.add(file.path)
+ })()
)
-
- var siblings = parent.children
-
- node.properties.loading = 'lazy'
- node.properties.width = info.width
- node.properties.height = info.height
-
- siblings[siblings.indexOf(node)] = h('picture', srcs.concat(node))
- })
+ }
+
+ await Promise.all(tasks)
+ // See dimension.
+ const info = await sharp(fileURLToPath(localUrl)).metadata()
+
+ // Generate the sources, but only if they exist.
+ /** @type {Array} */
+ const results = []
+
+ for (const format of formats) {
+ for (const mode of modes) {
+ /** @type {Array<[path: string, size: number | undefined]>} */
+ const applicable = []
+
+ for (const size of sizes) {
+ const file = new VFile({path: localUrl})
+ rename(file, {
+ extname: '.' + format,
+ stem: {suffix: mode + (size ? '-' + size : '')}
+ })
+
+ if (available.has(file.path)) {
+ applicable.push([file.path, size])
+ }
+ }
+
+ if (applicable.length > 0) {
+ /** @type {Array} */
+ const srcSet = []
+
+ for (const d of applicable) {
+ srcSet.push(
+ '/' +
+ pathToFileURL(d[0]).href.slice(base.href.length) +
+ (d[1] ? ' ' + d[1] + 'w' : '')
+ )
+ }
+
+ results.push(
+ h('source', {
+ media:
+ '(prefers-color-scheme: ' + (mode ? 'dark' : 'light') + ')',
+ srcSet: srcSet.join(','),
+ type: mimes[format]
+ })
+ )
+ }
+ }
+ }
+
+ node.properties.height = info.height
+ node.properties.loading = 'lazy'
+ node.properties.sizes = 'auto'
+ node.properties.width = info.width
+
+ const siblings = parent.children
+ const index = siblings.indexOf(node)
+ siblings[index] = h('picture', {}, [...results, node])
}
}
}
diff --git a/generate/plugin/rehype-resolve-urls.js b/generate/plugin/rehype-resolve-urls.js
index 55aa9c48e763..cd112b56b778 100644
--- a/generate/plugin/rehype-resolve-urls.js
+++ b/generate/plugin/rehype-resolve-urls.js
@@ -1,84 +1,116 @@
-'use strict'
-
-var visit = require('unist-util-visit')
-var map = require('../util/tag-to-url')
-
-module.exports = resolveUrls
-
-var own = {}.hasOwnProperty
-var gh = 'https://github.com'
-
-// Resolve relative URLs to a place in a repo on GH, making them absolute.
-function resolveUrls(options) {
- var opts = options || {}
+/**
+ * @import {Element, Root} from 'hast'
+ * @import {BuildVisitor} from 'unist-util-visit'
+ * @import {VFile} from 'vfile'
+ */
+
+/**
+ * @typedef Options
+ * @property {string | null | undefined} [dirname]
+ * @property {string | null | undefined} [object]
+ * @property {string | null | undefined} [repo]
+ */
+
+import {isElement} from 'hast-util-is-element'
+import {urlAttributes} from 'html-url-attributes'
+import {visit} from 'unist-util-visit'
+
+const gh = 'https://github.com'
+
+/**
+ * Resolve relative URLs to a place in a repo on GH, making them absolute.
+ *
+ * @param {Options | null | undefined} [options]
+ * Configuration.
+ * @returns
+ * Transform.
+ */
+export default function rehypeResolveUrls(options) {
+ const settings = options || {}
return transform
+ /**
+ * @param {Root} tree
+ * @param {VFile} file
+ * @returns {undefined}
+ */
function transform(tree, file) {
- var data = file.data
- var repo = data.repo || opts.repo
- var dirname = data.dirname || opts.dirname || '/'
- var prefix = [repo, 'blob', 'master']
- var base
-
- if (!repo) {
- file.fail('Missing `repo` in `options` or `file.data`', tree)
+ const data = file.data
+ const repo = data.repo || settings.repo || undefined
+ const dirname = data.dirname || settings.dirname || '/'
+ const object = settings.object || 'HEAD'
+
+ if (!repo || typeof repo !== 'string') {
+ file.fail('Unexpected missing `repo` in `options` or `file.data`', tree)
}
- if (dirname && dirname !== '/') {
- prefix = prefix.concat(dirname.split('/'))
+ const prefix = [repo, 'blob', object]
+
+ if (typeof dirname === 'string' && dirname !== '/') {
+ prefix.push(...dirname.split('/'))
}
- base = [gh, ...prefix, ''].join('/')
+ const base = [gh, ...prefix, ''].join('/')
visit(tree, 'element', visitor)
+ /** @type {BuildVisitor} */
function visitor(node) {
- var {tagName} = node
- if (own.call(map, tagName)) {
- map[tagName].forEach(p => resolve(node, p, tagName))
+ for (const property in node.properties) {
+ if (
+ Object.hasOwn(urlAttributes, property) &&
+ isElement(node, urlAttributes[property])
+ ) {
+ resolve(node, property, node.tagName)
+ }
}
}
- function resolve(node, prop, name) {
- var value = node.properties[prop]
- var result
- var length
- var index
+ /**
+ * @param {Element} node
+ * @param {string} property
+ * @param {string} name
+ * @returns {undefined}
+ */
+ function resolve(node, property, name) {
+ const value = node.properties[property]
if (value && typeof value === 'object' && 'length' in value) {
- result = []
- length = value.length
- index = -1
- while (++index < length) {
- result[index] = resolveOne(value[index], prop, node)
+ /** @type {Array} */
+ const result = []
+ let index = -1
+ while (++index < value.length) {
+ result[index] = resolveOne(value[index], property, name)
}
- } else {
- result = resolveOne(value, prop, node, name)
- }
- node.properties[prop] = result
- }
-
- function resolveOne(val, prop, node, name) {
- if (val === undefined || val === null) {
- return
+ node.properties[property] = result
+ } else if (value !== null && value !== undefined) {
+ node.properties[property] = resolveOne(value, property, name)
}
+ }
- val = String(val)
+ /**
+ * @param {boolean | number | string} value
+ * @param {string} property
+ * @param {string} name
+ * @returns {string}
+ */
+ function resolveOne(value, property, name) {
+ value = String(value)
// Absolute paths are interpreted relative to the base, not to GH itself.
- if (val.charAt(0) === '/') {
- val = '.' + val
+ if (value.charAt(0) === '/') {
+ value = '.' + value
}
- val = new URL(val, base)
+ const url = new URL(value, base)
- if (name === 'img' && prop === 'src' && val.host === 'github.com') {
- val.searchParams.set('raw', 'true')
+ if (name === 'img' && property === 'src' && url.host === 'github.com') {
+ url.searchParams.set('raw', 'true')
}
- return val.href
+ return url.href
}
}
}
diff --git a/generate/plugin/rehype-rewrite-urls.js b/generate/plugin/rehype-rewrite-urls.js
index 587dacec1e03..5ed67f1369d0 100644
--- a/generate/plugin/rehype-rewrite-urls.js
+++ b/generate/plugin/rehype-rewrite-urls.js
@@ -1,38 +1,64 @@
-'use strict'
-
-var visit = require('unist-util-visit')
-var data = require('../data')
-var map = require('../util/tag-to-url')
-
-module.exports = urls
-
-var own = {}.hasOwnProperty
-
-function urls(options) {
- var opts = options || {}
+/**
+ * @import {Element, Root} from 'hast'
+ * @import {BuildVisitor} from 'unist-util-visit'
+ * @import {VFile} from 'vfile'
+ */
+
+/**
+ * @typedef Options
+ * @property {string | null | undefined} [origin]
+ * @property {string | null | undefined} [pathname]
+ */
+
+import assert from 'node:assert/strict'
+import {isElement} from 'hast-util-is-element'
+import {urlAttributes} from 'html-url-attributes'
+import {visit} from 'unist-util-visit'
+import {data} from '../data.js'
+
+/**
+ * @param {Options | null | undefined} [options]
+ * Configuration.
+ * @returns
+ * Transform.
+ */
+export default function rehypeRewriteUrls(options) {
+ const settings = options || {}
return transform
+ /**
+ * @param {Root} tree
+ * @param {VFile} file
+ * @returns {undefined}
+ */
function transform(tree, file) {
- var meta = file.data.meta || {}
- var origin = meta.origin || opts.origin
- var pathname = meta.pathname || opts.pathname || '/'
+ const meta = file.data.meta || {}
+ const origin = meta.origin || settings.origin || undefined
+ const pathname = meta.pathname || settings.pathname || '/'
if (!origin) {
- file.fail('Missing `origin` in `options` or `file.data.meta`', tree)
+ file.fail(
+ 'Unexpected missing `origin` in `options` or `file.data.meta`',
+ tree
+ )
}
visit(tree, 'element', visitor)
+ /** @type {BuildVisitor} */
function visitor(node) {
- var head
-
- if (own.call(map, node.tagName)) {
- map[node.tagName].forEach(p => rewrite(node, p))
+ for (const property in node.properties) {
+ if (
+ Object.hasOwn(urlAttributes, property) &&
+ isElement(node, urlAttributes[property])
+ ) {
+ rewrite(node, property)
+ }
}
if (node.tagName === 'a') {
- head = (node.properties.href || '').charAt(0)
+ const head = String(node.properties.href || '').charAt(0)
if (head && head !== '/' && head !== '#') {
node.properties.rel = ['nofollow', 'noopener', 'noreferrer']
@@ -40,11 +66,17 @@ function urls(options) {
}
}
- function rewrite(node, prop) {
- var value = node.properties[prop]
- var url
-
- if (value === undefined || value === null) {
+ /**
+ * @param {Element} node
+ * @param {string} property
+ * @returns {undefined}
+ */
+ function rewrite(node, property) {
+ let value = node.properties[property]
+ /** @type {URL | undefined} */
+ let url
+
+ if (value === null || value === undefined) {
return
}
@@ -52,71 +84,76 @@ function urls(options) {
try {
url = new URL(value, origin + pathname)
- } catch (_) {
+ } catch {
return
}
url = rewriteNpm(url, origin) || rewriteGithub(url, origin) || url
+ assert(url)
// Minify / make relative.
- if (url.origin === origin) {
- if (url.pathname === pathname) {
- value = url.hash || '#'
- } else {
- value = url.pathname + url.hash
- }
+ if (url && url.origin === origin) {
+ value =
+ url.pathname === pathname ? url.hash || '#' : url.pathname + url.hash
} else {
value = url.href
}
- node.properties[prop] = value
+ node.properties[property] = value
}
}
+ /**
+ * @param {URL} url
+ * @param {string | undefined} origin
+ * @returns {URL | undefined}
+ */
function rewriteNpm(url, origin) {
- var host = url.host
- var rest
- var name
+ let host = url.host
if (host.startsWith('www.')) {
host = host.slice(4)
}
if (host === 'npmjs.com' && url.pathname.startsWith('/package/')) {
- rest = url.pathname.slice('/package/'.length).split('/')
+ const rest = url.pathname.slice('/package/'.length).split('/')
- // Ignore trailing slasg.
- if (rest[rest.length - 1] === '') {
+ // Ignore trailing slas.
+ if (rest.at(-1) === '') {
rest.pop()
}
// Support unscoped and scoped.
if (rest.length > 0 && rest.length < 3) {
- name = rest.join('/')
+ const name = rest.join('/')
- if (own.call(data.packageByName, name)) {
+ if (Object.hasOwn(data.packageByName, name)) {
return new URL('/explore/package/' + name + '/' + url.hash, origin)
}
}
}
}
+ /**
+ * @param {URL} url
+ * @param {string | undefined} origin
+ * @returns {URL | undefined}
+ */
function rewriteGithub(url, origin) {
- var host = url.host
- var rest
- var repo
- var length
- var packages
- var slug
- var match
+ const host = url.host
if (host === 'github.com') {
- rest = url.pathname.slice(1).split('/')
+ let rest = url.pathname.slice(1).split('/')
- repo = rest.slice(0, 2).join('/')
+ // Tree goes to directories, blob to files.
+ if (rest[3] === 'master' && (rest[2] === 'tree' || rest[2] === 'blob')) {
+ rest[3] = 'HEAD'
+ }
- if (own.call(data.projectByRepo, repo)) {
- packages = data.packagesByRepo[repo]
+ const repo = rest.slice(0, 2).join('/')
+
+ if (Object.hasOwn(data.packagesByRepo, repo)) {
+ const packages = data.packagesByRepo[repo]
rest = rest.slice(2)
// Tree goes to directories, blob to files.
@@ -126,12 +163,14 @@ function urls(options) {
}
// Pop trailing slash.
- if (rest[rest.length - 1] === '') {
+ let tail = rest.at(-1)
+ if (tail === '') {
rest.pop()
+ tail = rest.at(-1)
}
// Pop readme.
- if (/^readme.md$/i.test(rest[rest.length - 1])) {
+ if (tail && /^readme\.md$/i.test(tail)) {
rest.pop()
}
@@ -140,14 +179,14 @@ function urls(options) {
url.hash = ''
}
- length = rest.length
+ let length = rest.length
while (length > -1) {
- slug = rest.slice(0, length)
- slug = slug.length === 0 ? undefined : slug.join('/')
- match = packages.find(
- d => data.packageByName[d].manifestBase === slug
- )
+ const slugParts = rest.slice(0, length)
+ const slug = slugParts.length === 0 ? undefined : slugParts.join('/')
+ const match = packages.find(function (d) {
+ return data.packageByName[d].manifestBase === slug
+ })
if (match && rest.length === length) {
return new URL('/explore/package/' + match + '/' + url.hash, origin)
@@ -160,6 +199,9 @@ function urls(options) {
if (!url.hash && rest.length === 0) {
return new URL('/explore/project/' + repo + '/', origin)
}
+ } else {
+ url.pathname = '/' + rest.join('/')
+ return url
}
}
}
diff --git a/generate/plugin/rehype-wrap.js b/generate/plugin/rehype-wrap.js
index b8ad23a0d018..25096352bd7d 100644
--- a/generate/plugin/rehype-wrap.js
+++ b/generate/plugin/rehype-wrap.js
@@ -1,11 +1,17 @@
-var header = require('../molecule/header')
-var footer = require('../molecule/footer')
+/**
+ * @import {Root} from 'hast'
+ */
-module.exports = section
+import {header} from '../molecule/header.js'
+import {footer} from '../molecule/footer.js'
-function section() {
+export default function rehypeWrap() {
return transform
+ /**
+ * @param {Root} tree
+ * @returns {undefined}
+ */
function transform(tree) {
tree.children.unshift(header())
tree.children.push(footer())
diff --git a/generate/types.d.ts b/generate/types.d.ts
new file mode 100644
index 000000000000..43e1b3ef3a63
--- /dev/null
+++ b/generate/types.d.ts
@@ -0,0 +1,37 @@
+// Turn into module.
+export {}
+
+/**
+ * Map of choices; `other` is required.
+ */
+export interface PluralRules
+ extends Partial> {
+ other: string
+}
+
+interface UnifiedjsGenerateFields {
+ /**
+ * GitHub username of author; data specific to `unifiedjs.github.io`.
+ */
+ archive?: boolean | undefined
+ /**
+ * GitHub username of author; data specific to `unifiedjs.github.io`.
+ */
+ authorGithub?: string | undefined
+ /**
+ * Index of post; data specific to `unifiedjs.github.io`.
+ */
+ index?: number | undefined
+ /**
+ * Group of post; data specific to `unifiedjs.github.io`.
+ */
+ group?: 'recipe' | 'guide' | undefined
+}
+
+declare module 'vfile' {
+ interface DataMapMatter extends UnifiedjsGenerateFields {}
+
+ interface DataMap {
+ matter: DataMapMatter
+ }
+}
diff --git a/generate/types.js b/generate/types.js
new file mode 100644
index 000000000000..683b5d19aa6a
--- /dev/null
+++ b/generate/types.js
@@ -0,0 +1,2 @@
+// Note: types only.
+export {}
diff --git a/generate/util/constant-collective.js b/generate/util/constant-collective.js
index 971c96755d4b..90c9e2ce9e03 100644
--- a/generate/util/constant-collective.js
+++ b/generate/util/constant-collective.js
@@ -1,14 +1,16 @@
-'use strict'
-
-// The orgs in the collective.
-module.exports = [
- 'remarkjs',
+/**
+ * The orgs in the collective.
+ *
+ * @type {ReadonlyArray}
+ */
+export const constantCollective = [
+ 'mdx-js',
+ 'micromark',
+ 'redotjs',
'rehypejs',
+ 'remarkjs',
'retextjs',
- 'redotjs',
'syntax-tree',
- 'vfile',
'unifiedjs',
- 'micromark',
- 'mdx-js'
+ 'vfile'
]
diff --git a/generate/util/constant-locale.js b/generate/util/constant-locale.js
index bea90273b9fc..9c3dcc7da8bd 100644
--- a/generate/util/constant-locale.js
+++ b/generate/util/constant-locale.js
@@ -1,3 +1 @@
-'use strict'
-
-module.exports = 'en-US'
+export const constantLocale = 'en-US'
diff --git a/generate/util/constant-topic.js b/generate/util/constant-topic.js
index 73491143ca3c..e2ceb0103d2e 100644
--- a/generate/util/constant-topic.js
+++ b/generate/util/constant-topic.js
@@ -1,18 +1,23 @@
-'use strict'
-
-// Topics used to tag projects.
-module.exports = [
- 'unified-plugin',
- 'remark-plugin',
+// Note: `mdx` not included as it is used too often by unrelated things.
+/**
+ * Topics used to tag projects.
+ *
+ * @type {ReadonlyArray}
+ */
+export const constantTopic = [
+ 'hast-util',
+ 'esast-util',
+ 'mdast-util',
+ 'micromark-extension',
+ 'nlcst-util',
+ 'redot-plugin',
'rehype-plugin',
+ 'remark-lint-rule',
+ 'remark-plugin',
'retext-plugin',
- 'redot-plugin',
+ 'unified-plugin',
+ 'unifiedjs',
'unist-util',
- 'mdast-util',
- 'hast-util',
- 'xast-util',
- 'nlcst-util',
'vfile-util',
- 'unifiedjs'
- // 'mdx', // Used too often by unrelated things...
+ 'xast-util'
]
diff --git a/generate/util/fmt-bytes.js b/generate/util/fmt-bytes.js
index 2439c69446dd..07e14b9f2479 100644
--- a/generate/util/fmt-bytes.js
+++ b/generate/util/fmt-bytes.js
@@ -1,10 +1,10 @@
-'use strict'
+import prettyBytes from 'pretty-bytes'
+import {constantLocale} from './constant-locale.js'
-var fmt = require('pretty-bytes')
-var locale = require('./constant-locale')
-
-module.exports = bytes
-
-function bytes(value) {
- return fmt(value || 0, {locale})
+/**
+ * @param {number | undefined} value
+ * @returns {string}
+ */
+export function fmtBytes(value) {
+ return prettyBytes(value || 0, {locale: constantLocale})
}
diff --git a/generate/util/fmt-compact.js b/generate/util/fmt-compact.js
index 9477f0d8afb6..d5b3d10bdf8d 100644
--- a/generate/util/fmt-compact.js
+++ b/generate/util/fmt-compact.js
@@ -1,12 +1,11 @@
-'use strict'
-
-var abbr = require('number-abbreviate')
-
-// Would like to use: `.toLocaleString(locale, {notation: 'compact'})`,
-// but that’s not widely supported yet.
-
-module.exports = compact
-
-function compact(value) {
- return String(abbr(value || 0))
+import {constantLocale} from './constant-locale.js'
+
+/**
+ * @param {number | undefined} value
+ * @returns {string}
+ */
+export function fmtCompact(value) {
+ return (value || 0)
+ .toLocaleString(constantLocale, {notation: 'compact'})
+ .toLowerCase()
}
diff --git a/generate/util/fmt-percent.js b/generate/util/fmt-percent.js
index b49bb961aa70..d011c0fc67e7 100644
--- a/generate/util/fmt-percent.js
+++ b/generate/util/fmt-percent.js
@@ -1,9 +1,9 @@
-'use strict'
+import {constantLocale} from './constant-locale.js'
-var locale = require('./constant-locale')
-
-module.exports = percent
-
-function percent(value) {
- return (value || 0).toLocaleString(locale, {style: 'percent'})
+/**
+ * @param {number | undefined} value
+ * @returns {string}
+ */
+export function fmtPercent(value) {
+ return (value || 0).toLocaleString(constantLocale, {style: 'percent'})
}
diff --git a/generate/util/fmt-plural.js b/generate/util/fmt-plural.js
index 776824d65db0..b00ac4012560 100644
--- a/generate/util/fmt-plural.js
+++ b/generate/util/fmt-plural.js
@@ -1,11 +1,16 @@
-'use strict'
+/**
+ * @import {PluralRules} from '../types.js'
+ */
-var locale = require('./constant-locale')
+import {constantLocale} from './constant-locale.js'
-module.exports = plural
+const plurals = new Intl.PluralRules(constantLocale)
-var plurals = new Intl.PluralRules(locale)
-
-function plural(count, rules) {
- return rules[plurals.select(count || 0)] || rules.other
+/**
+ * @param {number} count
+ * @param {PluralRules} rules
+ * @returns {string}
+ */
+export function fmtPlural(count, rules) {
+ return rules[plurals.select(count)] || rules.other
}
diff --git a/generate/util/fmt-url.js b/generate/util/fmt-url.js
index 22cd386cdce0..45aec81b8120 100644
--- a/generate/util/fmt-url.js
+++ b/generate/util/fmt-url.js
@@ -1,9 +1,9 @@
-'use strict'
+import humanizeUrl from 'humanize-url'
-var fmt = require('humanize-url')
-
-module.exports = url
-
-function url(value) {
- return fmt(value || '')
+/**
+ * @param {string | undefined} value
+ * @returns {string}
+ */
+export function fmtUrl(value) {
+ return humanizeUrl(value || '')
}
diff --git a/generate/util/pick-random.js b/generate/util/pick-random.js
index ccdf1e24d234..41894f2f326f 100644
--- a/generate/util/pick-random.js
+++ b/generate/util/pick-random.js
@@ -1,9 +1,11 @@
-'use strict'
+import pickRandom_ from 'pick-random'
-var pick = require('pick-random')
-
-module.exports = pickRandom
-
-function pickRandom(list, max) {
- return list.length > max ? pick(list, {count: max}) : list
+/**
+ * @template T
+ * @param {ReadonlyArray