Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2960f0f
refresh dependency lock file
mojavelinux Jun 16, 2020
b4b16f8
upgrade dependencies
mojavelinux Jun 16, 2020
491a504
when creating a resource bundle, use mtime of newest bundled / import…
mojavelinux Jun 16, 2020
24a6a3e
preserve option for postcss-custom-properties is now a boolean
mojavelinux Jun 16, 2020
fc2ee13
announce path of bundle in the form of the --ui-bundle-url option; co…
mojavelinux Jun 16, 2020
2a3b877
format source code
mojavelinux Jun 16, 2020
0b70510
drop unnecessary check for page component and fallback for antoraVersion
mojavelinux Jun 16, 2020
065472b
enhance preview build to simplify UI model and make it handle multipl…
mojavelinux Jun 16, 2020
8094dc2
use home URL instead of domain root for links to home page
mojavelinux Jun 16, 2020
afc43a2
pull URL for repository from environment variable in CI build
mojavelinux Jun 16, 2020
b86731f
port updates to content of default preview page
mojavelinux Jun 16, 2020
354d360
format source
mojavelinux Jun 16, 2020
30ca380
add support for layout role on table
mojavelinux Jun 16, 2020
a3fa3f3
wrap home page in article.doc and adjust styles to suit
mojavelinux Jun 16, 2020
cc9169a
show and emphasize current version in version selector
mojavelinux Jun 16, 2020
ef18c13
use built-in if-else to avoid unnecessary calls relativize
mojavelinux Jun 16, 2020
56f0d4a
use helper to get current year for footer
mojavelinux Jun 16, 2020
24b339b
derive edit URL from properties in Antora 2.3 UI model instead of usi…
mojavelinux Jun 16, 2020
d624c30
check for role on page before adding
mojavelinux Jun 16, 2020
ecc0765
don't show first breadcrumb if it matches component title or componen…
mojavelinux Jun 16, 2020
1e300bc
use resolvePageURL in header to resolve URL of specific pages
mojavelinux Jun 16, 2020
43b02d2
replace #if statements with #with statements to simplify calls
mojavelinux Jun 16, 2020
296ab16
fix color of status badge
mojavelinux Jun 16, 2020
093910d
use triple handlebars to output uiRootPath and year values
mojavelinux Jun 16, 2020
c4ac8c5
use leaner footer from production site; make link animation snappier
mojavelinux Jun 16, 2020
4346b98
use content catalog to find related SDK pages
mojavelinux Jun 16, 2020
1cb04a4
use discrete scrollbars in Firefox
mojavelinux Jun 16, 2020
2d56908
prepend Version label on component version using CSS to hide it from …
mojavelinux Jun 16, 2020
8e59b24
use custom logic to resolve latest page URL and canonical URL
mojavelinux Jun 16, 2020
66f1fd3
don't indent page.contents variable reference
mojavelinux Jun 16, 2020
f9d2ffd
fix release task
mojavelinux Jun 16, 2020
0032cca
refresh dependency lock file
mojavelinux Jun 16, 2020
c620575
fix stylelint errors
mojavelinux Jun 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
"extends": "standard",
"rules": {
"arrow-parens": ["error", "always"],
"comma-dangle": ["error", "always-multiline"],
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline"
}],
"max-len": [1, 120, 2],
"spaced-comment": "off"
}
Expand Down
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pipeline {
steps {
script {
properties([
[$class: 'GithubProjectProperty', projectUrlStr: 'https://github.com/couchbase/docs-ui'],
[$class: 'GithubProjectProperty', projectUrlStr: env.GIT_URL],
pipelineTriggers([githubPush()]),
])
}
Expand Down
55 changes: 55 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,61 @@ You can test the feedback widget directly from the preview site by setting the `

The configuration for the widget is currently hardcoded into the partial template.

== View Latest and Canonical URL

This section documents the logic used to compute the URL for the View Latest button and the canonical URL.

=== View Latest

If the version of the current page does not match the latest version of the component (i.e., product), a banner is displayed to the visitor.
If the version is a prerelease, the banner states that you're viewing a prerelease version.
If the version is an older stable release, the banner states that a newer version is available.
On the banner offers a button named "View Latest" that directs the visitor to the latest version.

The "View Latest" button tries to preserve the current page when switching versions.
If the page is no longer available, then the button directs the user to the start page for the component.

The URL for the "View Latest" button is computed by the latest-page-url helper.
Here's the logic that the helper uses:

* If the current page is found in the latest version, the latest page URL resolves to the URL of that page.
For example, the latest page URL for https://docs.couchbase.com/server/6.0/introduction/intro.html resolves to https://docs.couchbase.com/server/6.5/introduction/intro.html (assuming 6.5 is the latest version)
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
For example, the latest page URL for https://docs.couchbase.com/server/6.0/introduction/intro.html resolves to https://docs.couchbase.com/server/current/introduction/intro.html
* If the current page is not found in the latest version, but the page is claimed by an alias, the latest page URL resolves to the URL of the page to which the alias points.
For example, the latest page URL for https://docs.couchbase.com/server/5.5/admin/ui-intro.html resolves to https://docs.couchbase.com/server/6.5/manage/management-overview.html (assuming 6.5 is the latest version)
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
For example, the latest page URL for https://docs.couchbase.com/server/5.5/admin/ui-intro.html resolves to https://docs.couchbase.com/server/current/manage/management-overview.html
* If neither the current page or an alias is found in the latest version, the latest page URL resolves to the component start page.
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".

If the current page is in the archive site and the latest version is in the production site, then the latest page URL will point to the production site.
In this case, the version segment will only be replaced with "current" if the PRIMARY_SITE_SUPPORTS_CURRENT_URL=true environment variable is set.

=== Canonical URL

The canonical URL differs slightly from the URL for the "View Latest" button in that if the page cannot be found in the latest version, it instead resolves to the newest version of the page.
The canonical URL can resolve to the current URL (if the current URL is the canonical URL).

The canonical URL is computed by the canonical-url helper.
Here's the logic that the helper uses:

* If the site.url is not set to an absolute path, no value is returned.
* If the current page is found in the latest version, the canonical URL resolves to the URL of that page.
For example, the canonical URL for https://docs.couchbase.com/server/6.0/introduction/intro.html resolves to https://docs.couchbase.com/server/6.5/introduction/intro.html (assuming 6.5 is the latest version)
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
For example, the canonical URL for https://docs.couchbase.com/server/6.0/introduction/intro.html resolves to https://docs.couchbase.com/server/current/introduction/intro.html
* If the current page is not found in the latest version, but the page is claimed by an alias, the canonical URL resolves to the URL of the page to which the alias points.
For example, the canonical URL for https://docs.couchbase.com/server/5.5/admin/ui-intro.html resolves to https://docs.couchbase.com/server/6.5/manage/management-overview.html (assuming 6.5 is the latest version)
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
For example, the canonical URL for https://docs.couchbase.com/server/5.5/admin/ui-intro.html resolves to https://docs.couchbase.com/server/current/manage/management-overview.html
* If neither the current page or an alias is found in the latest version, the current URL resolves to the newest version of the page (which could be the current page).
For example, the canonical URL for https://docs.couchbase.com/server/4.0/architecture/cluster-ram-quotas.html resolves to https://docs.couchbase.com/server/4.1/architecture/cluster-ram-quotas.html
** If the SUPPORTS_CURRENT_URL=true environment variable is set, it has no affect on this case.

If the current page is in the archive site and the latest version is in the production site, then the canonical URL will point to the production site.
In this case, the version segment will only be replaced with "current" if the PRIMARY_SITE_SUPPORTS_CURRENT_URL=true environment variable is set and the newest version of the page is the latest version of the component.

== Release the UI Bundle

Once you're satisfied with the changes you've made to the UI and would like to make those changes available to Antora, you'll need to publish the UI as a bundle by making a release.
Expand Down
5 changes: 3 additions & 2 deletions gulp.d/lib/gulp-prettier-eslint.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const log = require('fancy-log')
const { obj: map } = require('through2')
const PluginError = require('plugin-error')
const prettierEslint = require('prettier-eslint')
Expand All @@ -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`)
}
})

Expand Down
177 changes: 141 additions & 36 deletions gulp.d/tasks/build-preview-pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,65 +11,121 @@ 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(
map((file, enc, next) => {
const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc))
const uiModel = { ...baseUiModel }
uiModel.page = { ...uiModel.page }
uiModel.siteRootPath = siteRootPath
uiModel.siteRootUrl = path.join(siteRootPath, 'index.html')
uiModel.uiRootPath = path.join(siteRootPath, '_')
if (file.stem === '404') {
uiModel.page = { layout: '404', title: 'Page Not Found' }
} else {
const pageModel = (uiModel.page = { ...uiModel.page })
const doc = asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES })
uiModel.page.attributes = Object.entries(doc.getAttributes())
.filter(([name, val]) => name.startsWith('page-'))
.reduce((accum, [name, val]) => {
accum[name.substr(5)] = val
return accum
}, {})
uiModel.page.layout = doc.getAttribute('page-layout', 'default')
uiModel.page.title = doc.getDocumentTitle()
uiModel.page.contents = Buffer.from(doc.convert())
if (file.stem === 'home') {
uiModel.page.component = { ...uiModel.page.component, name: 'home' }
const attributes = doc.getAttributes()
pageModel.layout = doc.getAttribute('page-layout', 'default')
pageModel.title = doc.getDocumentTitle()
pageModel.url = '/' + file.relative.slice(0, -5) + '.html'
if (file.stem === 'home') pageModel.home = true
const componentName = doc.getAttribute('page-component-name', pageModel.src.component)
const versionString = doc.getAttribute(
'page-version',
doc.hasAttribute('page-component-name') ? undefined : pageModel.src.version
)
let component
let componentVersion
if (componentName) {
component = pageModel.component = uiModel.site.components[componentName]
componentVersion = pageModel.componentVersion = versionString
? component.versions.find(({ version }) => version === versionString)
: component.latest
} else {
component = pageModel.component = Object.values(uiModel.site.components)[0]
componentVersion = pageModel.componentVersion = component.latest
}
pageModel.module = 'ROOT'
pageModel.relativeSrcPath = file.relative
pageModel.version = componentVersion.version
pageModel.displayVersion = componentVersion.displayVersion
pageModel.editUrl = pageModel.origin.editUrlPattern.replace('%s', file.relative)
pageModel.navigation = componentVersion.navigation || []
pageModel.breadcrumbs = findNavPath(pageModel.url, pageModel.navigation)
if (pageModel.component.versions.length > 1) {
pageModel.versions = pageModel.component.versions.map(({ version, displayVersion, url }, idx, arr) => {
const pageVersion = { version, displayVersion: displayVersion || version, url }
if (version === component.latest.version) pageVersion.latest = true
if (idx === arr.length - 1) {
delete pageVersion.url
pageVersion.missing = true
}
return pageVersion
})
}
pageModel.attributes = Object.entries({ ...attributes, ...componentVersion.asciidoc.attributes })
.filter(([name, val]) => name.startsWith('page-'))
.reduce((accum, [name, val]) => ({ ...accum, [name.substr(5)]: val }), {})
pageModel.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())
)

function loadSampleUiModel (src) {
return fs.readFile(ospath.join(src, 'ui-model.yml'), 'utf8').then((contents) => yaml.safeLoad(contents))
return fs.readFile(ospath.join(src, 'ui-model.yml'), 'utf8').then((contents) => {
const uiModel = yaml.safeLoad(contents)
uiModel.env = process.env
Object.entries(uiModel.site.components).forEach(([name, component]) => {
component.name = name
if (!component.versions) component.versions = [(component.latest = { url: '#' })]
component.versions.forEach((version) => {
Object.defineProperty(version, 'name', { value: component.name, enumerable: true })
if (!('displayVersion' in version)) version.displayVersion = version.version
if (!('asciidoc' in version)) version.asciidoc = { attributes: {} }
})
Object.defineProperties(component, {
asciidoc: {
get () {
return this.latest.asciidoc
},
},
title: {
get () {
return this.latest.title
},
},
url: {
get () {
return this.latest.url
},
},
})
})
return uiModel
})
}

function registerPartials (src) {
Expand All @@ -82,6 +138,9 @@ function registerPartials (src) {
}

function registerHelpers (src) {
handlebars.registerHelper('relativize', relativize)
handlebars.registerHelper('resolvePage', resolvePage)
handlebars.registerHelper('resolvePageURL', resolvePageURL)
return vfs.src('helpers/*.js', { base: src, cwd: src }).pipe(
map((file, enc, next) => {
handlebars.registerHelper(file.stem, requireFromString(file.contents.toString()))
Expand All @@ -90,19 +149,65 @@ 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(({ stem, contents, basename }, enc, next) => {
layouts[stem] = handlebars.compile(contents.toString(), { srcName: basename, 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))
}

function findNavPath (currentUrl, node = [], current_path = [], root = true) {
for (const item of node) {
const { url, items } = item
if (url === currentUrl) {
return current_path.concat(item)
} else if (items) {
const activePath = findNavPath(currentUrl, items, current_path.concat(item), false)
if (activePath) return activePath
}
}
if (root) return []
}

function relativize (url) {
return url ? (url.charAt() === '#' ? url : url.slice(1)) : '#'
}

function resolvePage (spec, context = {}) {
if (spec) return { pub: { url: resolvePageURL(spec) } }
}

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))
)
}
Loading