├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .gulp.json ├── .nvmrc ├── .stylelintrc ├── LICENSE ├── README.adoc ├── gulp.d ├── lib │ ├── create-task.js │ ├── export-tasks.js │ └── gulp-prettier-eslint.js └── tasks │ ├── build-preview-pages.js │ ├── build.js │ ├── format.js │ ├── index.js │ ├── lint-css.js │ ├── lint-js.js │ ├── pack.js │ ├── release.js │ ├── remove.js │ └── serve.js ├── gulpfile.js ├── index.js ├── netlify.toml ├── package-lock.json ├── package.json ├── preview-src ├── 404.adoc ├── home.adoc ├── index.adoc ├── multirepo-ssg.svg └── ui-model.yml └── src ├── css ├── base.css ├── body.css ├── breadcrumbs.css ├── doc.css ├── footer.css ├── header.css ├── highlight.css ├── home.css ├── main.css ├── nav.css ├── page-versions.css ├── pagination.css ├── print.css ├── site.css ├── toc.css ├── toolbar.css ├── typeface-comfortaa.css ├── typeface-roboto-mono.css ├── typeface-roboto.css ├── vars.css └── vendor │ ├── docsearch.css │ └── tabs.css ├── helpers ├── and.js ├── detag.js ├── dig.js ├── endsWith.js ├── eq.js ├── increment.js ├── matches.js ├── ne.js ├── not.js ├── or.js ├── rearrange.js ├── siteStartPage.js ├── spec.js └── year.js ├── img ├── algolia-logo.svg ├── asciidoctor-logo.svg ├── asciidoctor-og.png ├── favicon.ico ├── netlify-logo.svg ├── octicons-16.svg ├── octicons-24.svg ├── twitter-logo.svg └── zulip-logo.svg ├── js ├── 01-nav.js ├── 02-on-this-page.js ├── 03-fragment-jumper.js ├── 04-page-versions.js ├── 05-mobile-navbar.js ├── 06-copy-to-clipboard.js ├── 07-copy-page-spec.js └── vendor │ ├── docsearch.bundle.js │ ├── highlight.bundle.js │ └── tabs.bundle.js ├── layouts ├── 404.hbs └── default.hbs └── partials ├── article-404.hbs ├── article.hbs ├── body.hbs ├── breadcrumbs.hbs ├── edit-this-page.hbs ├── footer-content.hbs ├── footer-scripts.hbs ├── footer.hbs ├── head-icons.hbs ├── head-info.hbs ├── head-meta.hbs ├── head-prelude.hbs ├── head-scripts.hbs ├── head-styles.hbs ├── head-title.hbs ├── head.hbs ├── header-content.hbs ├── header-scripts.hbs ├── header.hbs ├── main.hbs ├── nav-explore.hbs ├── nav-menu.hbs ├── nav-toggle.hbs ├── nav-tree.hbs ├── nav.hbs ├── page-versions.hbs ├── pagination.hbs ├── toc.hbs └── toolbar.hbs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "rules": { 4 | "arrow-parens": ["error", "always"], 5 | "comma-dangle": ["error", { 6 | "arrays": "always-multiline", 7 | "objects": "always-multiline", 8 | "imports": "always-multiline", 9 | "exports": "always-multiline" 10 | }], 11 | "no-restricted-properties": ["error", { 12 | "property": "substr", 13 | "message": "Use String#slice instead." 14 | }], 15 | "max-len": [1, 120, 2], 16 | "spaced-comment": "off", 17 | "radix": ["error", "always"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [main] 5 | permissions: 6 | contents: write 7 | jobs: 8 | activate: 9 | runs-on: ubuntu-latest 10 | if: | 11 | github.repository == 'asciidoctor/asciidoctor-docs-ui' && !startsWith(github.event.head_commit.message, 'Release ') 12 | steps: 13 | - run: echo ok go 14 | build: 15 | needs: activate 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | - name: Install Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '16' 24 | cache: 'npm' 25 | - name: Install dependencies 26 | run: npm ci 27 | - name: Tag and release 28 | env: 29 | GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | run: npx gulp release 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /node_modules/ 3 | /public/ 4 | -------------------------------------------------------------------------------- /.gulp.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Build tasks for the Antora default UI project", 3 | "flags.tasksDepth": 1 4 | } 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "comment-empty-line-before": null, 5 | "no-descending-specificity": null, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Asciidoctor Docs UI 2 | // Variables: 3 | :current-release: prod-349 4 | // Settings: 5 | :experimental: 6 | :hide-uri-scheme: 7 | :toc: macro 8 | ifdef::env-github[] 9 | :important-caption: :exclamation: 10 | :tip-caption: :bulb: 11 | :!toc-title: 12 | :badges: 13 | endif::[] 14 | // Project URLs: 15 | :project-repo-name: asciidoctor/asciidoctor-docs-ui 16 | :url-project: https://github.com/{project-repo-name} 17 | :url-preview: https://asciidoctor-docs-ui.netlify.app 18 | :url-ci: {project-repo-name}/actions 19 | :url-netlify-deploys: https://app.netlify.com/sites/asciidoctor-docs-ui/deploys 20 | // External URLs: 21 | :url-antora: https://antora.org 22 | :url-antora-docs: https://docs.antora.org 23 | :url-antora-default-ui: https://gitlab.com/antora/antora-ui-default 24 | :url-asciidoctor: https://asciidoctor.org 25 | :url-git: https://git-scm.com 26 | :url-git-dl: {url-git}/downloads 27 | :url-opendevise: https://opendevise.com 28 | :url-nodejs: https://nodejs.org 29 | :url-nvm: https://github.com/creationix/nvm 30 | :url-nvm-install: {url-nvm}#installation 31 | :url-source-maps: https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map 32 | 33 | ifdef::badges[] 34 | image:https://img.shields.io/github/release/{project-repo-name}.svg[Latest Release,link={url-project}/releases/download/{current-release}/ui-bundle.zip] 35 | image:https://api.netlify.com/api/v1/badges/a9db5e1b-d7b7-48c0-b066-1b8d91e9c3d1/deploy-status[Deploy Status,link={url-netlify-deploys}] 36 | endif::[] 37 | 38 | toc::[] 39 | 40 | This project provides the UI for the Asciidoctor docs site. 41 | It provides both the visual theme and user interactions for the docs site. 42 | The UI bundle that this project produces is designed to be used with Antora. 43 | It bundles the HTML templates (layouts, partials, and helpers), CSS, JavaScript, fonts, and site-wide images. 44 | The rest of the material for the docs site comes from the content repositories. 45 | 46 | == Usage 47 | 48 | To use this UI with Antora, add the following configuration to the playbook file for your site: 49 | 50 | [,yaml,subs=attributes+] 51 | ---- 52 | ui: 53 | bundle: 54 | url: {url-project}/releases/download/{current-release}/ui-bundle.zip 55 | ---- 56 | 57 | To avoid having to manage the release number in the URL, you can grab the latest release using the following configuration instead: 58 | 59 | [,yaml,subs=attributes+] 60 | ---- 61 | ui: 62 | bundle: 63 | url: {url-project}/releases/download/prod-latest/ui-bundle.zip 64 | ---- 65 | 66 | In order for Antora to fetch the latest UI bundle in this case, the `--fetch` option must be used and the `snapshot` key must be set to `true` to bypass the cache. 67 | 68 | Read on to learn how to develop the UI. 69 | 70 | == Development Quickstart 71 | 72 | This section offers a basic tutorial to teach you how to set up the UI project, preview it locally, and bundle it for use with Antora. 73 | A more comprehensive tutorial can be found in the documentation at {url-antora-docs}. 74 | 75 | === Prerequisites 76 | 77 | To preview and bundle the UI, you need the following software on your computer: 78 | 79 | * {url-git}[git] (command: `git`) 80 | * {url-nodejs}[Node.js] (commands: `node`, `npm`, and `npx`) 81 | 82 | ==== git 83 | 84 | First, make sure you have git installed. 85 | 86 | $ git --version 87 | 88 | If not, {url-git-dl}[download and install] the git package for your system. 89 | 90 | ==== Node.js 91 | 92 | Next, make sure that you have Node.js installed (which also provides npm). 93 | 94 | $ node --version 95 | 96 | If this command fails with an error, you don't have Node.js installed. 97 | If the command doesn't report an active LTS version of Node.js (e.g., v16.16.0), it means you don't have a suitable version of Node.js installed. 98 | In this guide, we'll be installing Node.js 16. 99 | 100 | While you can install Node.js from the official packages, we strongly recommend that you use {url-nvm}[nvm] (Node Version Manager) to manage your Node.js installation(s). 101 | Follow the {url-nvm-install}[nvm installation instructions] to set up nvm on your machine. 102 | 103 | Once you've installed nvm, open a new terminal and install Node.js 16 using the following command: 104 | 105 | $ nvm install 16 106 | 107 | You can switch to this version of Node.js at any time using the following command: 108 | 109 | $ nvm use 16 110 | 111 | To make Node.js 16 the default in new terminals, type: 112 | 113 | $ nvm alias default 16 114 | 115 | Now that you have Node.js installed, you can proceed with cloning and initializing the project. 116 | 117 | === Clone and Initialize the UI Project 118 | 119 | Clone the UI project using git: 120 | 121 | [subs=attributes+] 122 | $ git clone {url-project} && 123 | cd "`basename $_`" 124 | 125 | The example above clones the UI project and then switches to the project folder on your filesystem. 126 | Stay in this project folder when executing all subsequent commands. 127 | 128 | Use npm to install the project's dependencies inside the project. 129 | In your terminal, execute the following command: 130 | 131 | $ npm ci 132 | 133 | This command installs the dependencies listed in [.path]_package.json_ into the [.path]_node_modules/_ folder inside the project. 134 | This folder does not get included in the UI bundle and should _not_ be committed to the source control repository. 135 | 136 | === Preview the UI 137 | 138 | The UI project is configured to preview offline. 139 | The files in the [.path]_preview-site-src/_ folder provide the sample content that allow you to see the UI in action. 140 | In this folder, you'll primarily find pages written in AsciiDoc. 141 | These pages provide a representative sample and kitchen sink of content from the real site. 142 | 143 | To build the UI and preview it in a local web server, run the `preview` command: 144 | 145 | $ npx gulp preview 146 | 147 | [TIP] 148 | ==== 149 | Alternatively, you can use the following command: 150 | 151 | $ npm start 152 | 153 | When using `npm start`, you don't have to prefix the `npm` command with `npx`. 154 | ==== 155 | 156 | You'll see a URL listed in the output of this command: 157 | 158 | .... 159 | [12:00:00] Starting server... 160 | [12:00:00] Server started http://localhost:5252 161 | [12:00:00] Running server 162 | .... 163 | 164 | Navigate to this URL to preview the site locally. 165 | 166 | While this command is running, any changes you make to the source files will be instantly reflected in the browser. 167 | This works by monitoring the project for changes, running the `preview:build` task if a change is detected, and sending the updates to the browser. 168 | 169 | Press kbd:[Ctrl+C] to stop the preview server and end the continuous build. 170 | 171 | === Package for Use with Antora 172 | 173 | If you need to package the UI so you can use it to generate the documentation site locally, run the following command: 174 | 175 | $ npx gulp bundle 176 | 177 | If any errors are reported by lint, you'll need to fix them. 178 | 179 | When the command completes successfully, the UI bundle will be available at [.path]_build/ui-bundle.zip_. 180 | You can point Antora at this bundle using the `--ui-bundle-url` command-line option. 181 | 182 | If you have the preview running, and you want to bundle without causing the preview to be clobbered, use: 183 | 184 | $ npx gulp bundle:pack 185 | 186 | The UI bundle will again be available at [.path]_build/ui-bundle.zip_. 187 | 188 | ==== Source Maps 189 | 190 | The build consolidates all the CSS and client-side JavaScript into combined files, [.path]_site.css_ and [.path]_site.js_, respectively, in order to reduce the size of the bundle. 191 | {url-source-maps}[Source maps] correlate these combined files with their original sources. 192 | 193 | This "`source mapping`" is accomplished by generating additional map files that make this association. 194 | These map files sit adjacent to the combined files in the build folder. 195 | The mapping they provide allows the debugger to present the original source rather than the obfuscated file, an essential tool for debugging. 196 | 197 | In preview mode, source maps are enabled automatically, so there's nothing you have to do to make use of them. 198 | If you need to include source maps in the bundle, you can do so by setting the `SOURCEMAPS` environment varible to `true` when you run the bundle command: 199 | 200 | $ SOURCEMAPS=true npx gulp bundle 201 | 202 | In this case, the bundle will include the source maps, which can be used for debuggging your production site. 203 | 204 | === Create an Online Preview 205 | 206 | You can share a preview of the UI online by submitting a pull request to GitHub. 207 | The repository is configured to create a deploy preview on Netlify for every pull request. 208 | Here's how that process works: 209 | 210 | . Fork the repository on GitHub (only has to be done once). 211 | . Create a local branch. 212 | . Make changes to the UI. 213 | . Commit your changes to that branch. 214 | . Push that branch to your fork (on GitHub). 215 | . Submit a pull request from the branch you pushed to your fork. 216 | . Wait for deploy/netlify check to say "`Deploy preview ready!`" on the pull request page. 217 | . Click on the "`Details`" link under "`Show all checks`" on the pull request page to get the preview URL. 218 | . Visit the preview URL to view your changes or share the preview URL with others. 219 | 220 | The deploy preview works because there is a webhook on the repository that pings \https://api.netlify.com/hooks/github for the following events: push, pull_request, delete_branch. 221 | Netlify then runs the command specified in netlify.toml, deploys the site, and allocates a temporary preview URL for it. 222 | 223 | Included in that temporary preview URL is the UI bundle itself. 224 | That means you can test it directly with Antora. 225 | To access the UI bundle, append `dist/ui-bundle.zip` to the end of the preview URL, then pass that URL to Antora as follows: 226 | 227 | $ antora --ui-bundle-url=/dist/ui-bundle.zip antora-playbook.yml 228 | 229 | The temporary preview URL will automatically be decommissioned once the PR is closed. 230 | 231 | == Releases 232 | 233 | Releases are handled by the `npx gulp release` task, which is automated by a CI job. 234 | The release process boils down to the following steps: 235 | 236 | . Pack the UI bundle. 237 | . Tag the git repository using the next version number in the sequence (e.g., v100 after v99) 238 | . Create a GitHub release from that git tag. 239 | . Attach the UI bundle to that release as an asset in zip format. 240 | . Update the README to reference the URL of the lastest bundle and commit that update to the repository. 241 | 242 | Fortunately, you don't have to do any of these steps yourself. 243 | These steps are fully automated by the `npx gulp release` task. 244 | In fact, you don't even have to run this task. 245 | Whenever a commit is pushed to the master branch of the repository, it triggers the CI job on master, which executes the `npx gulp release` task using pre-configured credentials. 246 | 247 | IMPORTANT: A release will only be made if the project validates (i.e., all lint tasks pass). 248 | To validate the project, run `npx gulp lint` before pushing your changes to GitHub. 249 | 250 | The CI job is already configured, so there's nothing you need to do to make automated release work. 251 | All you have to do is commit your changes and push those commits to the master branch of the git repository. 252 | A few seconds later, a new bundle will be available for use with Antora. 253 | Run `git pull` to retrieve the updated README that includes the new URL. 254 | 255 | If you want to commit a change to master without making a release, add the string `[skip ci]` to the end of the commit message. 256 | 257 | The next two sections document how the CI job is set up an configured. 258 | 259 | === Release Task Prerequisites 260 | 261 | In addition to the <> covered above, you'll need a personal access token for the automated GitHub account, asciidoctor-docbot, so it has permission to make changes to the repository on GitHub. 262 | The asciidoctor-docbot account will need at least write access to the {url-project} repository, though admin access is recommended. 263 | 264 | Start by creating a https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/[personal access token] for the asciidoctor-docbot user. 265 | The `release` task relies on this token to interact with the GitHub API to create the tag, create the release, and attach the UI bundle. 266 | The token must have the `public_repo` scope. 267 | No other scopes are required (as long as the asciidoctor-docbot user has write access to the repository). 268 | 269 | === CI Job 270 | 271 | The {url-ci}[CI job] is executed by GitHub Actions and is defined in the file [.path]_.github/workflows/release.yml_. 272 | It boils down to running the `npx gulp release` task on the main branch. 273 | The GITHUB_API_TOKEN environment variable is defined in the job configuration. 274 | 275 | Once the CI job runs and a new UI bundle is available, you can update the URL of the UI bundle in the Antora playbook file. 276 | See <> for details. 277 | 278 | == Copyright and License 279 | 280 | === Software 281 | 282 | This project is a derivative of the {url-antora-default-ui}[Antora default UI]. 283 | The software assets in this repository (Gulp build script and tasks, web JavaScript files, Handlebars templates and JavaScript helpers, common CSS, utility icons, etc.) come from the {url-antora}[Antora project]. 284 | As such, use of the software is granted under the terms of the https://www.mozilla.org/en-US/MPL/2.0/[Mozilla Public License Version 2.0] (MPL-2.0). 285 | See link:LICENSE[] to find the full license text. 286 | 287 | === Branding and Design 288 | 289 | Copyright (C) {url-asciidoctor}[Asciidoctor] 2018-present. 290 | This includes any CSS that provides colors or iconography that depict the Asciidoctor brand. 291 | All rights reserved (until further notice). 292 | -------------------------------------------------------------------------------- /gulp.d/lib/create-task.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const metadata = require('undertaker/lib/helpers/metadata') 4 | const { watch } = require('gulp') 5 | 6 | module.exports = ({ name, desc, opts, call: fn, loop }) => { 7 | if (name) { 8 | const displayName = fn.displayName 9 | if (displayName === '' || displayName === '') { 10 | metadata.get(fn).tree.label = `${displayName} ${name}` 11 | } 12 | fn.displayName = name 13 | } 14 | if (loop) { 15 | const delegate = fn 16 | name = delegate.displayName 17 | delegate.displayName = `${name}:loop` 18 | fn = () => watch(loop, { ignoreInitial: false }, delegate) 19 | fn.displayName = name 20 | } 21 | if (desc) fn.description = desc 22 | if (opts) fn.flags = opts 23 | return fn 24 | } 25 | -------------------------------------------------------------------------------- /gulp.d/lib/export-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (...tasks) => { 4 | const seed = {} 5 | if (tasks.length) { 6 | if (tasks.lastIndexOf(tasks[0]) > 0) { 7 | const task1 = tasks.shift() 8 | seed.default = Object.assign(task1.bind(null), { description: `=> ${task1.displayName}`, displayName: 'default' }) 9 | } 10 | return tasks.reduce((acc, it) => (acc[it.displayName || it.name] = it) && acc, seed) 11 | } else { 12 | return seed 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gulp.d/lib/gulp-prettier-eslint.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('fancy-log') 4 | const PluginError = require('plugin-error') 5 | const prettierEslint = require('prettier-eslint') 6 | const { Transform } = require('node:stream') 7 | const map = (transform) => new Transform({ objectMode: true, transform }) 8 | 9 | module.exports = () => { 10 | const report = { changed: 0, unchanged: 0 } 11 | return map(format).on('finish', () => { 12 | if (report.changed > 0) { 13 | const changed = 'formatted ' 14 | .concat(report.changed) 15 | .concat(' file') 16 | .concat(report.changed === 1 ? '' : 's') 17 | const unchanged = 'left ' 18 | .concat(report.unchanged) 19 | .concat(' file') 20 | .concat(report.unchanged === 1 ? '' : 's') 21 | .concat(' unchanged') 22 | log(`prettier-eslint: ${changed}; ${unchanged}`) 23 | } else { 24 | log(`prettier-eslint: left ${report.unchanged} file${report.unchanged === 1 ? '' : 's'} unchanged`) 25 | } 26 | }) 27 | 28 | function format (file, enc, next) { 29 | if (file.isNull()) return next() 30 | if (file.isStream()) return next(new PluginError('gulp-prettier-eslint', 'Streaming not supported')) 31 | 32 | const input = file.contents.toString() 33 | const output = prettierEslint({ text: input, filePath: file.path }) 34 | 35 | if (input === output) { 36 | report.unchanged += 1 37 | } else { 38 | report.changed += 1 39 | file.contents = Buffer.from(output) 40 | } 41 | 42 | next(null, file) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gulp.d/tasks/build-preview-pages.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Asciidoctor = require('@asciidoctor/core')() 4 | const fs = require('node:fs') 5 | const { promises: fsp } = fs 6 | const handlebars = require('handlebars') 7 | const merge = require('merge-stream') 8 | const ospath = require('node:path') 9 | const path = ospath.posix 10 | const requireFromString = require('require-from-string') 11 | const { Transform } = require('node:stream') 12 | const map = (transform = () => {}, flush = undefined) => new Transform({ objectMode: true, transform, flush }) 13 | const vfs = require('vinyl-fs') 14 | const yaml = require('js-yaml') 15 | 16 | const ASCIIDOC_ATTRIBUTES = { experimental: '', icons: 'font', sectanchors: '', 'source-highlighter': 'highlight.js' } 17 | 18 | module.exports = 19 | (src, previewSrc, previewDest, sink = () => map()) => 20 | (done) => 21 | Promise.all([ 22 | loadSampleUiModel(previewSrc), 23 | toPromise( 24 | merge(compileLayouts(src), registerPartials(src), registerHelpers(src), copyImages(previewSrc, previewDest)) 25 | ), 26 | ]) 27 | .then(([baseUiModel, { layouts }]) => [{ ...baseUiModel, env: process.env }, layouts]) 28 | .then(([baseUiModel, layouts]) => 29 | vfs 30 | .src('**/*.adoc', { base: previewSrc, cwd: previewSrc }) 31 | .pipe( 32 | map((file, enc, next) => { 33 | const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc)) 34 | const uiModel = { ...baseUiModel } 35 | const url = uiModel.env.DEPLOY_PRIME_URL || uiModel.env.URL 36 | if (url) uiModel.site.url = url 37 | uiModel.page = { ...uiModel.page } 38 | uiModel.siteRootPath = siteRootPath 39 | uiModel.uiRootPath = path.join(siteRootPath, '_') 40 | if (file.stem === '404') { 41 | uiModel.page = { layout: '404', title: 'Page Not Found' } 42 | } else { 43 | const doc = Asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES }) 44 | uiModel.page.attributes = Object.entries(doc.getAttributes()) 45 | .filter(([name, val]) => name.startsWith('page-')) 46 | .reduce((accum, [name, val]) => { 47 | accum[name.slice(5)] = val 48 | return accum 49 | }, {}) 50 | uiModel.page.layout = doc.getAttribute('page-layout', 'default') 51 | if (doc.hasAttribute('docrole')) uiModel.page.role = doc.getAttribute('docrole') 52 | uiModel.page.title = doc.getDocumentTitle() 53 | uiModel.page.contents = Buffer.from(doc.convert()) 54 | } 55 | file.extname = '.html' 56 | try { 57 | file.contents = Buffer.from(layouts.get(uiModel.page.layout)(uiModel)) 58 | next(null, file) 59 | } catch (e) { 60 | next(transformHandlebarsError(e, uiModel.page.layout)) 61 | } 62 | }) 63 | ) 64 | .pipe(vfs.dest(previewDest)) 65 | .on('error', done) 66 | .pipe(sink()) 67 | ) 68 | 69 | function loadSampleUiModel (src) { 70 | return fsp.readFile(ospath.join(src, 'ui-model.yml'), 'utf8').then((contents) => yaml.load(contents)) 71 | } 72 | 73 | function registerPartials (src) { 74 | return vfs.src('partials/*.hbs', { base: src, cwd: src }).pipe( 75 | map((file, enc, next) => { 76 | handlebars.registerPartial(file.stem, file.contents.toString()) 77 | next() 78 | }) 79 | ) 80 | } 81 | 82 | function registerHelpers (src) { 83 | handlebars.registerHelper('relativize', relativize) 84 | handlebars.registerHelper('resolvePage', resolvePage) 85 | handlebars.registerHelper('resolvePageURL', resolvePageURL) 86 | return vfs.src('helpers/*.js', { base: src, cwd: src }).pipe( 87 | map((file, enc, next) => { 88 | handlebars.registerHelper(file.stem, requireFromString(file.contents.toString())) 89 | next() 90 | }) 91 | ) 92 | } 93 | 94 | function compileLayouts (src) { 95 | const layouts = new Map() 96 | return vfs.src('layouts/*.hbs', { base: src, cwd: src }).pipe( 97 | map( 98 | (file, enc, next) => { 99 | const srcName = path.join(src, file.relative) 100 | layouts.set(file.stem, handlebars.compile(file.contents.toString(), { preventIndent: true, srcName })) 101 | next() 102 | }, 103 | function (done) { 104 | this.push({ layouts }) 105 | done() 106 | } 107 | ) 108 | ) 109 | } 110 | 111 | function copyImages (src, dest) { 112 | return vfs 113 | .src('**/*.{png,svg}', { base: src, cwd: src }) 114 | .pipe(vfs.dest(dest)) 115 | .pipe(map((file, enc, next) => next())) 116 | } 117 | 118 | function relativize (to, { data: { root } }) { 119 | if (!to) return '#' 120 | if (to.charAt() !== '/') return to 121 | const from = root.page.url 122 | if (!from) return (root.site.path || '') + to 123 | let hash = '' 124 | const hashIdx = to.indexOf('#') 125 | if (~hashIdx) { 126 | hash = to.slice(hashIdx) 127 | to = to.slice(0, hashIdx) 128 | } 129 | if (to === from) return hash || (to.charAt(to.length - 1) === '/' ? './' : path.basename(to)) 130 | const rel = path.relative(path.dirname(from + '.'), to) 131 | const toDir = to.charAt(to.length - 1) === '/' 132 | return rel ? (toDir ? rel + '/' : rel) + hash : (toDir ? './' : '../' + path.basename(to)) + hash 133 | } 134 | 135 | function resolvePage (spec, context = {}) { 136 | if (spec) return { pub: { url: resolvePageURL(spec) } } 137 | } 138 | 139 | function resolvePageURL (spec, context = {}) { 140 | if (spec) return '/' + (spec = spec.split(':').pop()).slice(0, spec.lastIndexOf('.')) + '.html' 141 | } 142 | 143 | function transformHandlebarsError ({ message, stack }, layout) { 144 | const m = stack.match(/^ *at Object\.ret \[as (.+?)\]/m) 145 | const templatePath = `src/${m ? 'partials/' + m[1] : 'layouts/' + layout}.hbs` 146 | const err = new Error(`${message}${~message.indexOf('\n') ? '\n^ ' : ' '}in UI template ${templatePath}`) 147 | err.stack = [err.toString()].concat(stack.slice(message.length + 8)).join('\n') 148 | return err 149 | } 150 | 151 | function toPromise (stream) { 152 | return new Promise((resolve, reject, data = {}) => 153 | stream 154 | .on('error', reject) 155 | .on('data', (chunk) => chunk.constructor === Object && Object.assign(data, chunk)) 156 | .on('finish', () => resolve(data)) 157 | ) 158 | } 159 | -------------------------------------------------------------------------------- /gulp.d/tasks/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const autoprefixer = require('autoprefixer') 4 | const browserify = require('browserify') 5 | const concat = require('gulp-concat') 6 | const cssnano = require('cssnano') 7 | const fs = require('node:fs') 8 | const { promises: fsp } = fs 9 | const imagemin = require('gulp-imagemin') 10 | const merge = require('merge-stream') 11 | const ospath = require('node:path') 12 | const path = ospath.posix 13 | const postcss = require('gulp-postcss') 14 | const postcssCalc = require('postcss-calc') 15 | const postcssImport = require('postcss-import') 16 | const postcssUrl = require('postcss-url') 17 | const postcssVars = require('postcss-custom-properties') 18 | const { Transform } = require('node:stream') 19 | const map = (transform) => new Transform({ objectMode: true, transform }) 20 | const through = () => map((file, enc, next) => next(null, file)) 21 | const uglify = require('gulp-uglify') 22 | const vfs = require('vinyl-fs') 23 | 24 | module.exports = (src, dest, preview) => () => { 25 | const opts = { base: src, cwd: src } 26 | const sourcemaps = preview || process.env.SOURCEMAPS === 'true' 27 | const postcssPlugins = [ 28 | postcssImport, 29 | (css, { messages, opts: { file } }) => 30 | Promise.all( 31 | messages 32 | .reduce((accum, { file: depPath, type }) => (type === 'dependency' ? accum.concat(depPath) : accum), []) 33 | .map((importedPath) => fsp.stat(importedPath).then(({ mtime }) => mtime)) 34 | ).then((mtimes) => { 35 | const newestMtime = mtimes.reduce((max, curr) => (!max || curr > max ? curr : max), file.stat.mtime) 36 | if (newestMtime > file.stat.mtime) file.stat.mtimeMs = +(file.stat.mtime = newestMtime) 37 | }), 38 | postcssUrl([ 39 | { 40 | filter: (asset) => new RegExp('^[~][^/]*(?:font|typeface)[^/]*/.*/files/.+[.](?:ttf|woff2?)$').test(asset.url), 41 | url: (asset) => { 42 | const relpath = asset.pathname.slice(1) 43 | const abspath = require.resolve(relpath) 44 | const basename = ospath.basename(abspath) 45 | const destpath = ospath.join(dest, 'font', basename) 46 | if (!fs.existsSync(destpath)) fs.cpSync(abspath, destpath, { recursive: true }) 47 | return path.join('..', 'font', basename) 48 | }, 49 | }, 50 | ]), 51 | // NOTE importFrom makes vars available to all top-level stylesheets without having to redeclare the variables 52 | // use preserve: false to resolve var() declarations (this option is broken for postcss-custom-properties >= 12.0) 53 | postcssVars({ disableDeprecationNotice: true, importFrom: path.join(src, 'css', 'vars.css'), preserve: true }), 54 | preview ? postcssCalc : () => {}, 55 | autoprefixer, 56 | preview 57 | ? () => {} 58 | : (css, result) => 59 | cssnano() 60 | .process(css, result.opts) 61 | .then(() => postcssPseudoElementFixer(css, result)), 62 | ] 63 | 64 | return merge( 65 | vfs.src('ui.yml', { ...opts, allowEmpty: true }), 66 | vfs 67 | .src('js/+([0-9])-*.js', { ...opts, read: false, sourcemaps }) 68 | .pipe(bundle(opts)) 69 | .pipe(uglify({ output: { comments: /^! / } })) 70 | // NOTE concat already uses stat from newest combined file 71 | .pipe(concat('js/site.js')), 72 | vfs 73 | .src('js/vendor/+([^.])?(.bundle).js', { ...opts, read: false }) 74 | .pipe(bundle(opts)) 75 | .pipe(uglify({ output: { comments: /^! / } })), 76 | vfs 77 | .src('js/vendor/*.min.js', opts) 78 | .pipe(map((file, enc, next) => next(null, Object.assign(file, { extname: '' }, { extname: '.js' })))), 79 | // NOTE use the next line to bundle a JavaScript library that cannot be browserified, like jQuery 80 | //vfs.src(require.resolve(''), opts).pipe(concat('js/vendor/.js')), 81 | vfs 82 | .src(['css/site.css', 'css/home.css', 'css/vendor/*.css'], { ...opts, sourcemaps }) 83 | .pipe(postcss((file) => ({ plugins: postcssPlugins, options: { file } }))), 84 | vfs.src('font/*.{ttf,woff*(2)}', opts), 85 | vfs.src('img/**/*.{gif,ico,jpg,png,svg}', opts).pipe( 86 | preview 87 | ? through() 88 | : imagemin( 89 | [ 90 | imagemin.gifsicle(), 91 | imagemin.mozjpeg(), 92 | imagemin.optipng(), 93 | imagemin.svgo({ 94 | plugins: [ 95 | { cleanupIDs: { preservePrefixes: ['icon-', 'view-'] } }, 96 | { removeViewBox: false }, 97 | { removeDesc: false }, 98 | ], 99 | }), 100 | ].reduce((accum, it) => (it ? accum.concat(it) : accum), []) 101 | ) 102 | ), 103 | vfs.src('helpers/*.js', opts), 104 | vfs.src('layouts/*.hbs', opts), 105 | vfs.src('partials/*.hbs', opts), 106 | vfs.src('static/**/*[!~]', { ...opts, base: ospath.join(src, 'static'), dot: true }) 107 | ).pipe(vfs.dest(dest, { sourcemaps: sourcemaps && '.' })) 108 | } 109 | 110 | function bundle ({ base: basedir, ext: bundleExt = '.bundle.js' }) { 111 | return map((file, enc, next) => { 112 | if (bundleExt && file.relative.endsWith(bundleExt)) { 113 | const mtimePromises = [] 114 | const bundlePath = file.path 115 | browserify(file.relative, { basedir, detectGlobals: false }) 116 | .plugin('browser-pack-flat/plugin') 117 | .on('file', (bundledPath) => { 118 | if (bundledPath !== bundlePath) mtimePromises.push(fsp.stat(bundledPath).then(({ mtime }) => mtime)) 119 | }) 120 | .bundle((bundleError, bundleBuffer) => 121 | Promise.all(mtimePromises).then((mtimes) => { 122 | const newestMtime = mtimes.reduce((max, curr) => (curr > max ? curr : max), file.stat.mtime) 123 | if (newestMtime > file.stat.mtime) file.stat.mtimeMs = +(file.stat.mtime = newestMtime) 124 | if (bundleBuffer !== undefined) file.contents = bundleBuffer 125 | next(bundleError, Object.assign(file, { path: file.path.slice(0, file.path.length - 10) + '.js' })) 126 | }) 127 | ) 128 | return 129 | } 130 | fsp.readFile(file.path, 'UTF-8').then((contents) => { 131 | next(null, Object.assign(file, { contents: Buffer.from(contents) })) 132 | }) 133 | }) 134 | } 135 | 136 | function postcssPseudoElementFixer (css, result) { 137 | css.walkRules(/(?:^|[^:]):(?:before|after)/, (rule) => { 138 | rule.selector = rule.selectors.map((it) => it.replace(/(^|[^:]):(before|after)$/, '$1::$2')).join(',') 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /gulp.d/tasks/format.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const prettier = require('../lib/gulp-prettier-eslint') 4 | const vfs = require('vinyl-fs') 5 | 6 | module.exports = (files) => () => 7 | vfs 8 | .src(files) 9 | .pipe(prettier()) 10 | .pipe(vfs.dest((file) => file.base)) 11 | -------------------------------------------------------------------------------- /gulp.d/tasks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const camelCase = (name) => name.replace(/[-]./g, (m) => m.slice(1).toUpperCase()) 4 | 5 | module.exports = require('require-directory')(module, __dirname, { recurse: false, rename: camelCase }) 6 | -------------------------------------------------------------------------------- /gulp.d/tasks/lint-css.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stylelint = require('gulp-stylelint') 4 | const vfs = require('vinyl-fs') 5 | 6 | module.exports = (files) => (done) => 7 | vfs 8 | .src(files) 9 | .pipe(stylelint({ reporters: [{ formatter: 'string', console: true }], failAfterError: true })) 10 | .on('error', done) 11 | -------------------------------------------------------------------------------- /gulp.d/tasks/lint-js.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const eslint = require('gulp-eslint') 4 | const vfs = require('vinyl-fs') 5 | 6 | module.exports = (files) => (done) => 7 | vfs.src(files).pipe(eslint()).pipe(eslint.format()).pipe(eslint.failAfterError()).on('error', done) 8 | -------------------------------------------------------------------------------- /gulp.d/tasks/pack.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ospath = require('node:path') 4 | const vfs = require('vinyl-fs') 5 | const zip = require('@vscode/gulp-vinyl-zip') 6 | 7 | module.exports = (src, dest, bundleName, onFinish) => () => 8 | vfs 9 | .src('**/*', { base: src, cwd: src, dot: true }) 10 | .pipe(zip.dest(ospath.join(dest, `${bundleName}-bundle.zip`))) 11 | .on('finish', () => onFinish && onFinish(ospath.resolve(dest, `${bundleName}-bundle.zip`))) 12 | -------------------------------------------------------------------------------- /gulp.d/tasks/release.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const File = require('vinyl') 4 | const fs = require('node:fs') 5 | const { promises: fsp } = fs 6 | const { Octokit } = require('@octokit/rest') 7 | const ospath = require('node:path') 8 | const { pipeline, Transform, Writable } = require('node:stream') 9 | const forEach = (write, final) => new Writable({ objectMode: true, write, final }) 10 | const map = (transform, flush = undefined) => new Transform({ objectMode: true, transform, flush }) 11 | const vfs = require('vinyl-fs') 12 | const zip = require('@vscode/gulp-vinyl-zip') 13 | 14 | function getNextReleaseNumber ({ octokit, owner, repo, tagPrefix, latestTagName }) { 15 | const filter = ({ name }) => name !== latestTagName && name.startsWith(tagPrefix) 16 | return collectReleases({ octokit, owner, repo, filter }).then((releases) => { 17 | if (releases.length) { 18 | releases.sort((a, b) => -1 * a.name.localeCompare(b.name, 'en', { numeric: true })) 19 | const latestName = releases[0].name 20 | return Number(latestName.slice(tagPrefix.length)) + 1 21 | } else { 22 | return 1 23 | } 24 | }) 25 | } 26 | 27 | function collectReleases ({ octokit, owner, repo, filter, page = 1, accum = [] }) { 28 | return octokit.repos.listReleases({ owner, repo, page, per_page: 100 }).then((result) => { 29 | const releases = result.data.filter(filter) 30 | const links = result.headers.link 31 | if (links && links.includes('; rel="next"')) { 32 | return collectReleases({ octokit, owner, repo, filter, page: page + 1, accum: accum.concat(releases) }) 33 | } else { 34 | return accum.concat(releases) 35 | } 36 | }) 37 | } 38 | 39 | function versionBundle (bundleFile, tagName) { 40 | let uiDescriptorFound 41 | return new Promise((resolve, reject) => 42 | vfs 43 | .src(bundleFile) 44 | .pipe(zip.src().on('error', reject)) 45 | .pipe( 46 | map( 47 | (file, _, next) => { 48 | if (file.path === 'ui.yml' && (uiDescriptorFound = true) && file.isStream()) { 49 | const buffer = [] 50 | pipeline( 51 | file.contents, 52 | forEach((chunk, _, done) => buffer.push(chunk) && done()), 53 | (err) => (err ? next(err) : next(null, addVersionEntry(file, tagName, Buffer.concat(buffer)))) 54 | ) 55 | } else { 56 | next(null, file) 57 | } 58 | }, 59 | function (done) { 60 | if (!uiDescriptorFound) this.push(addVersionEntry(new File({ path: 'ui.yml' }), tagName)) 61 | done() 62 | } 63 | ) 64 | ) 65 | .pipe(zip.dest(bundleFile)) 66 | .on('finish', () => resolve(bundleFile)) 67 | ) 68 | } 69 | 70 | function addVersionEntry (file, tagName, contents = Buffer.alloc(0)) { 71 | let versionEntry = `version: ${tagName}\n` 72 | if (contents.length && contents[contents.length - 1] !== 10) versionEntry = `\n${versionEntry}` 73 | file.contents = Buffer.concat([contents, Buffer.from(versionEntry)]) 74 | return file 75 | } 76 | 77 | module.exports = (dest, bundleName, owner, repo, ref, token, updateBranch, latestAlias) => async () => { 78 | const octokit = new Octokit({ auth: `token ${token}` }) 79 | let variant = ref ? ref.replace(/^refs\/heads\//, '') : 'main' 80 | if (variant === 'main') variant = 'prod' 81 | ref = ref.replace(/^refs\//, '') 82 | const tagPrefix = `${variant}-` 83 | const latestTagName = latestAlias === false ? undefined : `${tagPrefix}${latestAlias || 'latest'}` 84 | const tagName = `${tagPrefix}${await getNextReleaseNumber({ octokit, owner, repo, tagPrefix, latestTagName })}` 85 | const message = `Release ${tagName}` 86 | const bundleFileBasename = `${bundleName}-bundle.zip` 87 | const bundleFile = await versionBundle(ospath.join(dest, bundleFileBasename), tagName) 88 | let commit = await octokit.git.getRef({ owner, repo, ref }).then((result) => result.data.object.sha) 89 | const readmeContent = await fsp 90 | .readFile('README.adoc', 'utf-8') 91 | .then((contents) => contents.replace(/^(?:\/\/)?(:current-release: ).+$/m, `$1${tagName}`)) 92 | const readmeBlob = await octokit.git 93 | .createBlob({ owner, repo, content: readmeContent, encoding: 'utf-8' }) 94 | .then((result) => result.data.sha) 95 | let tree = await octokit.git.getCommit({ owner, repo, commit_sha: commit }).then((result) => result.data.tree.sha) 96 | tree = await octokit.git 97 | .createTree({ 98 | owner, 99 | repo, 100 | tree: [{ path: 'README.adoc', mode: '100644', type: 'blob', sha: readmeBlob }], 101 | base_tree: tree, 102 | }) 103 | .then((result) => result.data.sha) 104 | commit = await octokit.git 105 | .createCommit({ owner, repo, message, tree, parents: [commit] }) 106 | .then((result) => result.data.sha) 107 | if (updateBranch) await octokit.git.updateRef({ owner, repo, ref, sha: commit }) 108 | if (latestTagName) { 109 | await octokit.repos.getReleaseByTag({ owner, repo, tag: latestTagName }).then( 110 | (result) => 111 | octokit.repos 112 | .deleteRelease({ owner, repo, release_id: result.data.id }) 113 | .then(() => octokit.git.deleteRef({ owner, repo, ref: `tags/${latestTagName}` }).catch(() => undefined)), 114 | () => undefined 115 | ) 116 | } 117 | for (const tag of latestTagName ? [tagName, latestTagName] : [tagName]) { 118 | if (tag !== tagName) await new Promise((resolve) => setTimeout(resolve, 1000)) 119 | const isLatest = tag === tagName ? 'true' : 'false' 120 | const uploadUrl = await octokit.repos 121 | .createRelease({ owner, repo, tag_name: tag, target_commitish: commit, name: tag, make_latest: isLatest }) 122 | .then((result) => result.data.upload_url) 123 | await octokit.repos.uploadReleaseAsset({ 124 | url: uploadUrl, 125 | data: fs.createReadStream(bundleFile), 126 | name: bundleFileBasename, 127 | headers: { 'content-length': (await fsp.stat(bundleFile)).size, 'content-type': 'application/zip' }, 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /gulp.d/tasks/remove.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('node:fs') 4 | const { Transform } = require('node:stream') 5 | const map = (transform) => new Transform({ objectMode: true, transform }) 6 | const vfs = require('vinyl-fs') 7 | 8 | module.exports = (files) => () => 9 | vfs.src(files, { allowEmpty: true }).pipe(map((file, enc, next) => fs.rm(file.path, { recursive: true }, next))) 10 | -------------------------------------------------------------------------------- /gulp.d/tasks/serve.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const connect = require('gulp-connect') 4 | const os = require('node:os') 5 | 6 | const ANY_HOST = '0.0.0.0' 7 | const URL_RX = /(https?):\/\/(?:[^/: ]+)(:\d+)?/ 8 | 9 | module.exports = 10 | (root, opts = {}, watch = undefined) => 11 | (done) => { 12 | connect.server({ ...opts, middleware: opts.host === ANY_HOST ? decorateLog : undefined, root }, function () { 13 | this.server.on('close', done) 14 | if (watch) watch() 15 | }) 16 | } 17 | 18 | function decorateLog (_, app) { 19 | const _log = app.log 20 | app.log = (msg) => { 21 | if (msg.startsWith('Server started ')) { 22 | const localIp = getLocalIp() 23 | const replacement = '$1://localhost$2' + (localIp ? ` and $1://${localIp}$2` : '') 24 | msg = msg.replace(URL_RX, replacement) 25 | } 26 | _log(msg) 27 | } 28 | return [] 29 | } 30 | 31 | function getLocalIp () { 32 | for (const records of Object.values(os.networkInterfaces())) { 33 | for (const record of records) { 34 | if (!record.internal && record.family === 'IPv4') return record.address 35 | } 36 | } 37 | return 'localhost' 38 | } 39 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const pkg = require('./package.json') 4 | const [owner, repo] = new URL(pkg.repository.url).pathname.slice(1).split('/') 5 | 6 | const { parallel, series, watch } = require('gulp') 7 | const createTask = require('./gulp.d/lib/create-task') 8 | const exportTasks = require('./gulp.d/lib/export-tasks') 9 | const log = require('fancy-log') 10 | 11 | const bundleName = 'ui' 12 | const buildDir = ['deploy-preview', 'branch-deploy'].includes(process.env.CONTEXT) ? 'public/dist' : 'build' 13 | const previewSrcDir = 'preview-src' 14 | const previewDestDir = 'public' 15 | const srcDir = 'src' 16 | const destDir = `${previewDestDir}/_` 17 | const { reload: livereload } = process.env.LIVERELOAD === 'true' ? require('gulp-connect') : {} 18 | const serverConfig = { host: '0.0.0.0', port: 5252, livereload } 19 | 20 | const task = require('./gulp.d/tasks') 21 | const glob = { 22 | all: [srcDir, previewSrcDir], 23 | css: `${srcDir}/css/**/*.css`, 24 | js: ['gulpfile.js', 'gulp.d/**/*.js', `${srcDir}/helpers/*.js`, `${srcDir}/js/**/+([^.])?(.bundle).js`], 25 | } 26 | 27 | const cleanTask = createTask({ 28 | name: 'clean', 29 | desc: 'Clean files and folders generated by build', 30 | call: task.remove(['build', 'public']), 31 | }) 32 | 33 | const lintCssTask = createTask({ 34 | name: 'lint:css', 35 | desc: 'Lint the CSS source files using stylelint (standard config)', 36 | call: task.lintCss(glob.css), 37 | }) 38 | 39 | const lintJsTask = createTask({ 40 | name: 'lint:js', 41 | desc: 'Lint the JavaScript source files using eslint (JavaScript Standard Style)', 42 | call: task.lintJs(glob.js), 43 | }) 44 | 45 | const lintTask = createTask({ 46 | name: 'lint', 47 | desc: 'Lint the CSS and JavaScript source files', 48 | call: parallel(lintCssTask, lintJsTask), 49 | }) 50 | 51 | const formatTask = createTask({ 52 | name: 'format', 53 | desc: 'Format the JavaScript source files using prettify (JavaScript Standard Style)', 54 | call: task.format(glob.js), 55 | }) 56 | 57 | const buildTask = createTask({ 58 | name: 'build', 59 | desc: 'Build and stage the UI assets for bundling', 60 | call: task.build( 61 | srcDir, 62 | destDir, 63 | process.argv.slice(2).some((name) => name.startsWith('preview')) 64 | ), 65 | }) 66 | 67 | const bundleBuildTask = createTask({ 68 | name: 'bundle:build', 69 | call: series(cleanTask, lintTask, buildTask), 70 | }) 71 | 72 | const bundlePackTask = createTask({ 73 | name: 'bundle:pack', 74 | desc: 'Create a bundle of the staged UI assets for publishing', 75 | call: task.pack( 76 | destDir, 77 | buildDir, 78 | bundleName, 79 | (bundlePath) => !process.env.CI && log(`Antora option: --ui-bundle-url=${bundlePath}`) 80 | ), 81 | }) 82 | 83 | const bundleTask = createTask({ 84 | name: 'bundle', 85 | desc: 'Clean, lint, build, and bundle the UI for publishing', 86 | call: series(bundleBuildTask, bundlePackTask), 87 | }) 88 | 89 | const packTask = createTask({ 90 | name: 'pack', 91 | desc: '(deprecated; use bundle instead)', 92 | call: series(bundleTask), 93 | }) 94 | 95 | const releasePublishTask = createTask({ 96 | name: 'release:publish', 97 | call: task.release(buildDir, bundleName, owner, repo, process.env.GITHUB_REF, process.env.GITHUB_API_TOKEN, true), 98 | }) 99 | 100 | const releaseTask = createTask({ 101 | name: 'release', 102 | desc: 'Bundle the UI and publish it to GitHub by attaching it to a new tag', 103 | call: series(bundleTask, releasePublishTask), 104 | }) 105 | 106 | const buildPreviewPagesTask = createTask({ 107 | name: 'preview:build-pages', 108 | call: task.buildPreviewPages(srcDir, previewSrcDir, previewDestDir, livereload), 109 | }) 110 | 111 | const previewBuildTask = createTask({ 112 | name: 'preview:build', 113 | desc: 'Process and stage the UI assets and generate pages for the preview', 114 | call: parallel(buildTask, buildPreviewPagesTask), 115 | }) 116 | 117 | const previewServeTask = createTask({ 118 | name: 'preview:serve', 119 | call: task.serve(previewDestDir, serverConfig, () => watch(glob.all, previewBuildTask)), 120 | }) 121 | 122 | const previewTask = createTask({ 123 | name: 'preview', 124 | desc: 'Generate a preview site and launch a server to view it', 125 | call: series(previewBuildTask, previewServeTask), 126 | }) 127 | 128 | module.exports = exportTasks( 129 | bundleTask, 130 | cleanTask, 131 | lintTask, 132 | formatTask, 133 | buildTask, 134 | bundleTask, 135 | bundlePackTask, 136 | releaseTask, 137 | previewTask, 138 | previewBuildTask, 139 | packTask 140 | ) 141 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This placeholder script allows this package to be discovered using require.resolve. 4 | // It may be used in the future to export information about the files in this UI. 5 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npx gulp preview:build" 4 | 5 | [build.environment] 6 | CI = "true" 7 | NODE_VERSION = "16" 8 | 9 | [context.deploy-preview] 10 | command = "npx gulp preview:build && npx gulp --series build bundle:pack" 11 | 12 | [[headers]] 13 | for = "/_/font/*" 14 | [headers.values] 15 | Cache-Control = "public,max-age=604800" 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asciidoctor-docs-ui", 3 | "description": "Produces the UI bundle for docs.asciidoctor.org", 4 | "homepage": "https://docs.asciidoctor.org", 5 | "license": "MPL-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/asciidoctor/asciidoctor-docs-ui" 9 | }, 10 | "scripts": { 11 | "start": "gulp preview" 12 | }, 13 | "engines": { 14 | "node": ">=16.0.0" 15 | }, 16 | "browserslist": [ 17 | "defaults" 18 | ], 19 | "devDependencies": { 20 | "@asciidoctor/core": "~2.2", 21 | "@asciidoctor/tabs": "1.0.0-beta.6", 22 | "@fontsource/comfortaa": "~5.0", 23 | "@fontsource/roboto": "~5.0", 24 | "@fontsource/roboto-mono": "~5.0", 25 | "@octokit/rest": "~19.0", 26 | "@vscode/gulp-vinyl-zip": "~2.5", 27 | "autoprefixer": "~10.4", 28 | "browser-pack-flat": "~3.5", 29 | "browserify": "~17.0", 30 | "core-js": "~3.33", 31 | "cssnano": "~6.0", 32 | "docsearch.js": "~2.6", 33 | "eslint": "~6.8", 34 | "eslint-config-standard": "~14.1", 35 | "eslint-plugin-import": "~2.22", 36 | "eslint-plugin-node": "~11.1", 37 | "eslint-plugin-promise": "~4.2", 38 | "eslint-plugin-standard": "~4.0", 39 | "fancy-log": "~2.0", 40 | "gulp": "~4.0", 41 | "gulp-concat": "~2.6", 42 | "gulp-connect": "~5.7", 43 | "gulp-eslint": "~6.0", 44 | "gulp-imagemin": "~7.1", 45 | "gulp-postcss": "~9.0", 46 | "gulp-stylelint": "~13.0", 47 | "gulp-uglify": "~3.0", 48 | "handlebars": "~4.7", 49 | "highlight.js": "9.18.3", 50 | "js-yaml": "~4.1", 51 | "merge-stream": "~2.0", 52 | "postcss": "~8.4", 53 | "postcss-calc": "~9.0", 54 | "postcss-custom-properties": "~11.0", 55 | "postcss-import": "~15.1", 56 | "postcss-url": "~10.1", 57 | "prettier-eslint": "~12.0", 58 | "require-directory": "~2.1", 59 | "require-from-string": "~2.0", 60 | "stylelint": "~13.13", 61 | "stylelint-config-standard": "~22.0", 62 | "vinyl-fs": "~3.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /preview-src/404.adoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-docs-ui/9a521169424222b109a33ae21b30c39878350327/preview-src/404.adoc -------------------------------------------------------------------------------- /preview-src/home.adoc: -------------------------------------------------------------------------------- 1 | = Home Page Title 2 | :navtitle: Home 3 | :page-role: home 4 | 5 | Welcome to the home page! 6 | 7 | [.panel] 8 | -- 9 | [discrete] 10 | == xref:home.adoc[First hero panel] 11 | 12 | Content for hero panel. 13 | -- 14 | 15 | [.panel] 16 | -- 17 | [discrete] 18 | == xref:home.adoc[Another hero panel] 19 | 20 | Content for hero panel. 21 | -- 22 | 23 | [discrete] 24 | === Things 25 | 26 | Introduction of things. 27 | 28 | [.grid.has-emblems] 29 | Thing A [.emblem]#Emblem#:: 30 | Description of thing A. 31 | 32 | Thing B:: 33 | Description of thing B. 34 | 35 | Thing C:: 36 | Description of thing C. 37 | 38 | [discrete] 39 | === Explore topics 40 | 41 | [.grid] 42 | Topic A:: 43 | Description of topic A. 44 | 45 | * xref:#[Quicklink 1] 46 | * xref:#[Quicklink 2] 47 | * xref:#[Quicklink 3] 48 | 49 | Topic B:: 50 | Description of topic B. 51 | 52 | Topic C:: 53 | Description of topic C. 54 | 55 | Topic D:: 56 | Description of topic D. 57 | -------------------------------------------------------------------------------- /preview-src/index.adoc: -------------------------------------------------------------------------------- 1 | = Hardware and Software Requirements 2 | Author Name 3 | :idprefix: 4 | :idseparator: - 5 | :!example-caption: 6 | :!table-caption: 7 | :page-pagination: 8 | :page-component-order: *, abc 9 | 10 | [.float-group] 11 | -- 12 | image:multirepo-ssg.svg[Multirepo SSG,180,135,float=right,role=float-gap] 13 | Platonem complectitur mediocritatem ea eos. 14 | Ei nonumy deseruisse ius. 15 | Mel id omnes verear. 16 | Vis no velit audiam, sonet <> eum ne. 17 | *Prompta eripuit* nec ad. 18 | Integer diam enim, dignissim eget eros et, ultricies mattis odio. 19 | -- 20 | 21 | Vestibulum consectetur nec urna a luctus. 22 | Quisque pharetra tristique arcu fringilla dapibus. 23 | https://example.org[Curabitur,role=unresolved] ut massa aliquam, cursus enim et, accumsan lectus. 24 | Mauris eget leo nunc, nec tempus mi? Curabitur id nisl mi, ut vulputate urna. 25 | 26 | == Cu solet 27 | 28 | Nominavi luptatum eos, an vim hinc philosophia intellegebat. 29 | Lorem pertinacia `expetenda` et nec, [.underline]#wisi# illud [.line-through]#sonet# qui ea. 30 | H~2~0. 31 | E = mc^2^. 32 | [.big]#Eum# an [.small]#doctus# <>. 33 | Eu mea inani iriure.footnote:[Quisque porta facilisis tortor, vitae bibendum velit fringilla vitae! Lorem ipsum dolor sit amet, consectetur adipiscing elit.] 34 | 35 | [,json] 36 | ---- 37 | { 38 | "name": "module-name", 39 | "version": "10.0.1", 40 | "description": "An example module to illustrate the usage of package.json", 41 | "author": "Author Name ", 42 | "scripts": { 43 | "test": "mocha", 44 | "lint": "eslint" 45 | } 46 | } 47 | ---- 48 | 49 | .Example paragraph syntax 50 | [,asciidoc] 51 | ---- 52 | .Optional title 53 | [example] 54 | This is an example paragraph. 55 | ---- 56 | 57 | .Optional title 58 | [example] 59 | This is an example paragraph. 60 | 61 | .Summary *Spoiler Alert!* 62 | [%collapsible] 63 | ==== 64 | Details. 65 | 66 | Loads of details. 67 | ==== 68 | 69 | [,asciidoc] 70 | ---- 71 | Voila! 72 | ---- 73 | 74 | .Result 75 | [%collapsible.result] 76 | ==== 77 | Voila! 78 | ==== 79 | 80 | === Some Code 81 | 82 | How about some code? 83 | 84 | [,js] 85 | ---- 86 | vfs 87 | .src('js/vendor/*.js', { cwd: 'src', cwdbase: true, read: false }) 88 | .pipe(tap((file) => { // <.> 89 | file.contents = browserify(file.relative, { basedir: 'src', detectGlobals: false }).bundle() 90 | })) 91 | .pipe(buffer()) // <.> 92 | .pipe(uglify()) 93 | .pipe(gulp.dest('build')) 94 | ---- 95 | <.> The `tap` function is used to wiretap the data in the pipe. 96 | <.> Wrap each streaming file in a buffer so the files can be processed by uglify. 97 | Uglify can only work with buffers, not streams. 98 | 99 | Execute these commands to validate and build your site: 100 | 101 | $ podman run -v $PWD:/antora:Z --rm -t antora/antora \ 102 | version 103 | 3.0.0 104 | $ podman run -v $PWD:/antora:Z --rm -it antora/antora \ 105 | --clean \ 106 | antora-playbook.yml 107 | 108 | Cum dicat #putant# ne. 109 | Est in <> homero principes, meis deleniti mediocrem ad has. 110 | Altera atomorum his ex, has cu elitr melius propriae. 111 | Eos suscipit scaevola at. 112 | 113 | .... 114 | pom.xml 115 | src/ 116 | main/ 117 | java/ 118 | HelloWorld.java 119 | test/ 120 | java/ 121 | HelloWorldTest.java 122 | .... 123 | 124 | Eu mea munere vituperata constituam. 125 | 126 | [%autowidth] 127 | |=== 128 | |Input | Output | Example 129 | 130 | m|"foo\nbar\nbaz" 131 | l|foo 132 | bar 133 | baz 134 | a| 135 | [,ruby] 136 | ---- 137 | puts "foo\nbar\nbaz" 138 | ---- 139 | |=== 140 | 141 | Select menu:File[Open Project] to open the project in your IDE. 142 | Per ea btn:[Cancel] inimicus. 143 | Ferri kbd:[F11] tacimates constituam sed ex, eu mea munere vituperata kbd:[Ctrl,T] constituam. 144 | 145 | .Sidebar Title 146 | **** 147 | Platonem complectitur mediocritatem ea eos. 148 | Ei nonumy deseruisse ius. 149 | Mel id omnes verear. 150 | 151 | Altera atomorum his ex, has cu elitr melius propriae. 152 | Eos suscipit scaevola at. 153 | **** 154 | 155 | [.rolename] 156 | === Liber recusabo 157 | 158 | No sea, at invenire voluptaria mnesarchum has. 159 | Ex nam suas nemore dignissim, vel apeirian democritum et. 160 | At ornatus splendide sed, phaedrum omittantur usu an, vix an noster voluptatibus. 161 | 162 | [upperalpha] 163 | . potenti donec cubilia tincidunt 164 | . etiam pulvinar inceptos velit quisque aptent himenaeos 165 | . lacus volutpat semper porttitor aliquet ornare primis nulla enim 166 | 167 | Natum facilisis theophrastus an duo. 168 | No sea, at invenire voluptaria mnesarchum has. 169 | 170 | .List with customized marker 171 | [square] 172 | * ultricies sociosqu tristique integer 173 | * lacus volutpat semper porttitor aliquet ornare primis nulla enim 174 | * etiam pulvinar inceptos velit quisque aptent himenaeos 175 | 176 | Eu sed antiopam gloriatur. 177 | Ea mea agam graeci philosophia. 178 | 179 | [circle] 180 | * circles 181 | ** circles 182 | *** and more circles! 183 | 184 | At ornatus splendide sed, phaedrum omittantur usu an, vix an noster voluptatibus. 185 | 186 | * [ ] todo 187 | * [x] done! 188 | 189 | Vis veri graeci legimus ad. 190 | 191 | sed:: 192 | splendide sed 193 | 194 | mea:: 195 | tad:: 196 | agam graeci 197 | 198 | Let's look at that another way. 199 | 200 | [horizontal] 201 | sed:: 202 | splendide sed 203 | 204 | mea:: 205 | agam graeci 206 | 207 | At ornatus splendide sed. 208 | 209 | .Library dependencies 210 | [#dependencies%autowidth%footer,stripes=hover] 211 | |=== 212 | |Library |Version 213 | 214 | |eslint 215 | |^1.7.3 216 | 217 | |eslint-config-gulp 218 | |^2.0.0 219 | 220 | |expect 221 | |^1.20.2 222 | 223 | |istanbul 224 | |^0.4.3 225 | 226 | |istanbul-coveralls 227 | |^1.0.3 228 | 229 | |jscs 230 | |^2.3.5 231 | 232 | h|Total 233 | |6 234 | |=== 235 | 236 | Cum dicat putant ne. 237 | Est in reque homero principes, meis deleniti mediocrem ad has. 238 | Altera atomorum his ex, has cu elitr melius propriae. 239 | Eos suscipit scaevola at. 240 | 241 | [TIP] 242 | This oughta do it! 243 | 244 | Cum dicat putant ne. 245 | Est in reque homero principes, meis deleniti mediocrem ad has. 246 | Altera atomorum his ex, has cu elitr melius propriae. 247 | Eos suscipit scaevola at. 248 | 249 | [NOTE] 250 | ==== 251 | You've been down _this_ road before. 252 | ==== 253 | 254 | Cum dicat putant ne. 255 | Est in reque homero principes, meis deleniti mediocrem ad has. 256 | Altera atomorum his ex, has cu elitr melius propriae. 257 | Eos suscipit scaevola at. 258 | 259 | [WARNING] 260 | ==== 261 | Watch out! 262 | ==== 263 | 264 | [CAUTION] 265 | ==== 266 | [#inline]#I wouldn't try that if I were you.# 267 | ==== 268 | 269 | [IMPORTANT] 270 | ==== 271 | Don't forget this step! 272 | ==== 273 | 274 | .Key Points to Remember 275 | [TIP] 276 | ==== 277 | If you installed the CLI and the default site generator globally, you can upgrade both of them with the same command. 278 | 279 | $ npm i -g @antora/cli @antora/site-generator-default 280 | ==== 281 | 282 | Nominavi luptatum eos, an vim hinc philosophia intellegebat. 283 | Eu mea inani iriure. 284 | 285 | [discrete] 286 | == Voluptua singulis 287 | 288 | [discrete] 289 | === Nominavi luptatum 290 | 291 | Cum dicat putant ne. 292 | Est in reque homero principes, meis deleniti mediocrem ad has. 293 | Ex nam suas nemore dignissim, vel apeirian democritum et. 294 | 295 | .Antora is a multi-repo documentation site generator 296 | image::multirepo-ssg.svg[Multirepo SSG,3000,opts=interactive] 297 | 298 | .Let's see that again, but a little smaller 299 | image::multirepo-ssg.svg[Multirepo SSG,300,role=text-left] 300 | 301 | Make the switch today! 302 | 303 | .Full Circle with Jake Blauvelt 304 | video::300817511[vimeo,640,360,align=left] 305 | 306 | [#english+中文] 307 | == English + 中文 308 | 309 | Altera atomorum his ex, has cu elitr melius propriae. 310 | Eos suscipit scaevola at. 311 | 312 | [,'Famous Person. Cum dicat putant ne.','Cum dicat putant ne. https://example.com[Famous Person Website]'] 313 | ____ 314 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 315 | Mauris eget leo nunc, nec tempus mi? Curabitur id nisl mi, ut vulputate urna. 316 | Quisque porta facilisis tortor, vitae bibendum velit fringilla vitae! 317 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 318 | Mauris eget leo nunc, nec tempus mi? Curabitur id nisl mi, ut vulputate urna. 319 | Quisque porta facilisis tortor, vitae bibendum velit fringilla vitae! 320 | ____ 321 | 322 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 323 | 324 | [verse] 325 | ____ 326 | The fog comes 327 | on little cat feet. 328 | ____ 329 | 330 | == Fin 331 | 332 | [.text-right] 333 | That's all, folks! 334 | -------------------------------------------------------------------------------- /preview-src/multirepo-ssg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /preview-src/ui-model.yml: -------------------------------------------------------------------------------- 1 | antoraVersion: '3.0.0' 2 | site: 3 | url: http://localhost:5252 4 | title: Asciidoctor Docs 5 | homeUrl: &home_url /index.html 6 | components: 7 | - name: abc 8 | title: Project ABC 9 | url: '#' 10 | versions: 11 | - &latest_version_abc 12 | url: '#' 13 | version: current 14 | displayVersion: current 15 | latestVersion: *latest_version_abc 16 | - &component 17 | name: xyz 18 | title: &component_title Project XYZ 19 | url: /xyz/6.0/index.html 20 | versions: 21 | - &latest_version_xyz 22 | url: /xyz/6.0/index.html 23 | version: '6.0' 24 | displayVersion: '6.0' 25 | - &component_version 26 | title: *component_title 27 | url: '#' 28 | version: '5.2' 29 | displayVersion: '5.2' 30 | - url: '#' 31 | version: '5.1' 32 | displayVersion: '5.1' 33 | - url: '#' 34 | version: '5.0' 35 | displayVersion: '5.0' 36 | - url: '#' 37 | version: '4.5' 38 | displayVersion: '4.5' 39 | latestVersion: *latest_version_xyz 40 | - name: '123' 41 | title: Project 123 42 | url: '#' 43 | versions: 44 | - &latest_version_123 45 | url: '#' 46 | version: '2.2' 47 | displayVersion: '2.2' 48 | latestVersion: *latest_version_123 49 | page: 50 | url: *home_url 51 | home: true 52 | title: Brand’s Hardware & Software Requirements 53 | component: *component 54 | componentVersion: *component_version 55 | version: '5.2' 56 | displayVersion: '5.2' 57 | module: ROOT 58 | relativeSrcPath: index.adoc 59 | editUrl: http://example.com/project-xyz/blob/master/index.adoc 60 | origin: 61 | private: false 62 | previous: 63 | content: Quickstart 64 | url: '#' 65 | urlType: 'internal' 66 | next: 67 | content: Liber Recusabo 68 | url: '#' 69 | urlType: 'internal' 70 | breadcrumbs: 71 | - content: Quickstart 72 | url: '#' 73 | urlType: fragment 74 | - content: Brand’s Hardware & Software Requirements 75 | url: /index.html 76 | urlType: internal 77 | versions: 78 | - version: '6.0' 79 | displayVersion: '6.0' 80 | url: '#' 81 | - version: '5.2' 82 | displayVersion: '5.2' 83 | url: '#' 84 | - version: '5.1' 85 | displayVersion: '5.1' 86 | url: '#' 87 | - version: '5.0' 88 | displayVersion: '5.0' 89 | missing: true 90 | url: '#' 91 | navigation: 92 | - root: true 93 | items: 94 | - content: Quickstart 95 | url: '#' 96 | urlType: fragment 97 | items: 98 | - content: Brand’s Hardware & Software Requirements 99 | url: /index.html 100 | urlType: internal 101 | - content: Cu Solet 102 | url: '/index.html#cu-solet' 103 | urlType: internal 104 | - content: English + 中文 105 | url: '/index.html#english+中文' 106 | urlType: internal 107 | - content: Liber Recusabo 108 | url: '#liber-recusabo' 109 | urlType: fragment 110 | - content: Reference 111 | items: 112 | - content: Keyboard Shortcuts 113 | url: '#' 114 | urlType: fragment 115 | - content: Importing and Exporting 116 | url: '#' 117 | urlType: fragment 118 | - content: Some Code 119 | url: '/index.html#some-code' 120 | urlType: internal 121 | -------------------------------------------------------------------------------- /src/css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: inherit; 5 | } 6 | 7 | html { 8 | box-sizing: border-box; 9 | font-size: var(--body-font-size); 10 | height: var(--viewport-height); 11 | min-height: 100vh; /* used as a reference to detect invalid vh unit */ 12 | scroll-behavior: smooth; 13 | } 14 | 15 | @media screen and (min-width: 1024px) { 16 | html { 17 | font-size: var(--body-font-size--desktop); 18 | } 19 | } 20 | 21 | body { 22 | background: var(--body-background); 23 | color: var(--body-font-color); 24 | font-family: var(--body-font-family); 25 | line-height: var(--body-line-height); 26 | margin: 0; 27 | tab-size: 4; 28 | word-wrap: anywhere; /* aka overflow-wrap; used when hyphens are disabled or aren't sufficient */ 29 | } 30 | 31 | a { 32 | text-decoration: none; 33 | } 34 | 35 | a:hover { 36 | text-decoration: underline; 37 | } 38 | 39 | a:active { 40 | background-color: none; 41 | } 42 | 43 | code, 44 | kbd, 45 | pre { 46 | font-family: var(--monospace-font-family); 47 | } 48 | 49 | b, 50 | dt, 51 | strong, 52 | th { 53 | font-weight: var(--body-font-weight-bold); 54 | } 55 | 56 | sub, 57 | sup { 58 | font-size: 75%; 59 | line-height: 0; 60 | position: relative; 61 | vertical-align: baseline; 62 | } 63 | 64 | sub { 65 | bottom: -0.25em; 66 | } 67 | 68 | sup { 69 | top: -0.5em; 70 | } 71 | 72 | em em { /* stylelint-disable-line */ 73 | font-style: normal; 74 | } 75 | 76 | strong strong { /* stylelint-disable-line */ 77 | font-weight: normal; 78 | } 79 | 80 | button { 81 | cursor: pointer; 82 | font-family: inherit; 83 | font-size: 1em; 84 | line-height: var(--body-line-height); 85 | margin: 0; 86 | } 87 | 88 | button::-moz-focus-inner { 89 | border: none; 90 | padding: 0; 91 | } 92 | 93 | summary { 94 | cursor: pointer; 95 | -webkit-tap-highlight-color: transparent; 96 | outline: none; 97 | } 98 | 99 | table { 100 | border-collapse: collapse; 101 | word-wrap: normal; /* table widths aren't computed as expected when word-wrap is enabled */ 102 | } 103 | 104 | object[type="image/svg+xml"]:not([width]) { 105 | width: fit-content; 106 | } 107 | 108 | ::placeholder { 109 | opacity: 0.5; 110 | } 111 | 112 | @media (pointer: fine) { 113 | @supports (scrollbar-width: thin) { 114 | html { 115 | scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-track-color); 116 | } 117 | 118 | body * { 119 | scrollbar-width: thin; 120 | scrollbar-color: var(--scrollbar-thumb-color) transparent; 121 | } 122 | } 123 | 124 | html::-webkit-scrollbar { 125 | background-color: var(--scrollbar-track-color); 126 | height: 12px; 127 | width: 12px; 128 | } 129 | 130 | body ::-webkit-scrollbar { 131 | height: 6px; 132 | width: 6px; 133 | } 134 | 135 | ::-webkit-scrollbar-thumb { 136 | background-clip: padding-box; 137 | background-color: var(--scrollbar-thumb-color); 138 | border: 3px solid transparent; 139 | border-radius: 12px; 140 | } 141 | 142 | body ::-webkit-scrollbar-thumb { 143 | border-width: 1.75px; 144 | border-radius: 6px; 145 | } 146 | 147 | ::-webkit-scrollbar-thumb:hover { 148 | background-color: var(--scrollbar_hover-thumb-color); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/css/body.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 1024px) { 2 | .body { 3 | display: flex; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/css/breadcrumbs.css: -------------------------------------------------------------------------------- 1 | .breadcrumbs { 2 | display: none; 3 | flex: 1 1; 4 | padding: 0 0.5rem 0 0.75rem; 5 | line-height: var(--nav-line-height); 6 | } 7 | 8 | @media screen and (min-width: 1024px) { 9 | .breadcrumbs { 10 | display: block; 11 | } 12 | } 13 | 14 | a + .breadcrumbs { 15 | padding-left: 0.05rem; 16 | } 17 | 18 | .breadcrumbs ul { 19 | display: flex; 20 | flex-wrap: wrap; 21 | margin: 0; 22 | padding: 0; 23 | list-style: none; 24 | } 25 | 26 | .breadcrumbs li { 27 | display: inline; 28 | margin: 0; 29 | } 30 | 31 | .breadcrumbs li::after { 32 | content: "/"; 33 | padding: 0 0.5rem; 34 | } 35 | 36 | .breadcrumbs li:last-of-type::after { 37 | content: none; 38 | } 39 | -------------------------------------------------------------------------------- /src/css/footer.css: -------------------------------------------------------------------------------- 1 | footer.footer { 2 | background-color: var(--footer-background); 3 | color: var(--footer-font-color); 4 | font-size: calc(14 / var(--rem-base) * 1rem); 5 | line-height: var(--footer-line-height); 6 | padding: 2rem 1rem; 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | .footer p { 12 | margin: 0; 13 | } 14 | 15 | .footer a { 16 | color: inherit; 17 | text-decoration: none; 18 | } 19 | 20 | .footer a:hover { 21 | color: var(--footer-link-font-color); 22 | } 23 | 24 | .footer img { 25 | vertical-align: middle; 26 | } 27 | 28 | .footer p + p { 29 | margin-top: 1em; 30 | } 31 | 32 | .footer-main { 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | } 37 | 38 | .footer-brand { 39 | display: flex; 40 | align-items: center; 41 | margin: 0; 42 | } 43 | 44 | .footer-brand .logo { 45 | display: flex; 46 | } 47 | 48 | .footer-brand a { 49 | color: var(--footer-link-font-color); 50 | } 51 | 52 | .footer-brand figcaption { 53 | color: var(--footer-link-font-color); 54 | font-family: "Comfortaa", sans-serif; 55 | font-size: calc(36 / var(--rem-base) * 1rem); 56 | line-height: 1; 57 | position: relative; 58 | top: 0.06em; /* compensate for built-in line height in font */ 59 | letter-spacing: -0.02em; 60 | } 61 | 62 | .footer-brand .logo + figcaption { 63 | margin-left: 1rem; 64 | } 65 | 66 | .footer-brand img { 67 | height: calc(45 / var(--rem-base) * 1rem); 68 | } 69 | 70 | .footer-brand-links { 71 | list-style: none; 72 | display: flex; 73 | flex-wrap: wrap; 74 | justify-content: center; 75 | margin: 2rem 0 0; 76 | padding: 0; 77 | font-size: 0.9rem; 78 | } 79 | 80 | .footer-brand-links li + li::before { 81 | content: '|'; 82 | padding: 0 0.5em; 83 | } 84 | 85 | .footer-brand-follow { 86 | font-size: calc(24 / var(--rem-base) * 1rem); 87 | font-weight: var(--body-font-weight-bold); 88 | line-height: 1; 89 | } 90 | 91 | .footer p.footer-brand-follow { 92 | margin-top: 1.25rem; 93 | } 94 | 95 | .footer-brand-follow .logo { 96 | width: calc(28 / var(--rem-base) * 1rem); 97 | } 98 | 99 | .footer-brand-follow .handle { 100 | padding: 0 0 0.1em 0.5ch; 101 | } 102 | 103 | .footer-legal { 104 | margin-top: 1.5rem; 105 | } 106 | 107 | .footer-legal, 108 | .footer-thanks { 109 | text-align: center; 110 | } 111 | 112 | .footer-thanks .badges { 113 | line-height: 1; 114 | display: inline-flex; 115 | align-items: center; 116 | } 117 | 118 | .footer-thanks .badges a + a { 119 | margin-left: 5px; 120 | } 121 | 122 | @media screen and (min-width: 1024px) { 123 | footer.footer { 124 | flex-direction: row; 125 | position: relative; 126 | z-index: var(--z-index-footer); 127 | } 128 | 129 | .footer-legal { 130 | margin-top: 0; 131 | padding: 0 1.5rem; 132 | text-align: left; 133 | width: 40%; 134 | } 135 | 136 | .footer-main, 137 | .footer-thanks { 138 | width: 30%; 139 | } 140 | 141 | .footer-thanks { 142 | text-align: right; 143 | } 144 | 145 | .footer-thanks .badges { 146 | display: flex; 147 | justify-content: flex-end; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/css/header.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 1023.5px) { 2 | html.is-clipped--navbar { 3 | overflow-y: hidden; 4 | } 5 | 6 | /* NOTE workaround bug in Firefox mobile, which clips the html element to the original viewport height */ 7 | @supports (scrollbar-width: none) { 8 | html.is-clipped--navbar { 9 | overflow-y: initial; 10 | scrollbar-width: none; 11 | } 12 | } 13 | } 14 | 15 | body { 16 | padding-top: var(--navbar-height); 17 | } 18 | 19 | .navbar { 20 | background: var(--navbar-background); 21 | color: var(--navbar-font-color); 22 | font-size: calc(16 / var(--rem-base) * 1rem); 23 | height: var(--navbar-height); 24 | position: fixed; 25 | top: 0; 26 | width: 100%; 27 | z-index: var(--z-index-navbar); 28 | } 29 | 30 | .navbar a { 31 | text-decoration: none; 32 | } 33 | 34 | .navbar-brand { 35 | display: flex; 36 | } 37 | 38 | .navbar-brand .navbar-item.logo { 39 | align-self: center; 40 | padding: 0; 41 | margin-left: 1rem; 42 | height: calc(0.6 * var(--navbar-height)); 43 | width: calc(0.6 * var(--navbar-height)); 44 | } 45 | 46 | .navbar-brand .navbar-item.logo img { 47 | width: inherit; 48 | } 49 | 50 | .navbar-brand .navbar-item.title { 51 | align-self: center; 52 | color: var(--navbar-font-color); 53 | font-family: "Comfortaa", sans-serif; 54 | font-size: 1rem; 55 | line-height: 1; 56 | position: relative; 57 | top: 0.06em; /* compensate for built-in line height in font */ 58 | letter-spacing: -0.02em; 59 | padding: 0; 60 | margin: 0 1rem; 61 | } 62 | 63 | #search { 64 | display: flex; 65 | position: relative; /* make results relative to whole search field, not just input */ 66 | color: var(--body-font-color); 67 | width: 100%; 68 | } 69 | 70 | #search .query { 71 | border: 1px solid var(--navbar-button-border-color); 72 | color: inherit; 73 | font-family: inherit; 74 | font-size: inherit; 75 | line-height: 1.5; 76 | padding: 0.25rem; 77 | width: 7ex; /* width required to make input flexible */ 78 | height: -webkit-fit-content; /* fix for Safari */ 79 | flex: auto; 80 | } 81 | 82 | #search :focus { 83 | outline: none; 84 | } 85 | 86 | #search.has-filter .query { 87 | border-right: none; 88 | border-radius: 0.1em 0 0 0.1em; 89 | } 90 | 91 | #search .filter { 92 | background: var(--color-white) linear-gradient(to bottom, var(--navbar-button-border-color) 0%, var(--navbar-button-border-color) 100%) no-repeat left center / 1px 50%; 93 | border: 1px solid var(--navbar-button-border-color); 94 | border-left: none; 95 | border-radius: 0 0.1em 0.1em 0; 96 | color: var(--toolbar-font-color); 97 | cursor: pointer; 98 | font-size: 0.875em; 99 | display: flex; 100 | align-items: center; 101 | padding: 0 0.5rem; 102 | white-space: nowrap; 103 | overflow: hidden; 104 | } 105 | 106 | #search .filter input { 107 | margin: 0 0.25rem 0 0; 108 | cursor: inherit; 109 | } 110 | 111 | .navbar-burger { 112 | background: none; 113 | border: none; 114 | outline: none; 115 | line-height: 1; 116 | position: relative; 117 | width: 3rem; 118 | margin-left: auto; 119 | padding: 0; 120 | display: flex; 121 | flex-direction: column; 122 | align-items: center; 123 | justify-content: center; 124 | min-width: 0; 125 | } 126 | 127 | .navbar-burger span { 128 | background-color: var(--navbar-font-color); 129 | height: 2px; 130 | width: 1rem; 131 | } 132 | 133 | .navbar-burger:not(.is-active) span { 134 | transition: transform ease-out 0.25s, opacity 0s 0.25s, margin-top ease-out 0.25s 0.25s; 135 | } 136 | 137 | .navbar-burger span + span { 138 | margin-top: 0.25rem; 139 | } 140 | 141 | .navbar-burger.is-active span + span { 142 | margin-top: -2px; 143 | } 144 | 145 | .navbar-burger.is-active span:nth-child(1) { 146 | transform: rotate(45deg); 147 | } 148 | 149 | .navbar-burger.is-active span:nth-child(2) { 150 | opacity: 0; 151 | } 152 | 153 | .navbar-burger.is-active span:nth-child(3) { 154 | transform: rotate(-45deg); 155 | } 156 | 157 | .navbar-item, 158 | .navbar-link { 159 | color: var(--navbar-menu-font-color); 160 | display: block; 161 | line-height: var(--doc-line-height); 162 | padding: 0.5rem 1rem; 163 | } 164 | 165 | .navbar-item.has-dropdown { 166 | padding: 0; 167 | } 168 | 169 | .navbar-item .icon { 170 | width: 1.25rem; 171 | height: 1.25rem; 172 | display: block; 173 | } 174 | 175 | .navbar-item .icon img, 176 | .navbar-item .icon svg { 177 | width: inherit; 178 | height: inherit; 179 | } 180 | 181 | .navbar-item .icon img { 182 | filter: var(--navbar-menu-icon-filter); 183 | } 184 | 185 | .navbar-item .icon svg { 186 | fill: currentColor; 187 | } 188 | 189 | .navbar-link { 190 | padding-right: 2.5em; 191 | } 192 | 193 | div.navbar-item, 194 | div.navbar-link { 195 | cursor: default; 196 | } 197 | 198 | .navbar-dropdown .navbar-item { 199 | display: flex; 200 | align-items: center; 201 | justify-content: space-between; 202 | padding-left: 1.5rem; 203 | padding-right: 1.5rem; 204 | } 205 | 206 | .navbar-dropdown .navbar-item small { 207 | color: var(--toolbar-muted-color); 208 | font-size: calc(12 / var(--rem-base) * 1rem); 209 | } 210 | 211 | .navbar-dropdown .navbar-item.has-icon { 212 | justify-content: flex-start; 213 | } 214 | 215 | .navbar-dropdown .navbar-item.has-icon .icon { 216 | margin-right: 1.25ex; 217 | } 218 | 219 | .navbar-divider { 220 | background-color: var(--navbar-menu-border-color); 221 | border: none; 222 | height: 1px; 223 | margin: 0.25rem 0; 224 | } 225 | 226 | .navbar-dropdown div.navbar-item { 227 | font-weight: var(--navbar-heading-font-weight); 228 | color: var(--navbar-heading-font-color); 229 | letter-spacing: -0.01em; 230 | } 231 | 232 | @media screen and (max-width: 1023.5px) { 233 | .navbar-brand { 234 | height: inherit; 235 | } 236 | 237 | .navbar-brand .navbar-item { 238 | align-items: center; 239 | display: flex; 240 | } 241 | 242 | .navbar-menu { 243 | background: var(--navbar-menu-background); 244 | box-shadow: 0 8px 16px rgba(10, 10, 10, 0.1); 245 | max-height: var(--body-min-height); 246 | overflow-y: auto; 247 | overscroll-behavior: none; 248 | padding: 0.5rem 0; 249 | } 250 | 251 | .navbar-menu:not(.is-active) { 252 | display: none; 253 | } 254 | 255 | .navbar-menu a.navbar-item:hover, 256 | .navbar-menu .navbar-link:hover { 257 | background: var(--navbar-menu_hover-background); 258 | } 259 | 260 | .navbar-end .navbar-item.search { 261 | height: 3rem; 262 | display: flex; 263 | } 264 | 265 | #search .query { 266 | height: 100%; 267 | } 268 | 269 | .navbar-dropdown .navbar-divider { 270 | margin-left: 1.5rem; 271 | } 272 | 273 | .navbar-item[data-title]::before { 274 | content: attr(data-title); 275 | } 276 | 277 | .navbar-item[data-title] .icon { 278 | display: none; 279 | } 280 | } 281 | 282 | @media screen and (min-width: 1024px) { 283 | .navbar-burger { 284 | display: none; 285 | } 286 | 287 | .navbar, 288 | .navbar-menu, 289 | .navbar-end { 290 | display: flex; 291 | } 292 | 293 | .navbar-menu { 294 | flex: auto; 295 | } 296 | 297 | .navbar-end { 298 | flex: auto; 299 | justify-content: flex-end; 300 | } 301 | 302 | .navbar-item, 303 | .navbar-link { 304 | display: flex; 305 | position: relative; 306 | flex: none; 307 | } 308 | 309 | .navbar-item:not(.has-dropdown), 310 | .navbar-link { 311 | align-items: center; 312 | } 313 | 314 | .navbar-item.is-hoverable:hover .navbar-dropdown { 315 | display: block; 316 | } 317 | 318 | .navbar-link::after { 319 | border-width: 0 0 1px 1px; 320 | border-style: solid; 321 | content: ""; 322 | display: block; 323 | height: 0.5em; 324 | pointer-events: none; 325 | position: absolute; 326 | transform: rotate(-45deg); 327 | width: 0.5em; 328 | margin-top: -0.375em; 329 | right: 1.125em; 330 | top: 50%; 331 | } 332 | 333 | .navbar-end > .navbar-item, 334 | .navbar-end .navbar-link { 335 | color: var(--navbar-font-color); 336 | } 337 | 338 | .navbar-end > a.navbar-item:hover, 339 | .navbar-end .navbar-link:hover, 340 | .navbar-end .navbar-item.has-dropdown:hover .navbar-link, 341 | .navbar-end .navbar-item.has-dropdown:hover > a.navbar-item { 342 | background: var(--navbar_hover-background); 343 | color: var(--navbar-font-color); 344 | } 345 | 346 | .navbar-end .navbar-link::after { 347 | border-color: currentColor; 348 | } 349 | 350 | .navbar-end .navbar-item.search { 351 | flex: 0 1 20rem; 352 | padding-left: 0; 353 | } 354 | 355 | .navbar-item .icon img { 356 | filter: invert(100%); 357 | } 358 | 359 | .navbar-dropdown { 360 | background: var(--navbar-menu-background); 361 | border: 1px solid var(--navbar-menu-border-color); 362 | border-top: 2px solid var(--color-brand-primary); 363 | border-radius: var(--navbar-menu-border-radius); 364 | box-shadow: var(--navbar-menu-box-shadow); 365 | display: none; 366 | top: 100%; 367 | left: 0; 368 | min-width: 100%; 369 | position: absolute; 370 | } 371 | 372 | .navbar-dropdown .navbar-item { 373 | padding: 0.5rem 2rem 0.5rem 1rem; 374 | white-space: nowrap; 375 | } 376 | 377 | .navbar-dropdown .navbar-item small { 378 | position: relative; 379 | right: -1rem; 380 | } 381 | 382 | .navbar-dropdown .navbar-item:last-child { 383 | border-radius: inherit; 384 | } 385 | 386 | .navbar-dropdown .navbar-item .icon img { 387 | filter: none; 388 | } 389 | 390 | .navbar-dropdown.is-right { 391 | left: auto; 392 | right: 0; 393 | } 394 | 395 | .navbar-dropdown a.navbar-item:hover { 396 | background: var(--navbar-menu_hover-background); 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /src/css/highlight.css: -------------------------------------------------------------------------------- 1 | /*! Adapted from the GitHub style by Vasily Polovnyov */ 2 | .hljs-comment, 3 | .hljs-quote { 4 | color: #998; 5 | font-style: italic; 6 | } 7 | 8 | .hljs-keyword, 9 | .hljs-selector-tag, 10 | .hljs-subst { 11 | color: #333; 12 | font-weight: var(--monospace-font-weight-bold); 13 | } 14 | 15 | .hljs-number, 16 | .hljs-literal, 17 | .hljs-variable, 18 | .hljs-template-variable, 19 | .hljs-tag .hljs-attr { 20 | color: #008080; 21 | } 22 | 23 | .hljs-string, 24 | .hljs-doctag { 25 | color: #d14; 26 | } 27 | 28 | .hljs-title, 29 | .hljs-section, 30 | .hljs-selector-id { 31 | color: #900; 32 | font-weight: var(--monospace-font-weight-bold); 33 | } 34 | 35 | .hljs-subst { 36 | font-weight: normal; 37 | } 38 | 39 | .hljs-type, 40 | .hljs-class .hljs-title { 41 | color: #458; 42 | font-weight: var(--monospace-font-weight-bold); 43 | } 44 | 45 | .hljs-tag, 46 | .hljs-name, 47 | .hljs-attribute { 48 | color: #000080; 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-regexp, 53 | .hljs-link { 54 | color: #009926; 55 | } 56 | 57 | .hljs-symbol, 58 | .hljs-bullet { 59 | color: #990073; 60 | } 61 | 62 | .hljs-built_in, 63 | .hljs-builtin-name { 64 | color: #0086b3; 65 | } 66 | 67 | .hljs-meta { 68 | color: #808080; 69 | font-weight: var(--monospace-font-weight-bold); 70 | } 71 | 72 | .hljs-deletion { 73 | background: #fdd; 74 | } 75 | 76 | .hljs-addition { 77 | background: #dfd; 78 | } 79 | 80 | .hljs-emphasis { 81 | font-style: italic; 82 | } 83 | 84 | .hljs-strong { 85 | font-weight: var(--monospace-font-weight-bold); 86 | } 87 | -------------------------------------------------------------------------------- /src/css/home.css: -------------------------------------------------------------------------------- 1 | .doc .panel { 2 | margin-top: 1rem; 3 | background-color: var(--color-smoke-50); 4 | border-radius: 0.5em; 5 | padding: 0.75rem 1.25rem; 6 | font-size: calc(16 / var(--rem-base) * 1rem); 7 | } 8 | 9 | .doc .panel h2 { 10 | font-size: 1rem; 11 | font-weight: var(--body-font-weight-bold); 12 | padding-bottom: 0.25rem; 13 | } 14 | 15 | .doc .panel h2, 16 | .doc .panel h2 + * { 17 | margin-top: 0; 18 | } 19 | 20 | .doc h3 { 21 | margin-top: 2rem; 22 | } 23 | 24 | .doc .grid { 25 | margin-top: 1rem; 26 | font-size: calc(16 / var(--rem-base) * 1rem); 27 | } 28 | 29 | @media screen and (min-width: 769px) { 30 | .doc .grid dl { 31 | display: grid; 32 | grid-auto-columns: 1fr; 33 | grid-auto-flow: row; 34 | grid-gap: 0.25rem 1rem; 35 | } 36 | 37 | .doc .grid dt { 38 | grid-row: 1; 39 | font-style: normal; 40 | padding-right: 0; 41 | word-wrap: normal; 42 | } 43 | 44 | .doc .grid dd { 45 | margin: 0; 46 | font-size: calc(14 / var(--rem-base) * 1rem); 47 | } 48 | } 49 | 50 | .doc .grid.has-emblems dt { 51 | display: flex; 52 | } 53 | 54 | .doc .grid dt .emblem { 55 | color: var(--color-gray-50); 56 | font-family: var(--monospace-font-family); 57 | font-weight: normal; 58 | font-size: 0.95em; 59 | letter-spacing: -0.025em; 60 | } 61 | 62 | .doc .grid dt .emblem::before { 63 | content: '//'; 64 | padding: 0 0.5ch; 65 | } 66 | 67 | .doc .grid .ulist { 68 | margin-top: 0.25rem; 69 | line-height: 1.3; 70 | } 71 | 72 | .doc .grid ul { 73 | padding-left: 0; 74 | list-style: none; 75 | } 76 | 77 | .doc .grid ul li { 78 | margin-top: 0.15em; 79 | margin-bottom: 0; 80 | hyphens: none; 81 | } 82 | 83 | .doc .grid ul a { 84 | font-weight: var(--body-font-weight-bold); 85 | } 86 | -------------------------------------------------------------------------------- /src/css/main.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 1023.5px) { 2 | aside.toc.sidebar { 3 | display: none; 4 | } 5 | 6 | main > .content { 7 | overflow-x: auto; 8 | } 9 | } 10 | 11 | @media screen and (min-width: 1024px) { 12 | main { 13 | flex: auto; 14 | min-width: 0; /* min-width: 0 required for flexbox to constrain overflowing elements */ 15 | } 16 | 17 | main > .content { 18 | display: flex; 19 | } 20 | 21 | aside.toc.embedded { 22 | display: none; 23 | } 24 | 25 | aside.toc.sidebar { 26 | flex: 0 0 var(--toc-width); 27 | order: 1; 28 | } 29 | } 30 | 31 | @media screen and (min-width: 1216px) { 32 | aside.toc.sidebar { 33 | flex-basis: var(--toc-width--widescreen); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/css/nav.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 768.5px) { 2 | html.is-clipped--nav { 3 | overflow-y: hidden; 4 | } 5 | 6 | /* NOTE workaround bug in Firefox mobile, which clips the html element to the original viewport height */ 7 | @supports (scrollbar-width: none) { 8 | html.is-clipped--nav { 9 | overflow-y: initial; 10 | scrollbar-width: none; 11 | } 12 | } 13 | } 14 | 15 | .nav-container { 16 | position: fixed; 17 | top: var(--navbar-height); 18 | left: 0; 19 | width: 100%; 20 | font-size: calc(17 / var(--rem-base) * 1rem); 21 | z-index: var(--z-index-nav); 22 | visibility: hidden; 23 | } 24 | 25 | @media screen and (min-width: 769px) { 26 | .nav-container { 27 | width: var(--nav-width); 28 | } 29 | } 30 | 31 | @media screen and (min-width: 1024px) { 32 | .nav-container { 33 | font-size: calc(15.5 / var(--rem-base) * 1rem); 34 | flex: none; 35 | position: static; 36 | visibility: visible; 37 | height: var(--nav-height--desktop); 38 | } 39 | } 40 | 41 | .nav-container.is-active { 42 | visibility: visible; 43 | } 44 | 45 | .nav { 46 | background: var(--nav-background); 47 | position: relative; 48 | top: var(--toolbar-height); 49 | height: var(--nav-height); 50 | } 51 | 52 | @media screen and (min-width: 769px) { 53 | .nav { 54 | box-shadow: 0.5px 0 3px var(--nav-border-color); 55 | } 56 | } 57 | 58 | @media screen and (min-width: 1024px) { 59 | .nav { 60 | top: var(--navbar-height); /* fixed position is relative to viewport */ 61 | box-shadow: none; 62 | position: fixed; 63 | width: inherit; 64 | height: inherit; 65 | } 66 | } 67 | 68 | .nav a { 69 | color: inherit; 70 | } 71 | 72 | .nav .panels { 73 | display: flex; 74 | flex-direction: column; 75 | height: inherit; 76 | } 77 | 78 | .nav-panel-menu { 79 | overflow-y: scroll; 80 | overscroll-behavior: none; 81 | height: var(--nav-panel-menu-height); 82 | } 83 | 84 | .nav-panel-menu.is-loading { 85 | visibility: hidden; 86 | } 87 | 88 | .nav-panel-menu:not(.is-active) .nav-menu { 89 | opacity: 0.75; 90 | } 91 | 92 | .nav-panel-menu:not(.is-active)::after { 93 | content: ""; 94 | background: rgba(0, 0, 0, 0.5); 95 | display: block; 96 | position: absolute; 97 | top: 0; 98 | right: 0; 99 | bottom: 0; 100 | left: 0; 101 | } 102 | 103 | .nav-menu { 104 | min-height: 100%; 105 | padding: 0.5rem 0.75rem; 106 | line-height: var(--nav-line-height); 107 | position: relative; 108 | } 109 | 110 | .nav-menu-toggle { 111 | background: transparent url(../img/octicons-24.svg#view-unfold) no-repeat center / 100% 100%; 112 | border: none; 113 | float: right; 114 | height: 1em; 115 | margin-right: -0.5rem; 116 | opacity: 0.5; 117 | outline: none; 118 | padding: 0; 119 | position: sticky; 120 | visibility: hidden; 121 | width: 1em; 122 | top: 0.8rem; 123 | } 124 | 125 | .nav-menu-toggle.is-active { 126 | background-image: url(../img/octicons-24.svg#view-fold); 127 | } 128 | 129 | .nav-panel-menu.is-active:hover .nav-menu-toggle { 130 | visibility: visible; 131 | } 132 | 133 | .nav-menu h3.title { 134 | color: var(--nav-heading-font-color); 135 | font-size: 0.9rem; 136 | font-weight: var(--body-font-weight-bold); 137 | margin: 0.15rem 0 0; 138 | padding: 0; 139 | } 140 | 141 | .nav-list { 142 | list-style: none; 143 | margin: 0 0 0 0.75rem; 144 | padding: 0; 145 | } 146 | 147 | .nav-menu > .nav-list + .nav-list { 148 | margin-top: 0.5rem; 149 | } 150 | 151 | .nav-item { 152 | margin-top: 0.5em; 153 | } 154 | 155 | /* adds some breathing room below a nested list */ 156 | .nav-item-toggle ~ .nav-list { 157 | padding-bottom: 0.125rem; 158 | } 159 | 160 | /* matches list without a title */ 161 | .nav-item[data-depth="0"] > .nav-list:first-child { 162 | display: block; 163 | margin: 0; 164 | } 165 | 166 | .nav-item:not(.is-active) > .nav-list { 167 | display: none; 168 | } 169 | 170 | .nav-item-toggle { 171 | background: transparent url(../img/octicons-16.svg#view-triangle-right) no-repeat center / 100% 100%; 172 | opacity: 0.25; 173 | border: none; 174 | outline: none; 175 | line-height: inherit; 176 | padding: 0; 177 | position: absolute; 178 | height: calc(var(--nav-line-height) * 1em); 179 | width: calc(var(--nav-line-height) * 1em); 180 | margin: -0.05em 0 0 calc(var(--nav-line-height) * -1em); 181 | } 182 | 183 | .nav-item.is-active > .nav-item-toggle { 184 | background-image: url(../img/octicons-16.svg#view-triangle-down); 185 | } 186 | 187 | .is-current-page > .nav-link, 188 | .is-current-page > .nav-text { 189 | /* 190 | font-weight: var(--body-font-weight-bold); 191 | letter-spacing: -0.004em; 192 | */ 193 | -webkit-text-stroke-width: 0.02em; 194 | } 195 | 196 | .nav-panel-explore { 197 | background: var(--nav-background); 198 | display: flex; 199 | flex-direction: column; 200 | position: absolute; 201 | top: 0; 202 | right: 0; 203 | bottom: 0; 204 | left: 0; 205 | } 206 | 207 | .nav-panel-explore:not(:first-child) { 208 | top: auto; 209 | max-height: var(--nav-panel-explore-height); 210 | } 211 | 212 | .nav-panel-explore .context { 213 | font-size: calc(15 / var(--rem-base) * 1rem); 214 | flex-shrink: 0; 215 | color: var(--nav-muted-color); 216 | box-shadow: 0 -1px 0 var(--nav-panel-divider-color); 217 | padding: 0 0.5rem; 218 | display: flex; 219 | align-items: center; 220 | justify-content: space-between; 221 | line-height: 1; 222 | height: var(--drawer-height); 223 | } 224 | 225 | .nav-panel-explore:not(:first-child) .context { 226 | cursor: pointer; 227 | } 228 | 229 | .nav-panel-explore .context .version { 230 | padding-right: 0.25rem; 231 | } 232 | 233 | .nav-panel-explore:not(:first-child) .context .version::after { 234 | content: ""; 235 | background: url(../img/octicons-16.svg#view-chevron-down) no-repeat center right -0.15em / 1em 1em; 236 | filter: var(--nav-muted-icon-filter); 237 | padding-right: 1.25em; 238 | margin-right: -0.25rem; 239 | } 240 | 241 | .nav-panel-explore .components { 242 | line-height: 1.3; 243 | flex-grow: 1; 244 | box-shadow: inset 0 1px 5px var(--nav-panel-divider-color); 245 | background: var(--nav-secondary-background); 246 | padding: 0.5rem 0.75rem 0 0.75rem; 247 | margin: 0; 248 | overflow-y: scroll; 249 | max-height: 100%; 250 | display: block; 251 | } 252 | 253 | .nav-panel-explore:not(.is-active) .components { 254 | display: none; 255 | } 256 | 257 | .nav-panel-explore .component { 258 | display: flex; 259 | justify-content: space-between; 260 | align-items: flex-start; 261 | } 262 | 263 | .nav-panel-explore .component + .component { 264 | margin-top: 0.5rem; 265 | } 266 | 267 | .nav-panel-explore .component:last-child { 268 | margin-bottom: 0.75rem; 269 | } 270 | 271 | .nav-panel-explore .component .title { 272 | font-weight: var(--body-font-weight-bold); 273 | margin-top: 0.25rem; 274 | flex: none; 275 | } 276 | 277 | .nav-panel-explore .component .title:not(:only-child) { 278 | max-width: 66.66%; 279 | } 280 | 281 | .nav-panel-explore .versions { 282 | display: flex; 283 | justify-content: flex-end; 284 | flex-wrap: wrap; 285 | padding-left: 0; 286 | line-height: 1; 287 | list-style: none; 288 | } 289 | 290 | .nav-panel-explore .component .version { 291 | margin: 0.25rem 0 0 0.25rem; 292 | } 293 | 294 | .nav-panel-explore .component .version a { 295 | border: 1px solid var(--nav-border-color); 296 | border-radius: 0.25rem; 297 | opacity: 0.75; 298 | white-space: nowrap; 299 | padding: 0.125em 0.25em; 300 | display: inherit; 301 | } 302 | 303 | .nav-panel-explore .component .is-current a { 304 | border-color: currentColor; 305 | opacity: 0.9; 306 | font-weight: var(--body-font-weight-bold); 307 | } 308 | -------------------------------------------------------------------------------- /src/css/page-versions.css: -------------------------------------------------------------------------------- 1 | .page-versions { 2 | margin: 0 0.2rem 0 auto; 3 | position: relative; 4 | line-height: 1; 5 | } 6 | 7 | @media screen and (min-width: 1024px) { 8 | .page-versions { 9 | margin-right: 0.7rem; 10 | } 11 | } 12 | 13 | .page-versions .version-menu-toggle { 14 | color: inherit; 15 | background-color: transparent; 16 | border: none; 17 | outline: none; 18 | line-height: inherit; 19 | padding: 0.5rem; 20 | position: relative; 21 | z-index: var(--z-index-page-version-menu); 22 | } 23 | 24 | .page-versions .version-menu-toggle::after { 25 | content: ""; 26 | background: url(../img/octicons-16.svg#view-chevron-down) no-repeat center right -0.15em / 1em 1em; 27 | filter: var(--toolbar-icon-filter); 28 | padding-right: 1rem; 29 | } 30 | 31 | .page-versions .version-menu { 32 | display: flex; 33 | min-width: 100%; 34 | flex-direction: column; 35 | align-items: flex-end; 36 | background: linear-gradient(to bottom, var(--page-version-menu-background) 0%, var(--page-version-menu-background) 100%) no-repeat; 37 | padding: 1.375rem 1.5rem 0.5rem 0.5rem; 38 | position: absolute; 39 | top: 0; 40 | right: 0; 41 | white-space: nowrap; 42 | } 43 | 44 | .page-versions:not(.is-active) .version-menu { 45 | display: none; 46 | } 47 | 48 | .page-versions .version { 49 | display: block; 50 | padding-top: 0.5rem; 51 | } 52 | 53 | .page-versions .version.is-current { 54 | display: none; 55 | } 56 | 57 | .page-versions .version.is-missing { 58 | color: var(--page-version-missing-font-color); 59 | font-style: italic; 60 | text-decoration: none; 61 | } 62 | -------------------------------------------------------------------------------- /src/css/pagination.css: -------------------------------------------------------------------------------- 1 | nav.pagination { 2 | display: flex; 3 | border-top: 1px solid var(--toolbar-border-color); 4 | line-height: 1; 5 | margin: 2rem -1rem -1rem; 6 | padding: 0.75rem 1rem 0; 7 | } 8 | 9 | nav.pagination span { 10 | display: flex; 11 | flex: 50%; 12 | flex-direction: column; 13 | } 14 | 15 | nav.pagination .prev { 16 | padding-right: 0.5rem; 17 | } 18 | 19 | nav.pagination .next { 20 | margin-left: auto; 21 | padding-left: 0.5rem; 22 | text-align: right; 23 | } 24 | 25 | nav.pagination span::before { 26 | color: var(--toolbar-muted-color); 27 | font-size: 0.75em; 28 | padding-bottom: 0.1em; 29 | } 30 | 31 | nav.pagination .prev::before { 32 | content: "Prev"; 33 | } 34 | 35 | nav.pagination .next::before { 36 | content: "Next"; 37 | } 38 | 39 | nav.pagination a { 40 | font-weight: var(--body-font-weight-bold); 41 | line-height: 1.3; 42 | position: relative; 43 | } 44 | 45 | nav.pagination a::before, 46 | nav.pagination a::after { 47 | color: var(--toolbar-muted-color); 48 | font-weight: normal; 49 | font-size: 1.5em; 50 | line-height: 0.75; 51 | position: absolute; 52 | top: 0; 53 | width: 1rem; 54 | } 55 | 56 | nav.pagination .prev a::before { 57 | content: "\2039"; 58 | transform: translateX(-100%); 59 | } 60 | 61 | nav.pagination .next a::after { 62 | content: "\203a"; 63 | } 64 | -------------------------------------------------------------------------------- /src/css/print.css: -------------------------------------------------------------------------------- 1 | @page { 2 | margin: 0.5in; 3 | } 4 | 5 | @media print { 6 | .hide-for-print { 7 | display: none !important; 8 | } 9 | 10 | html { 11 | font-size: var(--body-font-size--print); 12 | } 13 | 14 | a { 15 | color: inherit !important; 16 | text-decoration: underline; 17 | } 18 | 19 | a.bare, 20 | a[href^="#"], 21 | a[href^="mailto:"] { 22 | text-decoration: none; 23 | } 24 | 25 | tr, 26 | img, 27 | object, 28 | svg { 29 | page-break-inside: avoid; 30 | } 31 | 32 | thead { 33 | display: table-header-group; 34 | } 35 | 36 | pre { 37 | hyphens: none; 38 | white-space: pre-wrap; 39 | } 40 | 41 | body { 42 | padding-top: 2rem; 43 | } 44 | 45 | .navbar { 46 | background: none; 47 | color: inherit; 48 | position: absolute; 49 | } 50 | 51 | .navbar * { 52 | color: inherit !important; 53 | } 54 | 55 | .navbar > :not(.navbar-brand), 56 | .nav-container, 57 | .toolbar, 58 | aside.toc, 59 | nav.pagination, 60 | .footer > :not(.footer-legal) { 61 | display: none; 62 | } 63 | 64 | .doc { 65 | color: inherit; 66 | margin: auto; 67 | max-width: none; 68 | padding-bottom: 2rem; 69 | } 70 | 71 | .doc .admonitionblock td.icon { 72 | print-color-adjust: exact; /* stylelint-disable-line */ 73 | } 74 | 75 | .doc .listingblock code[data-lang]::before { 76 | display: block; 77 | } 78 | 79 | footer.footer { 80 | background: none; 81 | border-top: 1px solid var(--panel-border-color); 82 | color: var(--quote-attribution-font-color); 83 | padding: 0.75rem 0.5rem 0; 84 | } 85 | 86 | .footer-legal { 87 | margin-top: 0; 88 | text-align: left; 89 | } 90 | 91 | .footer * { 92 | color: inherit; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/css/site.css: -------------------------------------------------------------------------------- 1 | @import "typeface-roboto.css"; 2 | @import "typeface-roboto-mono.css"; 3 | @import "typeface-comfortaa.css"; 4 | @import "vars.css"; 5 | @import "base.css"; 6 | @import "body.css"; 7 | @import "nav.css"; 8 | @import "main.css"; 9 | @import "toolbar.css"; 10 | @import "breadcrumbs.css"; 11 | @import "page-versions.css"; 12 | @import "toc.css"; 13 | @import "doc.css"; 14 | @import "pagination.css"; 15 | @import "header.css"; 16 | @import "footer.css"; 17 | @import "highlight.css"; 18 | @import "print.css"; 19 | -------------------------------------------------------------------------------- /src/css/toc.css: -------------------------------------------------------------------------------- 1 | .toc-menu { 2 | color: var(--toc-font-color); 3 | } 4 | 5 | .toc.sidebar .toc-menu { 6 | margin-right: 0.75rem; 7 | position: sticky; 8 | top: var(--toc-top); 9 | } 10 | 11 | .toc .toc-menu h3 { 12 | color: var(--toc-heading-font-color); 13 | font-size: calc(16 / var(--rem-base) * 1rem); 14 | font-weight: var(--body-font-weight-bold); 15 | line-height: 1.3; 16 | margin: 0 -0.5px; 17 | padding-bottom: 0.25rem; 18 | } 19 | 20 | .toc.sidebar .toc-menu h3 { 21 | display: flex; 22 | flex-direction: column; 23 | height: 2.5rem; 24 | justify-content: flex-end; 25 | } 26 | 27 | .toc .toc-menu ul { 28 | font-size: calc(15 / var(--rem-base) * 1rem); 29 | line-height: var(--toc-line-height); 30 | list-style: none; 31 | margin: 0; 32 | padding: 0; 33 | } 34 | 35 | .toc.sidebar .toc-menu ul { 36 | max-height: var(--toc-height); 37 | overflow-y: auto; 38 | overscroll-behavior: none; 39 | scrollbar-width: none; 40 | } 41 | 42 | .toc .toc-menu ul::-webkit-scrollbar { 43 | width: 0; 44 | height: 0; 45 | } 46 | 47 | @media screen and (min-width: 1024px) { 48 | .toc .toc-menu h3 { 49 | font-size: calc(15 / var(--rem-base) * 1rem); 50 | } 51 | 52 | .toc .toc-menu ul { 53 | font-size: calc(13.5 / var(--rem-base) * 1rem); 54 | } 55 | } 56 | 57 | .toc .toc-menu li { 58 | margin: 0; 59 | } 60 | 61 | .toc .toc-menu li[data-level="2"] a { 62 | padding-left: 1.25rem; 63 | } 64 | 65 | .toc .toc-menu li[data-level="3"] a { 66 | padding-left: 2rem; 67 | } 68 | 69 | .toc .toc-menu a { 70 | color: inherit; 71 | border-left: 2px solid var(--toc-border-color); 72 | display: inline-block; 73 | padding: 0.25rem 0 0.25rem 0.5rem; 74 | text-decoration: none; 75 | } 76 | 77 | .sidebar.toc .toc-menu a { 78 | display: block; 79 | outline: none; 80 | } 81 | 82 | .toc .toc-menu a:hover { 83 | color: var(--doc-font-color); 84 | -webkit-text-stroke-width: 0.01em; 85 | } 86 | 87 | .toc .toc-menu a.is-active { 88 | border-left-color: var(--color-brand-primary); 89 | color: var(--doc-font-color); 90 | -webkit-text-stroke-width: 0.01em; 91 | } 92 | 93 | .sidebar.toc .toc-menu a:focus { 94 | background: var(--panel-background); 95 | } 96 | 97 | .toc .toc-menu .is-hidden-toc { 98 | display: none !important; 99 | } 100 | -------------------------------------------------------------------------------- /src/css/toolbar.css: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | color: var(--toolbar-font-color); 3 | align-items: center; 4 | background-color: var(--toolbar-background); 5 | box-shadow: 0 1px 0 var(--toolbar-border-color); 6 | display: flex; 7 | font-size: calc(15 / var(--rem-base) * 1rem); 8 | height: var(--toolbar-height); 9 | justify-content: flex-start; 10 | position: sticky; 11 | top: var(--navbar-height); 12 | z-index: var(--z-index-toolbar); 13 | } 14 | 15 | .toolbar a { 16 | color: inherit; 17 | } 18 | 19 | .nav-toggle { 20 | background: url(../img/octicons-24.svg#view-file-directory) no-repeat center / 50% 50%; 21 | filter: var(--toolbar-primary-icon-filter); 22 | border: none; 23 | outline: none; 24 | line-height: inherit; 25 | padding: 0; 26 | margin-right: -0.625rem; 27 | height: var(--toolbar-height); 28 | width: var(--toolbar-height); 29 | } 30 | 31 | @media screen and (min-width: 1024px) { 32 | .nav-toggle { 33 | display: none; 34 | } 35 | } 36 | 37 | .nav-toggle.is-active { 38 | background-image: url(../img/octicons-24.svg#view-arrow-left); 39 | } 40 | 41 | .home-link { 42 | display: block; 43 | background: url(../img/octicons-24.svg#view-home) no-repeat center / 100% 100%; 44 | height: calc(var(--toolbar-height) / 2); 45 | width: calc(var(--toolbar-height) / 2); 46 | margin: calc(var(--toolbar-height) / 4); 47 | margin-right: 0.5rem; 48 | filter: var(--toolbar-primary-icon-filter); 49 | } 50 | 51 | .home-link:hover, 52 | .home-link.is-current { 53 | background-image: url(../img/octicons-24.svg#view-home-fill); 54 | } 55 | 56 | .project-link { 57 | display: none; 58 | filter: var(--toolbar-primary-icon-filter); 59 | margin-right: 1ch; 60 | } 61 | 62 | .project-link .icon { 63 | display: inherit; 64 | height: 1.25em; 65 | width: 1.25em; 66 | } 67 | 68 | .project-link .icon img { 69 | height: inherit; 70 | width: inherit; 71 | } 72 | 73 | .edit-this-page { 74 | display: none; 75 | padding-right: 0.5rem; 76 | } 77 | 78 | @media screen and (min-width: 1024px) { 79 | .project-link, 80 | .edit-this-page { 81 | display: block; 82 | } 83 | } 84 | 85 | .toolbar .edit-this-page a { 86 | color: var(--toolbar-muted-color); 87 | } 88 | -------------------------------------------------------------------------------- /src/css/typeface-comfortaa.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Comfortaa"; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: 6 | url(~@fontsource/comfortaa/files/comfortaa-latin-400-normal.woff2) format("woff2"), 7 | url(~@fontsource/comfortaa/files/comfortaa-latin-400-normal.woff) format("woff"); 8 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 9 | } 10 | -------------------------------------------------------------------------------- /src/css/typeface-roboto-mono.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Roboto Mono"; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: 6 | url(~@fontsource/roboto-mono/files/roboto-mono-latin-400-normal.woff2) format("woff2"), 7 | url(~@fontsource/roboto-mono/files/roboto-mono-latin-400-normal.woff) format("woff"); 8 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 9 | } 10 | 11 | @font-face { 12 | font-family: "Roboto Mono"; 13 | font-style: normal; 14 | font-weight: 600; 15 | src: 16 | url(~@fontsource/roboto-mono/files/roboto-mono-latin-500-normal.woff2) format("woff2"), 17 | url(~@fontsource/roboto-mono/files/roboto-mono-latin-500-normal.woff) format("woff"); 18 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 19 | } 20 | -------------------------------------------------------------------------------- /src/css/typeface-roboto.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Roboto"; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: 6 | url(~@fontsource/roboto/files/roboto-latin-400-normal.woff2) format("woff2"), 7 | url(~@fontsource/roboto/files/roboto-latin-400-normal.woff) format("woff"); 8 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 9 | } 10 | 11 | @font-face { 12 | font-family: "Roboto"; 13 | font-style: italic; 14 | font-weight: 400; 15 | src: 16 | url(~@fontsource/roboto/files/roboto-latin-400-italic.woff2) format("woff2"), 17 | url(~@fontsource/roboto/files/roboto-latin-400-italic.woff) format("woff"); 18 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 19 | } 20 | 21 | @font-face { 22 | font-family: "Roboto"; 23 | font-style: normal; 24 | font-weight: 600; 25 | src: 26 | url(~@fontsource/roboto/files/roboto-latin-500-normal.woff2) format("woff2"), 27 | url(~@fontsource/roboto/files/roboto-latin-500-normal.woff) format("woff"); 28 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 29 | } 30 | 31 | @font-face { 32 | font-family: "Roboto"; 33 | font-style: italic; 34 | font-weight: 600; 35 | src: 36 | url(~@fontsource/roboto/files/roboto-latin-500-italic.woff2) format("woff2"), 37 | url(~@fontsource/roboto/files/roboto-latin-500-italic.woff) format("woff"); 38 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 39 | } 40 | -------------------------------------------------------------------------------- /src/css/vars.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* colors */ 3 | --color-brand-primary: #e40046; 4 | --color-white: #fff; 5 | --color-smoke-10: #fefefe; 6 | --color-smoke-30: #fafafa; 7 | --color-smoke-50: #f5f5f5; 8 | --color-smoke-70: #f0f0f0; 9 | --color-smoke-90: #e1e1e1; 10 | --color-gray-10: #c1c1c1; 11 | --color-gray-15: #bababa; 12 | --color-gray-30: #8e8e8e; 13 | --color-gray-50: #808080; 14 | --color-gray-70: #5d5d5d; 15 | --color-jet-20: #4a4a4a; 16 | --color-jet-30: #424242; 17 | --color-jet-50: #333; 18 | --color-jet-70: #222; 19 | --color-jet-80: #191919; 20 | --color-black: #000; 21 | /* utils */ 22 | --rem-base: 18; /* used to compute rem value from desired pixel value (e.g., calc(18 / var(--rem-base) * 1rem) = 18px) */ 23 | --viewport-height: calc(100 * var(--vh, 1vh)); /* used to represent a tunable value of 100vh */ 24 | /* fonts */ 25 | --body-font-size: 1.0625em; /* 17px */ 26 | --body-font-size--desktop: 1.125em; /* 18px */ 27 | --body-font-size--print: 0.9375em; /* 15px */ 28 | --body-line-height: 1.15; 29 | --body-font-color: var(--color-jet-80); 30 | --body-font-family: "Roboto", sans-serif; 31 | --body-font-weight-bold: 600; 32 | --monospace-font-family: "Roboto Mono", monospace; 33 | --monospace-font-weight-bold: 600; 34 | /* base */ 35 | --body-background: var(--color-white); 36 | --panel-background: var(--color-smoke-30); 37 | --panel-border-color: var(--color-smoke-90); 38 | --scrollbar-track-color: var(--color-smoke-30); 39 | --scrollbar-thumb-color: var(--color-gray-10); 40 | --scrollbar_hover-thumb-color: #9c9c9c; 41 | /* navbar */ 42 | --navbar-background: var(--color-jet-80); 43 | --navbar-font-color: var(--color-white); 44 | --navbar-heading-font-color: var(--color-gray-70); 45 | --navbar-heading-font-weight: var(--body-font-weight-bold); 46 | --navbar_hover-background: var(--color-black); 47 | --navbar-button-background: var(--color-white); 48 | --navbar-button-border-color: var(--panel-border-color); 49 | --navbar-button-font-color: var(--body-font-color); 50 | --navbar-menu-border-color: var(--panel-border-color); 51 | --navbar-menu-border-radius: 0 0 0.25rem 0.25rem; 52 | --navbar-menu-box-shadow: 0 2px 4px rgba(160, 160, 160, 0.15); 53 | --navbar-menu-background: var(--color-white); 54 | --navbar-menu-font-color: var(--body-font-color); 55 | --navbar-menu-icon-filter: invert(10%); /* matches --color-jet-80 */ 56 | --navbar-menu_hover-background: var(--color-smoke-50); 57 | /* nav */ 58 | --nav-background: var(--panel-background); 59 | --nav-border-color: var(--color-gray-10); 60 | --nav-line-height: 1.35; 61 | --nav-heading-font-color: var(--color-brand-primary); 62 | --nav-muted-color: var(--color-gray-70); 63 | --nav-muted-icon-filter: invert(36.5%); /* matches --color-gray-70 */ 64 | --nav-panel-divider-color: var(--color-smoke-90); 65 | --nav-secondary-background: var(--color-smoke-70); 66 | /* toolbar */ 67 | --toolbar-background: var(--panel-background); 68 | --toolbar-border-color: var(--panel-border-color); 69 | --toolbar-font-color: var(--nav-muted-color); 70 | --toolbar-icon-filter: var(--nav-muted-icon-filter); 71 | --toolbar-primary-icon-filter: var(--navbar-menu-icon-filter); 72 | --toolbar-muted-color: var(--color-gray-30); 73 | --page-version-menu-background: var(--color-smoke-70); 74 | --page-version-missing-font-color: var(--color-gray-30); 75 | /* admonitions (see https://coolors.co/802392-e40046-2d7dd2-43b929-ff7700) */ 76 | --caution-color: #802392; 77 | --caution-on-color: var(--color-white); 78 | --important-color: var(--color-brand-primary); 79 | --important-on-color: var(--color-white); 80 | --note-color: #2d7dd2; 81 | --note-on-color: var(--color-white); 82 | --tip-color: #43b929; 83 | --tip-on-color: var(--color-white); 84 | --warning-color: #f70; 85 | --warning-on-color: var(--color-white); 86 | /* doc */ 87 | /* 88 | --doc-font-color: #242424; 89 | --doc-font-color: #24292e; 90 | */ 91 | --doc-font-color: #24262b; 92 | --doc-icon-filter: invert(14.5%); /* matches #242424 */ 93 | --doc-font-size: inherit; 94 | --doc-font-size--desktop: calc(17 / var(--rem-base) * 1rem); 95 | --doc-line-height: 1.6; 96 | --doc-margin: 0 auto; 97 | --doc-margin--desktop: 0 2rem; 98 | --heading-font-color: var(--color-jet-80); 99 | --heading-font-weight: normal; 100 | --alt-heading-font-weight: var(--body-font-weight-bold); 101 | --section-divider-color: var(--panel-border-color); 102 | /* 103 | --link-font-color: #048ba8; blue munsell 104 | --link-font-color: #2e86ab; blue NCS 105 | --link-font-color: #2c7fa2; blue NCS (with increased contrast) 106 | --link-font-color: #218380; celadon green 107 | */ 108 | --link-font-color: #2c7fa2; 109 | --link_hover-font-color: #08658c; /* brightness(75%) saturate(150%) */ 110 | --link_unresolved-font-color: var(--important-color); 111 | --link_unresolved-hover-font-color: #ec003a; 112 | --abstract-background: var(--color-smoke-70); 113 | --abstract-font-color: var(--color-jet-20); 114 | --abstract-border-color: var(--panel-border-color); 115 | --admonition-background: var(--panel-background); 116 | --admonition-label-font-weight: var(--body-font-weight-bold); 117 | --caption-font-color: var(--color-gray-70); 118 | --caption-font-style: italic; 119 | --caption-font-weight: var(--body-font-weight-bold); 120 | --code-background: var(--panel-background); 121 | --code-font-color: var(--body-font-color); 122 | --example-background: var(--color-white); 123 | --example-border-color: var(--color-gray-70); 124 | --kbd-background: var(--panel-background); 125 | --kbd-border-color: var(--color-gray-10); 126 | --pre-background: var(--panel-background); 127 | --pre-border-color: var(--panel-border-color); 128 | --pre-annotation-font-color: var(--color-gray-50); 129 | --pre-annotation-icon-filter: invert(50.2%); /* matches --color-gray-50 */ 130 | --quote-background: var(--panel-background); 131 | --quote-border-color: var(--color-gray-70); 132 | --quote-font-color: var(--color-gray-70); 133 | --quote-attribution-font-color: var(--color-gray-30); 134 | --sidebar-background: var(--color-smoke-90); 135 | --table-border-color: var(--panel-border-color); 136 | --table-stripe-background: var(--panel-background); 137 | --table-footer-background: linear-gradient(to bottom, var(--color-smoke-70) 0%, var(--color-white) 100%); 138 | /* toc */ 139 | --toc-font-color: var(--nav-muted-color); 140 | --toc-heading-font-color: var(--doc-font-color); 141 | --toc-border-color: var(--panel-border-color); 142 | --toc-line-height: 1.2; 143 | /* footer */ 144 | --footer-line-height: var(--doc-line-height); 145 | --footer-background: var(--color-jet-80); 146 | --footer-font-color: var(--color-gray-15); 147 | --footer-link-font-color: var(--color-white); 148 | /* dimensions and positioning */ 149 | --navbar-height: calc(63 / var(--rem-base) * 1rem); 150 | --toolbar-height: calc(45 / var(--rem-base) * 1rem); 151 | --drawer-height: var(--toolbar-height); 152 | --body-top: var(--navbar-height); 153 | --body-min-height: calc(var(--viewport-height) - var(--body-top)); 154 | --nav-height: calc(var(--body-min-height) - var(--toolbar-height)); 155 | --nav-height--desktop: var(--body-min-height); 156 | --nav-panel-menu-height: calc(100% - var(--drawer-height)); 157 | --nav-panel-explore-height: calc(50% + var(--drawer-height)); 158 | --nav-width: calc(270 / var(--rem-base) * 1rem); 159 | --toc-top: calc(var(--body-top) + var(--toolbar-height)); 160 | --toc-height: calc(var(--viewport-height) - var(--toc-top) - 2.5rem); 161 | --toc-width: calc(162 / var(--rem-base) * 1rem); 162 | --toc-width--widescreen: calc(216 / var(--rem-base) * 1rem); 163 | --doc-max-width: calc(720 / var(--rem-base) * 1rem); 164 | --doc-max-width--desktop: calc(828 / var(--rem-base) * 1rem); 165 | /* stacking */ 166 | --z-index-doc: 1; 167 | --z-index-nav: 2; 168 | --z-index-toolbar: 3; 169 | --z-index-page-version-menu: 4; 170 | --z-index-footer: 5; 171 | --z-index-navbar: 6; 172 | } 173 | -------------------------------------------------------------------------------- /src/css/vendor/docsearch.css: -------------------------------------------------------------------------------- 1 | @import "docsearch.js/dist/cdn/docsearch.css"; 2 | 3 | .algolia-autocomplete-results { 4 | position: absolute; 5 | top: 100%; 6 | width: inherit; 7 | } 8 | 9 | .algolia-autocomplete { 10 | left: auto !important; 11 | width: inherit; 12 | } 13 | 14 | .algolia-autocomplete .ds-dropdown-menu { 15 | background: var(--color-white); 16 | border: 1px solid var(--navbar-button-border-color); 17 | border-radius: var(--navbar-menu-border-radius); 18 | box-shadow: var(--navbar-menu-box-shadow); 19 | display: flex !important; 20 | flex-direction: column; 21 | /* -4rem accounts for the height of the input, which is below the navbar */ 22 | max-height: calc(var(--viewport-height) - var(--navbar-height) - 4rem); 23 | max-width: none; 24 | min-width: auto; 25 | position: absolute; 26 | top: 0; 27 | width: inherit; 28 | } 29 | 30 | .algolia-autocomplete .ds-dropdown-menu::before { 31 | border-color: var(--navbar-button-border-color); 32 | border-radius: 0; 33 | } 34 | 35 | .algolia-autocomplete .ds-dropdown-menu .ds-dataset-1 { 36 | background: none; 37 | border: none; 38 | border-radius: 0; 39 | overscroll-behavior: none; 40 | padding: 0 8px; 41 | } 42 | 43 | .algolia-autocomplete .ds-dropdown-menu .ds-suggestions { 44 | margin: 4px 0 8px; 45 | } 46 | 47 | .algolia-autocomplete .algolia-docsearch-suggestion { 48 | background: none; 49 | } 50 | 51 | .algolia-autocomplete .algolia-docsearch-suggestion--title { 52 | font-weight: var(--body-font-weight-bold); 53 | } 54 | 55 | .algolia-autocomplete .algolia-docsearch-suggestion:hover .algolia-docsearch-suggestion--title, 56 | .algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--title { 57 | text-decoration: underline; 58 | } 59 | 60 | .algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column { 61 | font-weight: normal; 62 | } 63 | 64 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column::after { 65 | display: none; 66 | } 67 | 68 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column-text::after { 69 | content: "|"; 70 | padding: 0 0.5em; 71 | } 72 | 73 | .algolia-autocomplete .ds-footer { 74 | display: flex; 75 | justify-content: space-between; 76 | background-color: rgba(164, 167, 174, 0.1); 77 | border-top: 1px solid rgba(225, 225, 225, 0.5); 78 | padding: 10px 8px 8px; 79 | } 80 | 81 | .algolia-autocomplete .ds-pagination { 82 | display: flex; 83 | align-items: center; 84 | font-size: 0.8em; 85 | line-height: 1; 86 | } 87 | 88 | .algolia-autocomplete .ds-pagination > :not(:first-child) { 89 | margin-left: 0.5em; 90 | } 91 | 92 | .algolia-autocomplete .ds-pagination a { 93 | color: #174d8c; 94 | } 95 | 96 | .algolia-autocomplete .ds-pagination--prev::before, 97 | .algolia-autocomplete .ds-pagination--next::after { 98 | display: inline-block; 99 | width: 0.5em; 100 | font-size: 1.2em; 101 | line-height: 0; 102 | } 103 | 104 | .algolia-autocomplete .ds-pagination--prev::before { 105 | content: "\2039"; 106 | } 107 | 108 | .algolia-autocomplete .ds-pagination--next::after { 109 | content: "\203a"; 110 | text-align: right; 111 | } 112 | 113 | .algolia-autocomplete .algolia-docsearch-footer { 114 | margin-top: 0; 115 | width: 112px; 116 | height: 16px; 117 | } 118 | 119 | @media screen and (min-width: 769px) { 120 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column-text::after { 121 | display: none; 122 | } 123 | } 124 | 125 | @media screen and (min-width: 1024px) { 126 | .algolia-autocomplete .ds-dropdown-menu.ds-with-1 { 127 | width: 28rem; 128 | } 129 | 130 | .algolia-autocomplete .ds-dropdown-menu { 131 | max-height: calc(var(--viewport-height) - var(--navbar-height)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/css/vendor/tabs.css: -------------------------------------------------------------------------------- 1 | @import "@asciidoctor/tabs" 2 | -------------------------------------------------------------------------------- /src/helpers/and.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (...args) => { 4 | const numArgs = args.length 5 | if (numArgs === 3) return args[0] && args[1] 6 | if (numArgs < 3) throw new Error('{{and}} helper expects at least 2 arguments') 7 | args.pop() 8 | return args.every((it) => it) 9 | } 10 | -------------------------------------------------------------------------------- /src/helpers/detag.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const TAG_ALL_RX = /<[^>]+>/g 4 | 5 | module.exports = (html) => html && html.replace(TAG_ALL_RX, '') 6 | -------------------------------------------------------------------------------- /src/helpers/dig.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (object, path) => { 4 | if (object == null) return 5 | const paths = path.split('.') 6 | let result = object 7 | for (let i = 0, len = paths.length; i < len; i++) { 8 | if (!(result = result[paths[i]])) break 9 | } 10 | return result 11 | } 12 | -------------------------------------------------------------------------------- /src/helpers/endsWith.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (subject, string) => subject.endsWith(string) 4 | -------------------------------------------------------------------------------- /src/helpers/eq.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (a, b) => a === b 4 | -------------------------------------------------------------------------------- /src/helpers/increment.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (value) => (value || 0) + 1 4 | -------------------------------------------------------------------------------- /src/helpers/matches.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (subject, pattern) => new RegExp(pattern).test(subject) 4 | -------------------------------------------------------------------------------- /src/helpers/ne.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (a, b) => a !== b 4 | -------------------------------------------------------------------------------- /src/helpers/not.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (val) => !val 4 | -------------------------------------------------------------------------------- /src/helpers/or.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (...args) => { 4 | const numArgs = args.length 5 | if (numArgs === 3) return args[0] || args[1] 6 | if (numArgs < 3) throw new Error('{{or}} helper expects at least 2 arguments') 7 | args.pop() 8 | return args.some((it) => it) 9 | } 10 | -------------------------------------------------------------------------------- /src/helpers/rearrange.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (collection, property, orderSpec) => { 4 | if (orderSpec == null || orderSpec === '*') return Object.values(collection) 5 | const sourceCollection = Object.values(collection).reduce((accum, it) => accum.set(it[property], it), new Map()) 6 | const order = orderSpec 7 | .split(',') 8 | .map((it) => it.trim()) 9 | .filter((it) => { 10 | if (it.charAt() !== '!') return true 11 | sourceCollection.delete(it.slice(1)) 12 | }) 13 | const restIdx = order.indexOf('*') 14 | if (~restIdx) order.splice(restIdx, 1) 15 | const targetCollection = order.reduce((accum, key) => { 16 | if (sourceCollection.has(key)) { 17 | accum.push(sourceCollection.get(key)) 18 | sourceCollection.delete(key) 19 | } 20 | return accum 21 | }, []) 22 | if (~restIdx) targetCollection.splice(restIdx, 0, ...sourceCollection.values()) 23 | return targetCollection 24 | } 25 | -------------------------------------------------------------------------------- /src/helpers/siteStartPage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = ({ 4 | data: { 5 | root: { contentCatalog = { getSiteStartPage () {} } }, 6 | }, 7 | }) => contentCatalog.getSiteStartPage() 8 | -------------------------------------------------------------------------------- /src/helpers/spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (page) => { 4 | const segments = [] 5 | if (page.component.latest !== page.componentVersion) segments.push(`${page.version}@`) 6 | segments.push(`${page.component.name}:`) 7 | segments.push(page.module === 'ROOT' ? ':' : `${page.module}:`) 8 | segments.push(page.relativeSrcPath) 9 | return segments.join('') 10 | } 11 | -------------------------------------------------------------------------------- /src/helpers/year.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = () => new Date().getFullYear().toString() 4 | -------------------------------------------------------------------------------- /src/img/algolia-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | Algolia logo 3 | Algolia logo - https://algolia.com - License: All rights reserved 4 | 8 | 9 | 10 | Algolia logo 11 | image/svg+xml 12 | 13 | 14 | Algolia 15 | 16 | 17 | 18 | 19 | Copyright (c) 2020 Algolia, Inc. 20 | 21 | 22 | https://algolia.com 23 | 24 | 25 | 26 | 27 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/img/asciidoctor-logo.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 13 | image/svg+xml 14 | 16 | Asciidoctor 17 | 18 | 19 | Asciidoctor 20 | 21 | 22 | Asciidoctor project logo. 23 | 24 | 25 | AsciiDoc 26 | 27 | 28 | https://asciidoctor.org 29 | 30 | 31 | 32 | 33 | 36 | 39 | 42 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/img/asciidoctor-og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-docs-ui/9a521169424222b109a33ae21b30c39878350327/src/img/asciidoctor-og.png -------------------------------------------------------------------------------- /src/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-docs-ui/9a521169424222b109a33ae21b30c39878350327/src/img/favicon.ico -------------------------------------------------------------------------------- /src/img/octicons-16.svg: -------------------------------------------------------------------------------- 1 | 2 | Octicons (16px subset) 3 | Octicons v11.2.0 by GitHub - https://primer.style/octicons/ - License: MIT 4 | 8 | 9 | 10 | @primer/octicons 11 | 11.2.0 12 | A scalable set of icons handcrafted with <3 by GitHub 13 | image/svg+xml 14 | 15 | 16 | GitHub 17 | 18 | 19 | 20 | 21 | Copyright (c) 2020 GitHub Inc. 22 | 23 | 24 | 25 | https://primer.style/octicons/ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/img/octicons-24.svg: -------------------------------------------------------------------------------- 1 | 2 | Octicons (24px subset) 3 | Octicons v11.2.0 by GitHub - https://primer.style/octicons/ - License: MIT 4 | 8 | 9 | 10 | @primer/octicons 11 | 11.2.0 12 | A scalable set of icons handcrafted with <3 by GitHub 13 | image/svg+xml 14 | 15 | 16 | GitHub 17 | 18 | 19 | 20 | 21 | Copyright (c) 2020 GitHub Inc. 22 | 23 | 24 | 25 | https://primer.style/octicons/ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/img/twitter-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | Twitter logo 3 | Twitter logo - https://about.twitter.com/en/who-we-are/brand-toolkit - License: All rights reserved 4 | 8 | 9 | 10 | Twitter logo 11 | image/svg+xml 12 | 13 | 14 | Twitter 15 | 16 | 17 | 18 | 19 | Copyright (c) 2021 Twitter, Inc. 20 | 21 | 22 | https://about.twitter.com/en/who-we-are/brand-toolkit 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/img/zulip-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | Zulip logo 3 | Zulip logo - https://zulipc.com - License: All rights reserved 4 | 8 | 9 | 10 | Zulip logo 11 | image/svg+xml 12 | 13 | 14 | Zulip 15 | 16 | 17 | 18 | 19 | Copyright (c) 2023 Kandra Labs, Inc. 20 | 21 | 22 | https://zulip.com 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/js/01-nav.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var SECT_CLASS_RX = /^sect[0-5](?=$| )/ 5 | 6 | var navContainer = document.querySelector('.nav-container') 7 | if (!navContainer) return 8 | var navToggle = document.querySelector('.toolbar .nav-toggle') 9 | 10 | navToggle.addEventListener('click', showNav) 11 | navContainer.addEventListener('click', trapEvent) 12 | 13 | var nav = navContainer.querySelector('.nav') 14 | var navMenuToggle = navContainer.querySelector('.nav-menu-toggle') 15 | var menuPanel = nav.querySelector('[data-panel=menu]') 16 | var navBounds = { encroachingElement: document.querySelector('footer.footer') } 17 | var currentPageItem 18 | 19 | window.addEventListener('load', fitNavInit) /* needed if images shift the content */ 20 | window.addEventListener('resize', fitNavInit) 21 | 22 | if (!menuPanel) return fitNavInit({}) 23 | 24 | if (menuPanel.classList.contains('is-loading')) { 25 | if ((currentPageItem = findItemForHash() || menuPanel.querySelector('.is-current-url'))) { 26 | activateCurrentPath(currentPageItem) 27 | scrollItemToMidpoint(menuPanel, currentPageItem) 28 | } else { 29 | menuPanel.scrollTop = 0 30 | } 31 | menuPanel.classList.remove('is-loading') 32 | } else { 33 | var match = (currentPageItem = menuPanel.querySelector('.is-current-page')) 34 | if ((!match || match.classList.contains('is-provisional')) && (match = findItemForHash(true))) { 35 | var update = !!currentPageItem 36 | activateCurrentPath((currentPageItem = match), update) 37 | scrollItemToMidpoint(menuPanel, currentPageItem) 38 | } 39 | } 40 | 41 | fitNavInit({}) 42 | 43 | find(menuPanel, '.nav-item-toggle').forEach(function (btn) { 44 | btn.addEventListener('click', toggleActive.bind(btn.parentElement)) 45 | var nextElement = btn.nextElementSibling 46 | if (nextElement && nextElement.classList.contains('nav-text')) { 47 | nextElement.style.cursor = 'pointer' 48 | nextElement.addEventListener('click', toggleActive.bind(btn.parentElement)) 49 | } 50 | }) 51 | 52 | if (navMenuToggle && menuPanel.querySelector('.nav-item-toggle')) { 53 | navMenuToggle.style.display = '' 54 | navMenuToggle.addEventListener('click', function () { 55 | var collapse = !this.classList.toggle('is-active') 56 | find(menuPanel, '.nav-item > .nav-item-toggle').forEach(function (btn) { 57 | collapse ? btn.parentElement.classList.remove('is-active') : btn.parentElement.classList.add('is-active') 58 | }) 59 | if (currentPageItem) { 60 | if (collapse) activateCurrentPath(currentPageItem) 61 | scrollItemToMidpoint(menuPanel, currentPageItem) 62 | } else { 63 | menuPanel.scrollTop = 0 64 | } 65 | }) 66 | } 67 | 68 | nav.querySelector('[data-panel=explore] .context').addEventListener('click', function () { 69 | find(nav, '[data-panel]').forEach(function (panel) { 70 | // NOTE logic assumes there are only two panels 71 | panel.classList.toggle('is-active') 72 | }) 73 | }) 74 | 75 | // NOTE prevent text from being selected by double click 76 | menuPanel.addEventListener('mousedown', function (e) { 77 | if (e.detail > 1) e.preventDefault() 78 | }) 79 | 80 | function onHashChange () { 81 | var navItem = findItemForHash() || menuPanel.querySelector('.is-current-url') 82 | if (!navItem || navItem === currentPageItem) return 83 | activateCurrentPath((currentPageItem = navItem), true) 84 | scrollItemToMidpoint(menuPanel, currentPageItem) 85 | } 86 | 87 | if (menuPanel.querySelector('.nav-link[href^="#"]')) window.addEventListener('hashchange', onHashChange) 88 | 89 | function activateCurrentPath (navItem, update) { 90 | if (update) { 91 | find(menuPanel, '.nav-item.is-active').forEach(function (el) { 92 | el.classList.remove('is-current-path', 'is-current-page', 'is-active') 93 | }) 94 | } 95 | var ancestor = navItem 96 | while ((ancestor = ancestor.parentNode) && ancestor !== menuPanel) { 97 | if (ancestor.classList.contains('nav-item')) ancestor.classList.add('is-current-path', 'is-active') 98 | } 99 | navItem.classList.add('is-current-page', 'is-active') 100 | } 101 | 102 | function toggleActive () { 103 | if (this.classList.toggle('is-active')) { 104 | var padding = parseFloat(window.getComputedStyle(this).marginTop) 105 | var rect = this.getBoundingClientRect() 106 | var menuPanelRect = menuPanel.getBoundingClientRect() 107 | var overflowY = Math.round(rect.bottom - menuPanelRect.top - menuPanelRect.height + padding) 108 | if (overflowY > 0) menuPanel.scrollTop += Math.min(Math.round(rect.top - menuPanelRect.top - padding), overflowY) 109 | } 110 | } 111 | 112 | function showNav (e) { 113 | if (navToggle.classList.contains('is-active')) return hideNav(e) 114 | trapEvent(e) 115 | var html = document.documentElement 116 | if (/mobi/i.test(window.navigator.userAgent)) { 117 | if (Math.round(parseFloat(window.getComputedStyle(html).minHeight)) !== window.innerHeight) { 118 | html.style.setProperty('--vh', window.innerHeight / 100 + 'px') 119 | } else { 120 | html.style.removeProperty('--vh') 121 | } 122 | } 123 | html.classList.add('is-clipped--nav') 124 | navToggle.classList.add('is-active') 125 | navContainer.classList.add('is-active') 126 | html.addEventListener('click', hideNav) 127 | } 128 | 129 | function hideNav (e) { 130 | trapEvent(e) 131 | var html = document.documentElement 132 | html.classList.remove('is-clipped--nav') 133 | navToggle.classList.remove('is-active') 134 | navContainer.classList.remove('is-active') 135 | html.removeEventListener('click', hideNav) 136 | } 137 | 138 | function trapEvent (e) { 139 | e.stopPropagation() 140 | } 141 | 142 | function findItemForHash (articleOnly) { 143 | var hash = window.location.hash 144 | if (!hash) return 145 | if (hash.indexOf('%')) hash = decodeURIComponent(hash) 146 | if (hash.indexOf('"')) hash = hash.replace(/(?=")/g, '\\') 147 | var navLink = !articleOnly && menuPanel.querySelector('.nav-link[href="' + hash + '"]') 148 | if (navLink) return navLink.parentNode 149 | var target = document.getElementById(hash.slice(1)) 150 | if (!target) return 151 | var scope = document.querySelector('article.doc') 152 | var ancestor = target 153 | while ((ancestor = ancestor.parentNode) && ancestor !== scope) { 154 | var id = ancestor.id 155 | if (!id) id = SECT_CLASS_RX.test(ancestor.className) && (ancestor.firstElementChild || {}).id 156 | if (id && (navLink = menuPanel.querySelector('.nav-link[href="#' + id + '"]'))) return navLink.parentNode 157 | } 158 | } 159 | 160 | function scrollItemToMidpoint (panel, item) { 161 | var panelRect = panel.getBoundingClientRect() 162 | if (panel.scrollHeight === Math.round(panelRect.height)) return // not scrollable 163 | var linkRect = item.querySelector('.nav-link').getBoundingClientRect() 164 | panel.scrollTop += Math.round(linkRect.top - panelRect.top - (panelRect.height - linkRect.height) * 0.5) 165 | } 166 | 167 | function find (from, selector) { 168 | return [].slice.call(from.querySelectorAll(selector)) 169 | } 170 | 171 | function fitNavInit (e) { 172 | window.removeEventListener('scroll', fitNav) 173 | if (window.getComputedStyle(navContainer).position === 'fixed') return 174 | navBounds.availableHeight = window.innerHeight 175 | navBounds.preferredHeight = navContainer.getBoundingClientRect().height 176 | if (fitNav() && e.type !== 'resize' && currentPageItem) scrollItemToMidpoint(menuPanel, currentPageItem) 177 | window.addEventListener('scroll', fitNav) 178 | } 179 | 180 | function fitNav () { 181 | var scrollDatum = menuPanel && menuPanel.scrollTop + menuPanel.offsetHeight 182 | var occupied = navBounds.availableHeight - navBounds.encroachingElement.getBoundingClientRect().top 183 | var modified = 184 | occupied > 0 185 | ? nav.style.height !== (nav.style.height = Math.max(Math.round(navBounds.preferredHeight - occupied), 0) + 'px') 186 | : !!nav.style.removeProperty('height') 187 | if (menuPanel) menuPanel.scrollTop = scrollDatum - menuPanel.offsetHeight 188 | return modified 189 | } 190 | })() 191 | -------------------------------------------------------------------------------- /src/js/02-on-this-page.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var sidebar = document.querySelector('aside.toc.sidebar') 5 | if (!sidebar) return 6 | if (document.querySelector('body.-toc')) return sidebar.parentNode.removeChild(sidebar) 7 | var levels = parseInt(sidebar.dataset.levels || 2, 10) 8 | if (levels < 0) return 9 | 10 | var articleSelector = 'article.doc' 11 | var article = document.querySelector(articleSelector) 12 | var headingsSelector = [] 13 | for (var level = 0; level <= levels; level++) { 14 | var headingSelector = [articleSelector] 15 | if (level) { 16 | for (var l = 1; l <= level; l++) headingSelector.push((l === 2 ? '.sectionbody>' : '') + '.sect' + l) 17 | headingSelector.push('h' + (level + 1) + '[id]' + (level > 1 ? ':not(.discrete)' : '')) 18 | } else { 19 | headingSelector.push('h1[id].sect0') 20 | } 21 | headingsSelector.push(headingSelector.join('>')) 22 | } 23 | var headings = find(headingsSelector.join(','), article.parentNode) 24 | if (!headings.length) return sidebar.parentNode.removeChild(sidebar) 25 | 26 | var lastActiveFragment 27 | var links = {} 28 | var list = headings.reduce(function (accum, heading) { 29 | var link = document.createElement('a') 30 | link.textContent = heading.textContent 31 | links[(link.href = '#' + heading.id)] = link 32 | var listItem = document.createElement('li') 33 | listItem.dataset.level = parseInt(heading.nodeName.slice(1), 10) - 1 34 | listItem.appendChild(link) 35 | accum.appendChild(listItem) 36 | return accum 37 | }, document.createElement('ul')) 38 | 39 | var menu = sidebar.querySelector('.toc-menu') 40 | if (!menu) (menu = document.createElement('div')).className = 'toc-menu' 41 | 42 | var title = document.createElement('h3') 43 | title.textContent = sidebar.dataset.title || 'Contents' 44 | menu.appendChild(title) 45 | menu.appendChild(list) 46 | 47 | var startOfContent = !document.getElementById('toc') && article.querySelector('h1.page ~ :not(.is-before-toc)') 48 | if (startOfContent) { 49 | var embeddedToc = document.createElement('aside') 50 | embeddedToc.className = 'toc embedded' 51 | embeddedToc.appendChild(menu.cloneNode(true)) 52 | startOfContent.parentNode.insertBefore(embeddedToc, startOfContent) 53 | } 54 | 55 | window.addEventListener('load', function () { 56 | onScroll() 57 | window.addEventListener('scroll', onScroll) 58 | }) 59 | 60 | function onScroll () { 61 | var scrolledBy = window.pageYOffset 62 | var buffer = getNumericStyleVal(document.documentElement, 'fontSize') * 1.15 63 | var ceil = article.offsetTop 64 | if (scrolledBy && window.innerHeight + scrolledBy + 2 >= document.documentElement.scrollHeight) { 65 | lastActiveFragment = Array.isArray(lastActiveFragment) ? lastActiveFragment : Array(lastActiveFragment || 0) 66 | var activeFragments = [] 67 | var lastIdx = headings.length - 1 68 | headings.forEach(function (heading, idx) { 69 | var fragment = '#' + heading.id 70 | if (idx === lastIdx || heading.getBoundingClientRect().top + getNumericStyleVal(heading, 'paddingTop') > ceil) { 71 | activeFragments.push(fragment) 72 | if (lastActiveFragment.indexOf(fragment) < 0) links[fragment].classList.add('is-active') 73 | } else if (~lastActiveFragment.indexOf(fragment)) { 74 | links[lastActiveFragment.shift()].classList.remove('is-active') 75 | } 76 | }) 77 | list.scrollTop = list.scrollHeight - list.offsetHeight 78 | lastActiveFragment = activeFragments.length > 1 ? activeFragments : activeFragments[0] 79 | return 80 | } 81 | if (Array.isArray(lastActiveFragment)) { 82 | lastActiveFragment.forEach(function (fragment) { 83 | links[fragment].classList.remove('is-active') 84 | }) 85 | lastActiveFragment = undefined 86 | } 87 | var activeFragment 88 | headings.some(function (heading) { 89 | if (heading.getBoundingClientRect().top + getNumericStyleVal(heading, 'paddingTop') - buffer > ceil) return true 90 | activeFragment = '#' + heading.id 91 | }) 92 | if (activeFragment) { 93 | if (activeFragment === lastActiveFragment) return 94 | if (lastActiveFragment) links[lastActiveFragment].classList.remove('is-active') 95 | var activeLink = links[activeFragment] 96 | activeLink.classList.add('is-active') 97 | if (list.scrollHeight > list.offsetHeight) { 98 | list.scrollTop = Math.max(0, activeLink.offsetTop + activeLink.offsetHeight - list.offsetHeight) 99 | } 100 | lastActiveFragment = activeFragment 101 | } else if (lastActiveFragment) { 102 | links[lastActiveFragment].classList.remove('is-active') 103 | lastActiveFragment = undefined 104 | } 105 | } 106 | 107 | function find (selector, from) { 108 | return [].slice.call((from || document).querySelectorAll(selector)) 109 | } 110 | 111 | function getNumericStyleVal (el, prop) { 112 | return parseFloat(window.getComputedStyle(el)[prop]) 113 | } 114 | })() 115 | -------------------------------------------------------------------------------- /src/js/03-fragment-jumper.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var article = document.querySelector('article.doc') 5 | var toolbar = document.querySelector('.toolbar') 6 | var supportsScrollToOptions = 'scrollTo' in document.documentElement 7 | 8 | function decodeFragment (hash) { 9 | return hash && (~hash.indexOf('%') ? decodeURIComponent(hash) : hash).slice(1) 10 | } 11 | 12 | function computePosition (el, sum) { 13 | return article.contains(el) ? computePosition(el.offsetParent, el.offsetTop + sum) : sum 14 | } 15 | 16 | function jumpToAnchor (e) { 17 | if (e) { 18 | if (e.altKey || e.ctrlKey) return 19 | window.location.hash = '#' + this.id 20 | e.preventDefault() 21 | } 22 | var y = computePosition(this, 0) - toolbar.getBoundingClientRect().bottom 23 | var instant = e === false && supportsScrollToOptions 24 | instant ? window.scrollTo({ left: 0, top: y, behavior: 'instant' }) : window.scrollTo(0, y) 25 | } 26 | 27 | window.addEventListener('load', function jumpOnLoad (e) { 28 | var fragment, target 29 | if ((fragment = decodeFragment(window.location.hash)) && (target = document.getElementById(fragment))) { 30 | jumpToAnchor.call(target, false) 31 | setTimeout(jumpToAnchor.bind(target, false), 250) 32 | } 33 | window.removeEventListener('load', jumpOnLoad) 34 | }) 35 | 36 | Array.prototype.slice.call(document.querySelectorAll('a[href^="#"]')).forEach(function (el) { 37 | var fragment, target 38 | if ((fragment = decodeFragment(el.hash)) && (target = document.getElementById(fragment))) { 39 | el.addEventListener('click', jumpToAnchor.bind(target)) 40 | } 41 | }) 42 | })() 43 | -------------------------------------------------------------------------------- /src/js/04-page-versions.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var toggle = document.querySelector('.page-versions .version-menu-toggle') 5 | if (!toggle) return 6 | 7 | var selector = document.querySelector('.page-versions') 8 | 9 | toggle.addEventListener('click', function (e) { 10 | selector.classList.toggle('is-active') 11 | e.stopPropagation() // trap event 12 | }) 13 | 14 | document.documentElement.addEventListener('click', function () { 15 | selector.classList.remove('is-active') 16 | }) 17 | })() 18 | -------------------------------------------------------------------------------- /src/js/05-mobile-navbar.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var navbarBurger = document.querySelector('.navbar-burger') 5 | if (!navbarBurger) return 6 | navbarBurger.addEventListener('click', toggleNavbarMenu.bind(navbarBurger)) 7 | 8 | function toggleNavbarMenu (e) { 9 | e.stopPropagation() // trap event 10 | var html = document.documentElement 11 | var menu = document.getElementById(this.getAttribute('aria-controls') || this.dataset.target) 12 | if (!menu.classList.contains('is-active') && /mobi/i.test(window.navigator.userAgent)) { 13 | if (Math.round(parseFloat(window.getComputedStyle(html).minHeight)) !== window.innerHeight) { 14 | html.style.setProperty('--vh', window.innerHeight / 100 + 'px') 15 | } else { 16 | html.style.removeProperty('--vh') 17 | } 18 | } 19 | html.classList.toggle('is-clipped--navbar') 20 | navbarBurger.setAttribute('aria-expanded', this.classList.toggle('is-active')) 21 | menu.classList.toggle('is-active') 22 | } 23 | })() 24 | -------------------------------------------------------------------------------- /src/js/06-copy-to-clipboard.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var CMD_RX = /^\$ (\S[^\\\n]*(\\\n(?!\$ )[^\\\n]*)*)(?=\n|$)/gm 5 | var LINE_CONTINUATION_RX = /( ) *\\\n *|\\\n( ?) */g 6 | var TRAILING_SPACE_RX = / +$/gm 7 | 8 | var config = (document.getElementById('site-script') || { dataset: {} }).dataset 9 | var supportsCopy = window.navigator.clipboard 10 | var svgAs = config.svgAs 11 | var uiRootPath = (config.uiRootPath == null ? window.uiRootPath : config.uiRootPath) || '.' 12 | 13 | ;[].slice.call(document.querySelectorAll('.doc pre.highlight, .doc .literalblock pre')).forEach(function (pre) { 14 | var code, language, lang, copy, toast, toolbox 15 | if (pre.classList.contains('highlight')) { 16 | code = pre.querySelector('code') 17 | if ((language = code.dataset.lang) && language !== 'console') { 18 | ;(lang = document.createElement('span')).className = 'source-lang' 19 | lang.appendChild(document.createTextNode(language)) 20 | } 21 | } else if (pre.innerText.startsWith('$ ')) { 22 | var block = pre.parentNode.parentNode 23 | block.classList.remove('literalblock') 24 | block.classList.add('listingblock') 25 | pre.classList.add('highlightjs', 'highlight') 26 | ;(code = document.createElement('code')).className = 'language-console hljs' 27 | code.dataset.lang = 'console' 28 | code.appendChild(pre.firstChild) 29 | pre.appendChild(code) 30 | } else { 31 | return 32 | } 33 | ;(toolbox = document.createElement('div')).className = 'source-toolbox' 34 | if (lang) toolbox.appendChild(lang) 35 | if (supportsCopy) { 36 | ;(copy = document.createElement('button')).className = 'copy-button' 37 | copy.setAttribute('title', 'Copy to clipboard') 38 | if (svgAs === 'svg') { 39 | var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') 40 | svg.setAttribute('class', 'copy-icon') 41 | var use = document.createElementNS('http://www.w3.org/2000/svg', 'use') 42 | use.setAttribute('href', uiRootPath + '/img/octicons-16.svg#icon-clippy') 43 | svg.appendChild(use) 44 | copy.appendChild(svg) 45 | } else { 46 | var img = document.createElement('img') 47 | img.src = uiRootPath + '/img/octicons-16.svg#view-clippy' 48 | img.alt = 'copy icon' 49 | img.className = 'copy-icon' 50 | copy.appendChild(img) 51 | } 52 | ;(toast = document.createElement('span')).className = 'copy-toast' 53 | toast.appendChild(document.createTextNode('Copied!')) 54 | copy.appendChild(toast) 55 | toolbox.appendChild(copy) 56 | } 57 | pre.parentNode.appendChild(toolbox) 58 | if (copy) copy.addEventListener('click', writeToClipboard.bind(copy, code)) 59 | }) 60 | 61 | function extractCommands (text) { 62 | var cmds = [] 63 | var m 64 | while ((m = CMD_RX.exec(text))) cmds.push(m[1].replace(LINE_CONTINUATION_RX, '$1$2')) 65 | return cmds.join(' && ') 66 | } 67 | 68 | function writeToClipboard (code) { 69 | var text = code.innerText.replace(TRAILING_SPACE_RX, '') 70 | if (code.dataset.lang === 'console' && text.startsWith('$ ')) text = extractCommands(text) 71 | window.navigator.clipboard.writeText(text).then( 72 | function () { 73 | this.classList.add('clicked') 74 | this.offsetHeight // eslint-disable-line no-unused-expressions 75 | this.classList.remove('clicked') 76 | }.bind(this), 77 | function () {} 78 | ) 79 | } 80 | })() 81 | -------------------------------------------------------------------------------- /src/js/07-copy-page-spec.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | if (!window.navigator.clipboard) return 5 | 6 | var HEADING_RX = /H[2-6]/ 7 | 8 | var pageSpec = (document.querySelector('head meta[name=page-spec]') || {}).content 9 | var editPageLink = document.querySelector('.toolbar .edit-this-page a') 10 | if (!(pageSpec && editPageLink)) return 11 | if (editPageLink) editPageLink.addEventListener('click', onEditPageLinkClick) 12 | ;[].slice.call(document.querySelectorAll('.doc a.anchor')).forEach(function (anchor) { 13 | if (HEADING_RX.test(anchor.parentNode.tagName)) anchor.addEventListener('click', onSectionAnchorClick.bind(anchor)) 14 | }) 15 | 16 | function onEditPageLinkClick (e) { 17 | if (e.altKey) writeToClipboard(pageSpec) 18 | } 19 | 20 | function onSectionAnchorClick (e) { 21 | if (e.altKey) { 22 | e.preventDefault() 23 | writeToClipboard(pageSpec + decodeFragment(this.hash)) 24 | } 25 | } 26 | 27 | function decodeFragment (hash) { 28 | return ~hash.indexOf('%') ? decodeURIComponent(hash) : hash 29 | } 30 | 31 | function writeToClipboard (text) { 32 | window.navigator.clipboard.writeText(text).then( 33 | function () {}, 34 | function () {} 35 | ) 36 | } 37 | })() 38 | -------------------------------------------------------------------------------- /src/js/vendor/docsearch.bundle.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var FORWARD_BACK_TYPE = 2 5 | var SEARCH_FILTER_ACTIVE_KEY = 'docs:search-filter-active' 6 | var SAVED_SEARCH_STATE_KEY = 'docs:saved-search-state' 7 | var SAVED_SEARCH_STATE_VERSION = '1' 8 | 9 | activateSearch(require('docsearch.js/dist/cdn/docsearch.js'), document.getElementById('search-script').dataset) 10 | 11 | function activateSearch (docsearch, config) { 12 | appendStylesheet(config.stylesheet) 13 | var baseAlgoliaOptions = { 14 | hitsPerPage: parseInt(config.pageSize, 10) || 20, // cannot exceed the hitsPerPage value defined on the index 15 | } 16 | var searchField = document.getElementById(config.searchFieldId || 'search') 17 | searchField.appendChild(Object.assign(document.createElement('div'), { className: 'algolia-autocomplete-results' })) 18 | var controller = docsearch({ 19 | appId: config.appId, 20 | apiKey: config.apiKey, 21 | indexName: config.indexName, 22 | inputSelector: '#' + searchField.id + ' .query', 23 | autocompleteOptions: { 24 | autoselect: false, 25 | debug: true, 26 | hint: false, 27 | minLength: 2, 28 | appendTo: '#' + searchField.id + ' .algolia-autocomplete-results', 29 | autoWidth: false, 30 | templates: { 31 | footer: 32 | '', 40 | }, 41 | }, 42 | baseAlgoliaOptions: baseAlgoliaOptions, 43 | }) 44 | var input = controller.input 45 | var typeahead = input.data('aaAutocomplete') 46 | var dropdown = typeahead.dropdown 47 | var menu = dropdown.$menu 48 | var dataset = dropdown.datasets[0] 49 | dataset.cache = false 50 | dataset.source = controller.getAutocompleteSource(undefined, processQuery.bind(typeahead, controller)) 51 | delete dataset.templates.footer 52 | controller.queryDataCallback = processQueryData.bind(typeahead) 53 | typeahead.setVal() // clear value on page reload 54 | input.on('autocomplete:closed', clearSearch.bind(typeahead)) 55 | input.on('autocomplete:cursorchanged autocomplete:cursorremoved', saveSearchState.bind(typeahead)) 56 | input.on('autocomplete:selected', onSuggestionSelected.bind(typeahead)) 57 | input.on('autocomplete:updated', onResultsUpdated.bind(typeahead)) 58 | dropdown._ensureVisible = ensureVisible 59 | menu.off('mousedown.aa') 60 | menu.off('mouseenter.aa') 61 | menu.off('mouseleave.aa') 62 | var suggestionSelector = '.' + dropdown.cssClasses.prefix + dropdown.cssClasses.suggestion 63 | menu.on('mousedown.aa', suggestionSelector, onSuggestionMouseDown.bind(dropdown)) 64 | typeahead.$facetFilterInput = input 65 | .closest('#' + searchField.id) 66 | .find('.filter input') 67 | .on('change', toggleFilter.bind(typeahead)) 68 | .prop('checked', window.localStorage.getItem(SEARCH_FILTER_ACTIVE_KEY) === 'true') 69 | menu.find('.ds-pagination--prev').on('click', paginate.bind(typeahead, -1)).css('visibility', 'hidden') 70 | menu.find('.ds-pagination--next').on('click', paginate.bind(typeahead, 1)).css('visibility', 'hidden') 71 | monitorCtrlKey.call(typeahead) 72 | searchField.addEventListener('click', confineEvent) 73 | document.documentElement.addEventListener('click', clearSearch.bind(typeahead)) 74 | document.addEventListener('keydown', handleShortcuts.bind(typeahead)) 75 | if (input.attr('autofocus') != null) input.focus() 76 | window.addEventListener('pageshow', reactivateSearch.bind(typeahead)) 77 | } 78 | 79 | function reactivateSearch (e) { 80 | var navigation = window.performance.navigation || {} 81 | if ('type' in navigation) { 82 | if (navigation.type !== FORWARD_BACK_TYPE) { 83 | return 84 | } else if (e.persisted && !isClosed(this)) { 85 | this.$input.focus() 86 | this.$input.val(this.getVal()) 87 | this.dropdown.datasets[0].page = this.dropdown.$menu.find('.ds-pagination--curr').data('page') 88 | } else if (window.sessionStorage.getItem('docs:restore-search-on-back') === 'true') { 89 | if (!window.matchMedia('(min-width: 1024px)').matches) document.querySelector('.navbar-burger').click() 90 | restoreSearch.call(this) 91 | } 92 | } 93 | window.sessionStorage.removeItem('docs:restore-search-on-back') 94 | } 95 | 96 | function appendStylesheet (href) { 97 | document.head.appendChild(Object.assign(document.createElement('link'), { rel: 'stylesheet', href: href })) 98 | } 99 | 100 | function onResultsUpdated () { 101 | var dropdown = this.dropdown 102 | var restoring = dropdown.restoring 103 | delete dropdown.restoring 104 | if (isClosed(this)) return 105 | updatePagination.call(dropdown) 106 | if (restoring && restoring.query === this.getVal() && restoring.filter === this.$facetFilterInput.prop('checked')) { 107 | var cursor = restoring.cursor 108 | if (cursor) dropdown._moveCursor(cursor) 109 | } else { 110 | saveSearchState.call(this) 111 | } 112 | } 113 | 114 | function toggleFilter (e) { 115 | if ('restoring' in this.dropdown) return 116 | window.localStorage.setItem(SEARCH_FILTER_ACTIVE_KEY, e.target.checked) 117 | isClosed(this) ? this.$input.focus() : (this.dropdown.datasets[0].page = 0) || requery.call(this) 118 | } 119 | 120 | function confineEvent (e) { 121 | e.stopPropagation() 122 | } 123 | 124 | function ensureVisible (el) { 125 | var container = getScrollableResultsContainer(this)[0] 126 | if (container.scrollHeight === container.offsetHeight) return 127 | var delta 128 | var item = el[0] 129 | if ((delta = 15 + item.offsetTop + item.offsetHeight - (container.offsetHeight + container.scrollTop)) > 0) { 130 | container.scrollTop += delta 131 | } 132 | if ((delta = item.offsetTop - container.scrollTop) < 0) { 133 | container.scrollTop += delta 134 | } 135 | } 136 | 137 | function getScrollableResultsContainer (dropdown) { 138 | return dropdown.datasets[0].$el 139 | } 140 | 141 | function handleShortcuts (e) { 142 | var target = e.target || {} 143 | if (e.altKey || target.isContentEditable || 'disabled' in target) return 144 | if (e.ctrlKey && e.key === '<') return restoreSearch.call(this) 145 | if (e.ctrlKey ? e.key === '/' : e.key === 's') { 146 | this.$input.focus() 147 | e.preventDefault() 148 | e.stopPropagation() 149 | } 150 | } 151 | 152 | function isClosed (typeahead) { 153 | var query = typeahead.getVal() 154 | return !query || query !== typeahead.dropdown.datasets[0].query 155 | } 156 | 157 | function monitorCtrlKey () { 158 | this.$input.on('keydown', onCtrlKeyDown.bind(this)) 159 | this.dropdown.$menu.on('keyup', onCtrlKeyUp.bind(this)) 160 | } 161 | 162 | function onCtrlKeyDown (e) { 163 | if (e.key !== 'Control') return 164 | this.ctrlKeyDown = true 165 | var container = getScrollableResultsContainer(this.dropdown) 166 | var prevScrollTop = container.scrollTop() 167 | this.dropdown.getCurrentCursor().find('a').focus() 168 | container.scrollTop(prevScrollTop) // calling focus can cause the container to scroll, so restore it 169 | } 170 | 171 | function onCtrlKeyUp (e) { 172 | if (e.key !== 'Control') return 173 | delete this.ctrlKeyDown 174 | this.$input.focus() 175 | } 176 | 177 | function onSuggestionMouseDown (e) { 178 | var dropdown = this 179 | var suggestion = dropdown._getSuggestions().filter('#' + e.currentTarget.id) 180 | if (suggestion[0] === dropdown._getCursor()[0]) return 181 | dropdown._removeCursor() 182 | dropdown._setCursor(suggestion, false) 183 | } 184 | 185 | function onSuggestionSelected (e, suggestion, datasetNum, context) { 186 | if (!this.ctrlKeyDown) { 187 | if (context.selectionMethod === 'click') saveSearchState.call(this) 188 | window.sessionStorage.setItem('docs:restore-search-on-back', 'true') 189 | } 190 | e.isDefaultPrevented = function () { 191 | return true 192 | } 193 | } 194 | 195 | function paginate (delta, e) { 196 | e.preventDefault() 197 | var dataset = this.dropdown.datasets[0] 198 | dataset.page = (dataset.page || 0) + delta 199 | requery.call(this) 200 | } 201 | 202 | function updatePagination () { 203 | var result = this.datasets[0].result 204 | var page = result.page 205 | var menu = this.$menu 206 | menu 207 | .find('.ds-pagination--curr') 208 | .html(result.pages ? 'Page ' + (page + 1) + ' of ' + result.pages : 'No results') 209 | .data('page', page) 210 | menu.find('.ds-pagination--prev').css('visibility', page > 0 ? '' : 'hidden') 211 | menu.find('.ds-pagination--next').css('visibility', result.pages > page + 1 ? '' : 'hidden') 212 | getScrollableResultsContainer(this).scrollTop(0) 213 | } 214 | 215 | function requery (query) { 216 | this.$input.focus() 217 | query === undefined ? (query = this.input.getInputValue()) : this.input.setInputValue(query, true) 218 | this.input.setQuery(query) 219 | this.dropdown.update(query) 220 | this.dropdown.open() 221 | } 222 | 223 | function clearSearch () { 224 | this.isActivated = true // we can't rely on this state being correct 225 | this.setVal() 226 | delete this.ctrlKeyDown 227 | delete this.dropdown.datasets[0].result 228 | } 229 | 230 | function processQuery (controller, query) { 231 | var algoliaOptions = {} 232 | if (this.$facetFilterInput.prop('checked')) { 233 | algoliaOptions.facetFilters = [this.$facetFilterInput.data('facetFilter')] 234 | } 235 | var dataset = this.dropdown.datasets[0] 236 | var activeResult = dataset.result 237 | algoliaOptions.page = activeResult && activeResult.query !== query ? (dataset.page = 0) : dataset.page || 0 238 | controller.algoliaOptions = Object.keys(algoliaOptions).length 239 | ? Object.assign({}, controller.baseAlgoliaOptions, algoliaOptions) 240 | : controller.baseAlgoliaOptions 241 | } 242 | 243 | function processQueryData (data) { 244 | var result = data.results[0] 245 | this.dropdown.datasets[0].result = { page: result.page, pages: result.nbPages, query: result.query } 246 | result.hits = preserveHitOrder(result.hits) 247 | } 248 | 249 | // preserves the original order of results by qualifying unique occurrences of the same lvl0 and lvl1 values 250 | function preserveHitOrder (hits) { 251 | var prevLvl0 252 | var lvl0Qualifiers = {} 253 | var lvl1Qualifiers = {} 254 | return hits.map(function (hit) { 255 | var lvl0 = hit.hierarchy.lvl0 256 | var lvl1 = hit.hierarchy.lvl1 257 | var lvl0Qualifier = lvl0Qualifiers[lvl0] 258 | if (lvl0 !== prevLvl0) { 259 | lvl0Qualifiers[lvl0] = lvl0Qualifier == null ? (lvl0Qualifier = '') : (lvl0Qualifier += ' ') 260 | lvl1Qualifiers = {} 261 | } 262 | if (lvl0Qualifier) hit.hierarchy.lvl0 = lvl0 + lvl0Qualifier 263 | if (lvl1 in lvl1Qualifiers) { 264 | hit.hierarchy.lvl1 = lvl1 + (lvl1Qualifiers[lvl1] += ' ') 265 | } else { 266 | lvl1Qualifiers[lvl1] = '' 267 | } 268 | prevLvl0 = lvl0 269 | return hit 270 | }) 271 | } 272 | 273 | function readSavedSearchState () { 274 | try { 275 | var state = window.localStorage.getItem(SAVED_SEARCH_STATE_KEY) 276 | if (state && (state = JSON.parse(state))._version.toString() === SAVED_SEARCH_STATE_VERSION) return state 277 | } catch (e) { 278 | window.localStorage.removeItem(SAVED_SEARCH_STATE_KEY) 279 | } 280 | } 281 | 282 | function restoreSearch () { 283 | var searchState = readSavedSearchState() 284 | if (!searchState) return 285 | this.dropdown.restoring = searchState 286 | this.$facetFilterInput.prop('checked', searchState.filter) // change event will be ignored 287 | var dataset = this.dropdown.datasets[0] 288 | dataset.page = searchState.page 289 | delete dataset.result 290 | requery.call(this, searchState.query) // cursor is restored by onResultsUpdated 291 | } 292 | 293 | function saveSearchState () { 294 | if (isClosed(this)) return 295 | window.localStorage.setItem( 296 | SAVED_SEARCH_STATE_KEY, 297 | JSON.stringify({ 298 | _version: SAVED_SEARCH_STATE_VERSION, 299 | cursor: this.dropdown.getCurrentCursor().index() + 1, 300 | filter: this.$facetFilterInput.prop('checked'), 301 | page: this.dropdown.datasets[0].page, 302 | query: this.getVal(), 303 | }) 304 | ) 305 | } 306 | })() 307 | -------------------------------------------------------------------------------- /src/js/vendor/highlight.bundle.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict' 3 | 4 | var hljs = require('highlight.js/lib/highlight') 5 | hljs.registerLanguage('asciidoc', require('highlight.js/lib/languages/asciidoc')) 6 | //hljs.registerLanguage('bash', require('highlight.js/lib/languages/bash')) 7 | hljs.registerLanguage('clojure', require('highlight.js/lib/languages/clojure')) 8 | //hljs.registerLanguage('cpp', require('highlight.js/lib/languages/cpp')) 9 | //hljs.registerLanguage('cs', require('highlight.js/lib/languages/cs')) 10 | hljs.registerLanguage('css', require('highlight.js/lib/languages/css')) 11 | //hljs.registerLanguage('diff', require('highlight.js/lib/languages/diff')) 12 | //hljs.registerLanguage('dockerfile', require('highlight.js/lib/languages/dockerfile')) 13 | //hljs.registerLanguage('elixir', require('highlight.js/lib/languages/elixir')) 14 | //hljs.registerLanguage('go', require('highlight.js/lib/languages/go')) 15 | hljs.registerLanguage('groovy', require('highlight.js/lib/languages/groovy')) 16 | //hljs.registerLanguage('haskell', require('highlight.js/lib/languages/haskell')) 17 | hljs.registerLanguage('java', require('highlight.js/lib/languages/java')) 18 | hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript')) 19 | hljs.registerLanguage('json', require('highlight.js/lib/languages/json')) 20 | //hljs.registerLanguage('kotlin', require('highlight.js/lib/languages/kotlin')) 21 | //hljs.registerLanguage('makefile', require('highlight.js/lib/languages/makefile')) 22 | hljs.registerLanguage('markdown', require('highlight.js/lib/languages/markdown')) 23 | //hljs.registerLanguage('nix', require('highlight.js/lib/languages/nix')) 24 | //hljs.registerLanguage('objectivec', require('highlight.js/lib/languages/objectivec')) 25 | //hljs.registerLanguage('perl', require('highlight.js/lib/languages/perl')) 26 | //hljs.registerLanguage('php', require('highlight.js/lib/languages/php')) 27 | //hljs.registerLanguage('properties', require('highlight.js/lib/languages/properties')) 28 | hljs.registerLanguage('python', require('highlight.js/lib/languages/python')) 29 | hljs.registerLanguage('ruby', require('highlight.js/lib/languages/ruby')) 30 | hljs.registerLanguage('scala', require('highlight.js/lib/languages/scala')) 31 | hljs.registerLanguage('shell', require('highlight.js/lib/languages/shell')) 32 | //hljs.registerLanguage('sql', require('highlight.js/lib/languages/sql')) 33 | //hljs.registerLanguage('swift', require('highlight.js/lib/languages/swift')) 34 | hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml')) 35 | hljs.registerLanguage('yaml', require('highlight.js/lib/languages/yaml')) 36 | ;[].slice.call(document.querySelectorAll('pre code.hljs[data-lang]')).forEach(function (node) { 37 | hljs.highlightBlock(node) 38 | }) 39 | })() 40 | -------------------------------------------------------------------------------- /src/js/vendor/tabs.bundle.js: -------------------------------------------------------------------------------- 1 | require('@asciidoctor/tabs') 2 | -------------------------------------------------------------------------------- /src/layouts/404.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{> head defaultPageTitle='Page Not Found'}} 5 | 6 | 7 | {{> header}} 8 | {{> body}} 9 | {{> footer}} 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/layouts/default.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{> head defaultPageTitle='Untitled'}} 5 | 6 | 7 | {{> header}} 8 | {{> body}} 9 | {{> footer}} 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/partials/article-404.hbs: -------------------------------------------------------------------------------- 1 |
2 |

{{{or page.title 'Page Not Found'}}}

3 |
4 |

The page you’re looking for does not exist. It may have been moved. You can{{#with site.homeUrl}} return to the start page, or{{/with}} follow one of the links in the navigation on the left.

5 |
6 |
7 |

If you arrived on this page by clicking a link on another site, please notify the owner of that site that the link is broken. 8 | If you typed the URL of this page manually, please double check that you entered the address correctly.

9 |
10 |
11 | 50 | -------------------------------------------------------------------------------- /src/partials/article.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#with page.title}} 3 |

{{{this}}}

4 | {{/with}} 5 | {{{page.contents}}} 6 | {{> pagination}} 7 |
8 | -------------------------------------------------------------------------------- /src/partials/body.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{> nav}} 3 | {{> main}} 4 |
5 | -------------------------------------------------------------------------------- /src/partials/breadcrumbs.hbs: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/partials/edit-this-page.hbs: -------------------------------------------------------------------------------- 1 | {{#if page.attributes.project-url}} 2 | 3 | {{/if}} 4 | {{#if (and page.fileUri (not env.CI))}} 5 | 6 | {{else if (and page.editUrl (or env.FORCE_SHOW_EDIT_PAGE_LINK (not page.origin.private)))}} 7 | 8 | {{/if}} 9 | -------------------------------------------------------------------------------- /src/partials/footer-content.hbs: -------------------------------------------------------------------------------- 1 |
2 | 20 | 25 | 35 |
36 | -------------------------------------------------------------------------------- /src/partials/footer-scripts.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{#with (resolvePage page.relativeSrcPath model=false)}} 5 | {{#unless (eq ./asciidoc.attributes.stem undefined)}} 6 | 22 | 23 | {{/unless}} 24 | {{/with}} 25 | {{#if env.ALGOLIA_API_KEY}} 26 | 27 | {{/if}} 28 | -------------------------------------------------------------------------------- /src/partials/footer.hbs: -------------------------------------------------------------------------------- 1 | {{> footer-content}} 2 | {{> footer-scripts}} 3 | -------------------------------------------------------------------------------- /src/partials/head-icons.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/partials/head-info.hbs: -------------------------------------------------------------------------------- 1 | {{#with (or antoraVersion site.antoraVersion)}} 2 | 3 | {{/with}} 4 | {{#with page.canonicalUrl}} 5 | 6 | 7 | {{/with}} 8 | {{#with page.description}} 9 | 10 | 11 | {{else}} 12 | {{#if page.component}} 13 | 14 | {{/if}} 15 | {{/with}} 16 | {{#with page.keywords}} 17 | 18 | {{/with}} 19 | {{#if page.component}} 20 | 21 | 22 | 23 | 24 | 25 | {{/if}} 26 | {{#unless (eq page.attributes.pagination undefined)}} 27 | {{#with page.previous}} 28 | 29 | {{/with}} 30 | {{#with page.next}} 31 | 32 | {{/with}} 33 | {{/unless}} 34 | {{#if page.component}} 35 | 36 | {{/if}} 37 | -------------------------------------------------------------------------------- /src/partials/head-meta.hbs: -------------------------------------------------------------------------------- 1 | {{!-- Add additional meta tags here --}} 2 | -------------------------------------------------------------------------------- /src/partials/head-prelude.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/partials/head-scripts.hbs: -------------------------------------------------------------------------------- 1 | {{#with site.keys.googleAnalytics}} 2 | 3 | 4 | {{/with}} 5 | -------------------------------------------------------------------------------- /src/partials/head-styles.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#if (eq page.attributes.role 'home')}} 3 | 4 | {{/if}} 5 | 6 | -------------------------------------------------------------------------------- /src/partials/head-title.hbs: -------------------------------------------------------------------------------- 1 | {{{detag (or page.title defaultPageTitle)}}}{{#unless page.home}}{{#with site.title}} | {{this}}{{/with}}{{/unless}} 2 | -------------------------------------------------------------------------------- /src/partials/head.hbs: -------------------------------------------------------------------------------- 1 | {{> head-prelude}} 2 | {{> head-title}} 3 | {{> head-styles}} 4 | {{> head-info}} 5 | {{> head-meta}} 6 | {{> head-scripts}} 7 | {{> head-icons}} 8 | -------------------------------------------------------------------------------- /src/partials/header-content.hbs: -------------------------------------------------------------------------------- 1 |
2 | 109 |
110 | -------------------------------------------------------------------------------- /src/partials/header-scripts.hbs: -------------------------------------------------------------------------------- 1 | {{!-- Add header scripts here --}} 2 | -------------------------------------------------------------------------------- /src/partials/header.hbs: -------------------------------------------------------------------------------- 1 | {{> header-scripts}} 2 | {{> header-content}} 3 | -------------------------------------------------------------------------------- /src/partials/main.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{> toolbar}} 3 |
4 | {{#if (eq page.layout '404')}} 5 | {{> article-404}} 6 | {{else}} 7 | {{> toc}} 8 | {{> article}} 9 | {{/if}} 10 |
11 |
12 | -------------------------------------------------------------------------------- /src/partials/nav-explore.hbs: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /src/partials/nav-menu.hbs: -------------------------------------------------------------------------------- 1 | {{#with page.navigation}} 2 | 43 | {{/with}} 44 | -------------------------------------------------------------------------------- /src/partials/nav-toggle.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/partials/nav-tree.hbs: -------------------------------------------------------------------------------- 1 | {{#if navigation.length}} 2 | 21 | {{/if}} 22 | -------------------------------------------------------------------------------- /src/partials/nav.hbs: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/partials/page-versions.hbs: -------------------------------------------------------------------------------- 1 | {{#with page.versions}} 2 |
3 | 4 |
5 | {{#each this}} 6 | {{./displayVersion}} 9 | {{/each}} 10 |
11 |
12 | {{/with}} 13 | -------------------------------------------------------------------------------- /src/partials/pagination.hbs: -------------------------------------------------------------------------------- 1 | {{#unless (eq page.attributes.pagination undefined)}} 2 | {{#if (or page.previous page.next)}} 3 | 15 | {{/if}} 16 | {{/unless}} 17 | -------------------------------------------------------------------------------- /src/partials/toc.hbs: -------------------------------------------------------------------------------- 1 | {{#unless (eq page.attributes.role 'home')}} 2 | {{#if (matches page.contents ' class="sect[01]"')}} 3 | 6 | {{/if}} 7 | {{/unless}} 8 | -------------------------------------------------------------------------------- /src/partials/toolbar.hbs: -------------------------------------------------------------------------------- 1 | 10 | --------------------------------------------------------------------------------