Commit 5b8ee46d authored by Yann DUPONT's avatar Yann DUPONT 🐷
Browse files

Merge remote-tracking branch 'upstream/master'

parents b68341a6 4f54fb8f
......@@ -102,7 +102,7 @@ Now that you have Node.js installed, you can proceed with installing the Gulp CL
You'll need the Gulp command-line interface (CLI) to run the build.
The Gulp CLI package provides the `gulp` command which, in turn, executes the version of Gulp declared by the project.
You should install the Gulp CLI globally (which resolves to a location in your user directory if you're using nvm) using the following command:
You can install the Gulp CLI globally (which resolves to a location in your user directory if you're using nvm) using the following command:
$ npm install -g gulp-cli
......@@ -110,12 +110,13 @@ Verify the Gulp CLI is installed and on your PATH by running:
$ gulp --version
[TIP]
====
If you prefer to install global packages using Yarn, run this command instead:
$ yarn global add gulp-cli
====
Alternately, you can use the `gulp` command that is installed by the project's dependencies.
$ $(npm bin)/gulp --version
Now that you have the prerequisites installed, you can fetch and build the UI project.
......
......@@ -3,12 +3,11 @@
This page explains how to add new fonts to your UI.
These instructions assume you've forked the default UI and are able to customize it.
There are two steps involved:
There are three steps involved:
. Add the font to your UI project
. Add a font-face declaration to your stylesheet
You can then reference the font family in your stylesheet.
. Use the new font in your stylesheet
How you reference the font file in the font-face declaration depends on how you decide to manage it.
You can manage the font with npm or download it manually and add it directly to your UI project.
......@@ -41,8 +40,17 @@ Here are the steps involved.
----
+
The Gulp build recognizes the `~` URL prefix and copies the font from the npm package to the build folder (and hence bundle).
+
You must define one @font-face for each font weight and style combination (e.g., `font-weight: 500` + `font-style: italic`) from the font that you want to use in your stylesheet.
. Import the typeface CSS file you just created into the main stylesheet, [.path]_src/css/site.css_ (adjacent to the other typeface imports):
+
[source,css]
----
@import "typeface-open-sans.css";
----
. Repeat the previous step for each font style and weight you want to use from that package.
. Repeat the previous steps for each font style and weight you want to use from that package.
. Change the CSS to use your newly imported font:
+
[source,css]
......@@ -51,6 +59,8 @@ html {
font-family: "Open Sans", sans;
}
----
+
TIP: If you're building on the default UI, you may instead want to define or update the font family using a variable defined in [.path]_src/css/vars.css_.
. Test the new font by previewing your UI:
......@@ -83,8 +93,17 @@ Create this folder if it does not exist.
----
+
Note that the path is a relative path starting from the [.path]_src/css_ folder to the [.path]_src/font_ folder.
+
You must define one @font-face for each font weight and style combination (e.g., `font-weight: 500` + `font-style: italic`) from the font that you want to use in your stylesheet.
. Repeat the previous step for each font style and weight you want to use.
. Import the typeface CSS file you just created into the main stylesheet, [.path]_src/css/site.css_ (adjacent to the other typeface imports):
+
[source,css]
----
@import "typeface-open-sans.css";
----
. Repeat the previous steps for each font style and weight you want to use.
. Change the CSS to use your newly imported font:
+
[source,css]
......@@ -93,6 +112,8 @@ html {
font-family: "Open Sans", sans;
}
----
+
TIP: If you're building on the default UI, you may instead want to define or update the font family using a variable defined in [.path]_src/css/vars.css_.
. Test the new font by previewing your UI:
......
......@@ -42,7 +42,8 @@ s| [[site]]site
| site.path
| The pathname (i.e., subpath) of the site.url under which the site is hosted (e.g., /docs).
Only set if site.url is defined.
This value is empty if site.url is not defined, has no path segment, or matches /.
Can be removed from the site.url value using a helper (e.g., `deleteSuffix site.url site.path`)
(since Antora 2.1)
| site.title
......@@ -51,6 +52,8 @@ Only set if site.url is defined.
| site.components
| A map of all the components in the site, keyed by component name.
Properties of each component include name, title, url, latest, and versions.
Properties of each version include name (since 2.3), version, displayVersion, prerelease (if set), title, url, asciidoc (since 2.3), and navigation.
The navigation property on each version provides access to the navigation menu for that component version.
| site.ui
| Information about the site UI.
......@@ -65,7 +68,8 @@ s| [[page]]page
| Information about the current page.
| page.title
| The page title (also used as the primary heading).
| The page title in HTML format (often used as the primary heading).
This value may include inline HTML elements and XML character references.
| page.contents
| The main article content in HTML format.
......@@ -90,7 +94,7 @@ Properties include name, title, url, latest, and versions.
| page.componentVersion
| Information about the component version for the current page.
Properties include version, displayVersion, prerelease (if set), title, and url.
Properties include name (since 2.3), version, displayVersion, prerelease (if set), title, url, and asciidoc (since 2.3).
| page.module
| The name of the module for the current page.
......@@ -107,9 +111,10 @@ Each entry has the properties url, string, and missing.
| page.breadcrumbs
| An array of breadcrumb items that represent the current selection in the navigation tree.
Includes text-only and external items.
| page.navigation
| A collection of navigation links for the current page.
| The hierarchical navigation menu for the component version of the current page.
Each navigation item contains the property `content` as well as the optional properties `url` and (child) `items`.
| page.url
......@@ -124,8 +129,18 @@ If there are multiple versions of the component, the canonical URL is the qualif
If there's only a single version of the component, the canonical URL is the qualified URL of the current page.
| page.editUrl
| The URL to edit the current page (typically a web-based editor on the git host).
The only hosts supported right now are github.com, gitlab.com, pagure.io, and bitbucket.org.
| The URL to edit the current page (i.e., activates the web-based editor on the git host).
This value is derived automatically for the hosts github.com, gitlab.com, pagure.io, and bitbucket.org, even if the repository is private.
If the host is not recognized, or you want to customize the value, you can use the `edit_url` key on the content source in the playbook.
The default UI shows an "Edit this Page" link that points to this URL unless the repository is private (i.e., `page.origin.private` is true) or `page.fileUri` is set.
You can force this link to be shown by setting the environment variable `FORCE_SHOW_EDIT_PAGE_LINK` (e.g., `FORCE_SHOW_EDIT_PAGE_LINK=true`) or by customizing the logic in the UI template.
| page.fileUri
| The local file:// URI to edit the current page if the page originates from the local filesystem (i.e., the worktree).
If this property is set, the default UI shows an "Edit this Page" link that points to this URI (instead of the `page.editUrl` value) unless the `CI` environment variable is set (e.g., `CI=true`).
When the `CI` environment variable is set, the default UI ignores this property (since linking to a local file:// URI in a published site doesn't make any sense).
| page.origin.private
| This value will be true if the repository requires authentication or the repository URL embeds credentials.
......@@ -140,13 +155,13 @@ Then, the "Edit the Page" link will not appear.
| The page layout for the current page.
| page.next
| The next logical page in the navigation tree.
| The next reachable page in the navigation tree (skips past text-only and external items).
| page.previous
| The previous logical page in the navigation tree.
| The previous reachable page in the navigation tree (skips past text-only and external items).
| page.parent
| The parent page in the navigation tree. Will resolve to grandparent if parent item is not a page.
| The parent page in the navigation tree (skips past text-only and external items).
s| env
| The map of environment variables (sourced from `process.env`).
......
'use strict'
const log = require('fancy-log')
const { obj: map } = require('through2')
const PluginError = require('plugin-error')
const prettierEslint = require('prettier-eslint')
......@@ -17,9 +18,9 @@ module.exports = () => {
.concat(' file')
.concat(report.unchanged === 1 ? '' : 's')
.concat(' unchanged')
console.log(`prettier-eslint: ${changed}; ${unchanged}`)
log(`prettier-eslint: ${changed}; ${unchanged}`)
} else {
console.log(`prettier-eslint: left ${report.unchanged} file${report.unchanged === 1 ? '' : 's'} unchanged`)
log(`prettier-eslint: left ${report.unchanged} file${report.unchanged === 1 ? '' : 's'} unchanged`)
}
})
......
'use strict'
// NOTE remove patch after upgrading from asciidoctor.js to @asciidoctor/core
Error.call = (self, ...args) => {
const err = new Error(...args)
return Object.assign(self, { message: err.message, stack: err.stack })
}
const asciidoctor = require('asciidoctor.js')()
const fs = require('fs-extra')
const handlebars = require('handlebars')
......@@ -11,27 +17,17 @@ const requireFromString = require('require-from-string')
const vfs = require('vinyl-fs')
const yaml = require('js-yaml')
const ASCIIDOC_ATTRIBUTES = {
experimental: '',
icons: 'font',
sectanchors: '',
'source-highlighter': 'highlight.js',
}
const ASCIIDOC_ATTRIBUTES = { experimental: '', icons: 'font', sectanchors: '', 'source-highlighter': 'highlight.js' }
module.exports = (src, previewSrc, previewDest, sink = () => map(), layouts = {}) => () =>
module.exports = (src, previewSrc, previewDest, sink = () => map()) => (done) =>
Promise.all([
loadSampleUiModel(previewSrc),
toPromise(
merge(
compileLayouts(src, layouts),
registerPartials(src),
registerHelpers(src),
copyImages(previewSrc, previewDest)
)
merge(compileLayouts(src), registerPartials(src), registerHelpers(src), copyImages(previewSrc, previewDest))
),
])
.then(([baseUiModel]) => ({ ...baseUiModel, env: process.env }))
.then((baseUiModel) =>
.then(([baseUiModel, { layouts }]) => [{ ...baseUiModel, env: process.env }, layouts])
.then(([baseUiModel, layouts]) =>
vfs
.src('**/*.adoc', { base: previewSrc, cwd: previewSrc })
.pipe(
......@@ -57,11 +53,16 @@ module.exports = (src, previewSrc, previewDest, sink = () => map(), layouts = {}
uiModel.page.contents = Buffer.from(doc.convert())
}
file.extname = '.html'
file.contents = Buffer.from(layouts[uiModel.page.layout](uiModel))
next(null, file)
try {
file.contents = Buffer.from(layouts.get(uiModel.page.layout)(uiModel))
next(null, file)
} catch (e) {
next(transformHandlebarsError(e, uiModel.page.layout))
}
})
)
.pipe(vfs.dest(previewDest))
.on('error', (e) => done)
.pipe(sink())
)
......@@ -89,17 +90,28 @@ function registerHelpers (src) {
)
}
function compileLayouts (src, layouts) {
function compileLayouts (src) {
const layouts = new Map()
return vfs.src('layouts/*.hbs', { base: src, cwd: src }).pipe(
map((file, enc, next) => {
layouts[file.stem] = handlebars.compile(file.contents.toString(), { preventIndent: true })
next()
})
map(
(file, enc, next) => {
const srcName = path.join(src, file.relative)
layouts.set(file.stem, handlebars.compile(file.contents.toString(), { preventIndent: true, srcName }))
next()
},
function (done) {
this.push({ layouts })
done()
}
)
)
}
function copyImages (src, dest) {
return vfs.src('**/*.{png,svg}', { base: src, cwd: src }).pipe(vfs.dest(dest))
return vfs
.src('**/*.{png,svg}', { base: src, cwd: src })
.pipe(vfs.dest(dest))
.pipe(map((file, enc, next) => next()))
}
function resolvePage (spec, context = {}) {
......@@ -110,6 +122,19 @@ function resolvePageURL (spec, context = {}) {
if (spec) return '/' + (spec = spec.split(':').pop()).slice(0, spec.lastIndexOf('.')) + '.html'
}
function transformHandlebarsError ({ message, stack }, layout) {
const m = stack.match(/^ *at Object\.ret \[as (.+?)\]/m)
const templatePath = `src/${m ? 'partials/' + m[1] : 'layouts/' + layout}.hbs`
const err = new Error(`${message}${~message.indexOf('\n') ? '\n^ ' : ' '}in UI template ${templatePath}`)
err.stack = [err.toString()].concat(stack.substr(message.length + 8)).join('\n')
return err
}
function toPromise (stream) {
return new Promise((resolve, reject) => stream.on('error', reject).on('finish', resolve))
return new Promise((resolve, reject, data = {}) =>
stream
.on('error', reject)
.on('data', (chunk) => chunk.constructor === Object && Object.assign(data, chunk))
.on('finish', () => resolve(data))
)
}
......@@ -24,6 +24,15 @@ module.exports = (src, dest, preview) => () => {
const sourcemaps = preview || process.env.SOURCEMAPS === 'true'
const postcssPlugins = [
postcssImport,
(css, { messages, opts: { file } }) =>
Promise.all(
messages
.reduce((accum, { file: depPath, type }) => (type === 'dependency' ? accum.concat(depPath) : accum), [])
.map((importedPath) => fs.stat(importedPath).then(({ mtime }) => mtime))
).then((mtimes) => {
const newestMtime = mtimes.reduce((max, curr) => (!max || curr > max ? curr : max))
if (newestMtime > file.stat.mtime) file.stat.mtimeMs = +(file.stat.mtime = newestMtime)
}),
postcssUrl([
{
filter: '**/~typeface-*/files/*',
......@@ -37,16 +46,19 @@ module.exports = (src, dest, preview) => () => {
},
},
]),
postcssVar({ preserve: preview ? 'preserve-computed' : false }),
postcssVar({ preserve: preview }),
preview ? postcssCalc : () => {},
autoprefixer,
preview ? () => {} : cssnano({ preset: 'default' }),
preview
? () => {}
: (css, result) => cssnano({ preset: 'default' })(css, result).then(() => postcssPseudoElementFixer(css, result)),
]
return merge(
vfs
.src('js/+([0-9])-*.js', { ...opts, sourcemaps })
.pipe(uglify())
// NOTE concat already uses stat from newest combined file
.pipe(concat('js/site.js')),
vfs
.src('js/vendor/*.js', { ...opts, read: false })
......@@ -54,11 +66,22 @@ module.exports = (src, dest, preview) => () => {
// see https://gulpjs.org/recipes/browserify-multiple-destination.html
map((file, enc, next) => {
if (file.relative.endsWith('.bundle.js')) {
file.contents = browserify(file.relative, { basedir: src, detectGlobals: false })
const mtimePromises = []
const bundlePath = file.path
browserify(file.relative, { basedir: src, detectGlobals: false })
.plugin('browser-pack-flat/plugin')
.bundle()
file.path = file.path.slice(0, file.path.length - 10) + '.js'
next(null, file)
.on('file', (bundledPath) => {
if (bundledPath !== bundlePath) mtimePromises.push(fs.stat(bundledPath).then(({ mtime }) => mtime))
})
.bundle((bundleError, bundleBuffer) =>
Promise.all(mtimePromises).then((mtimes) => {
const newestMtime = mtimes.reduce((max, curr) => (!max || curr > max ? curr : max))
if (newestMtime > file.stat.mtime) file.stat.mtimeMs = +(file.stat.mtime = newestMtime)
if (bundleBuffer !== undefined) file.contents = bundleBuffer
file.path = file.path.slice(0, file.path.length - 10) + '.js'
next(bundleError, file)
})
)
} else {
fs.readFile(file.path, 'UTF-8').then((contents) => {
file.contents = Buffer.from(contents)
......@@ -69,7 +92,11 @@ module.exports = (src, dest, preview) => () => {
)
.pipe(buffer())
.pipe(uglify()),
vfs.src('css/site.css', { ...opts, sourcemaps }).pipe(postcss(postcssPlugins)),
// NOTE use this statement to bundle a JavaScript library that cannot be browserified, like jQuery
//vfs.src(require.resolve('<package-name-or-require-path>'), opts).pipe(concat('js/vendor/<library-name>.js')),
vfs
.src('css/site.css', { ...opts, sourcemaps })
.pipe(postcss((file) => ({ plugins: postcssPlugins, options: { file } }))),
vfs.src('font/*.{ttf,woff*(2)}', opts),
vfs
.src('img/**/*.{gif,ico,jpg,png,svg}', opts)
......@@ -88,3 +115,9 @@ module.exports = (src, dest, preview) => () => {
vfs.src('partials/*.hbs', opts)
).pipe(vfs.dest(dest, { sourcemaps: sourcemaps && '.' }))
}
function postcssPseudoElementFixer (css, result) {
css.walkRules(/(?:^|[^:]):(?:before|after)/, (rule) => {
rule.selector = rule.selectors.map((it) => it.replace(/(^|[^:]):(before|after)$/, '$1::$2')).join(',')
})
}
......@@ -2,9 +2,10 @@
const vfs = require('vinyl-fs')
const zip = require('gulp-vinyl-zip')
const path = require('path')
module.exports = (src, dest, bundleName) => () =>
module.exports = (src, dest, bundleName, onFinish) => () =>
vfs
.src('**/*', { base: src, cwd: src })
.pipe(zip.zip(`${bundleName}-bundle.zip`))
.pipe(vfs.dest(dest))
.pipe(zip.dest(path.join(dest, `${bundleName}-bundle.zip`)))
.on('finish', () => onFinish && onFinish(path.resolve(dest, `${bundleName}-bundle.zip`)))
......@@ -3,6 +3,7 @@
const { parallel, series, watch } = require('gulp')
const createTask = require('./gulp.d/lib/create-task')
const exportTasks = require('./gulp.d/lib/export-tasks')
const log = require('fancy-log')
const bundleName = 'ui'
const buildDir = 'build'
......@@ -53,7 +54,11 @@ const formatTask = createTask({
const buildTask = createTask({
name: 'build',
desc: 'Build and stage the UI assets for bundling',
call: task.build(srcDir, destDir, process.argv.slice(2).some((name) => name.startsWith('preview'))),
call: task.build(
srcDir,
destDir,
process.argv.slice(2).some((name) => name.startsWith('preview'))
),
})
const bundleBuildTask = createTask({
......@@ -64,7 +69,12 @@ const bundleBuildTask = createTask({
const bundlePackTask = createTask({
name: 'bundle:pack',
desc: 'Create a bundle of the staged UI assets for publishing',
call: task.pack(destDir, buildDir, bundleName),
call: task.pack(
destDir,
buildDir,
bundleName,
(bundlePath) => !process.env.CI && log(`Antora option: --ui-bundle-url=${bundlePath}`)
),
})
const bundleTask = createTask({
......
'use strict'
// This placeholder script allows this package to be discovered using require.resolve.
// It may be used in the future to export information about the files in this UI.
This diff is collapsed.
{
"name": "antora-ui-default",
"name": "@antora/ui-default",
"description": "An archetype project that produces a UI for creating documentation sites with Antora",
"homepage": "https://gitlab.com/antora/antora-ui-default",
"license": "MPL-2.0",
......@@ -25,6 +25,7 @@
"eslint-plugin-node": "~11.1",
"eslint-plugin-promise": "~4.2",
"eslint-plugin-standard": "~4.0",
"fancy-log": "~1.3",
"fs-extra": "~8.1",
"gulp": "~4.0",
"gulp-concat": "~2.6",
......
......@@ -4,6 +4,7 @@ Author Name
:idseparator: -
:!example-caption:
:!table-caption:
:page-pagination:
image:multirepo-ssg.svg[Multirepo SSG,200,float=right]
Platonem complectitur mediocritatem ea eos.
......@@ -19,7 +20,7 @@ Curabitur ut massa aliquam, cursus enim et, accumsan lectus.
== Cu solet
Nominavi luptatum eos, an vim hinc philosophia intellegebat.
Lorem `expetenda` pertinacia et nec, [.underline]#wisi# illud [.line-through]#sonet# qui ea.
Lorem pertinacia `expetenda` et nec, [.underline]#wisi# illud [.line-through]#sonet# qui ea.
Eum an doctus <<liber-recusabo,maiestatis efficiantur>>.
Eu mea inani iriure.
......@@ -84,6 +85,17 @@ src/
HelloWorldTest.java
....
Eu mea munere vituperata constituam.
[%autowidth]
|===
|Input | Output
m|"foo\nbar"
l|foo
bar
|===
Select menu:File[Open Project] to open the project in your IDE.
Per ea btn:[Cancel] inimicus.
Ferri kbd:[F11] tacimates constituam sed ex, eu mea munere vituperata kbd:[Ctrl,T] constituam.
......@@ -231,4 +243,14 @@ Mauris eget leo nunc, nec tempus mi? Curabitur id nisl mi, ut vulputate urna.
Quisque porta facilisis tortor, vitae bibendum velit fringilla vitae!
____
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
[verse]
____
The fog comes
on little cat feet.
____
== Fin
That's all, folks!
......@@ -63,6 +63,14 @@ page:
editUrl: http://example.com/project-xyz/blob/master/index.adoc
origin:
private: false
previous:
content: Quickstart
url: '#'
urlType: 'internal'
next:
content: Liber Recusabo
url: '#'
urlType: 'internal'
breadcrumbs:
- content: Quickstart
url: '#'
......@@ -114,3 +122,6 @@ page:
- content: Importing and Exporting
url: '#'
urlType: fragment
- content: Some Code
url: '/xyz/5.2/index.html#some-code'
urlType: internal
.body {
word-wrap: break-word; /* aka overflow-wrap; used when hyphens are disabled or don't do the trick */
}
@media screen and (min-width: 1024px) {
.body {
display: flex;
......
.doc {
color: var(--doc-font-color);
font-size: var(--doc-font-size);
hyphens: auto;
line-height: var(--doc-line-height);
margin: var(--doc-margin);
max-width: var(--doc-max-width);
......@@ -9,6 +10,7 @@
@media screen and (min-width: 1024px) {
.doc {
flex: auto;
font-size: var(--doc-font-size--desktop);
margin: var(--doc-margin--desktop);
max-width: var(--doc-max-width--desktop);
......@@ -24,6 +26,7 @@
.doc h6 {
color: var(--heading-font-color);
font-weight: var(--heading-font-weight);
hyphens: none;
line-height: 1.3;
margin: 1rem 0 0;
}
......@@ -104,12 +107,6 @@
font-size: inherit;
}
.doc p,
.doc :not(td) > .content,
.doc .tableblock thead {
hyphens: auto;
}
.doc a {
color: var(--link-font-color);
}
......@@ -118,10 +115,6 @@
color: var(--link_hover-font-color);
}
.doc a.bare {
hyphens: none;
}
.doc a.unresolved {
color: var(--link_unresolved-font-color);
}
......@@ -306,7 +299,6 @@
.doc .admonitionblock .icon i::after {
content: attr(title);
hyphens: none;
}
.doc .imageblock {
......@@ -333,10 +325,14 @@
padding: 0.75em 1em;
}