Skip to content

Commit d9c0648

Browse files
authored
Merge pull request #18 from opendevise/new-ui
Backport changes from production UI to cloud (aka new) UI
2 parents 774f145 + c620575 commit d9c0648

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3202
-3393
lines changed

.eslintrc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
"extends": "standard",
33
"rules": {
44
"arrow-parens": ["error", "always"],
5-
"comma-dangle": ["error", "always-multiline"],
5+
"comma-dangle": ["error", {
6+
"arrays": "always-multiline",
7+
"objects": "always-multiline",
8+
"imports": "always-multiline",
9+
"exports": "always-multiline"
10+
}],
611
"max-len": [1, 120, 2],
712
"spaced-comment": "off"
813
}

Jenkinsfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pipeline {
3131
steps {
3232
script {
3333
properties([
34-
[$class: 'GithubProjectProperty', projectUrlStr: 'https://github.com/couchbase/docs-ui'],
34+
[$class: 'GithubProjectProperty', projectUrlStr: env.GIT_URL],
3535
pipelineTriggers([githubPush()]),
3636
])
3737
}

README.adoc

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,61 @@ You can test the feedback widget directly from the preview site by setting the `
360360

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

363+
== View Latest and Canonical URL
364+
365+
This section documents the logic used to compute the URL for the View Latest button and the canonical URL.
366+
367+
=== View Latest
368+
369+
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.
370+
If the version is a prerelease, the banner states that you're viewing a prerelease version.
371+
If the version is an older stable release, the banner states that a newer version is available.
372+
On the banner offers a button named "View Latest" that directs the visitor to the latest version.
373+
374+
The "View Latest" button tries to preserve the current page when switching versions.
375+
If the page is no longer available, then the button directs the user to the start page for the component.
376+
377+
The URL for the "View Latest" button is computed by the latest-page-url helper.
378+
Here's the logic that the helper uses:
379+
380+
* If the current page is found in the latest version, the latest page URL resolves to the URL of that page.
381+
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)
382+
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
383+
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
384+
* 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.
385+
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)
386+
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
387+
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
388+
* If neither the current page or an alias is found in the latest version, the latest page URL resolves to the component start page.
389+
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
390+
391+
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.
392+
In this case, the version segment will only be replaced with "current" if the PRIMARY_SITE_SUPPORTS_CURRENT_URL=true environment variable is set.
393+
394+
=== Canonical URL
395+
396+
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.
397+
The canonical URL can resolve to the current URL (if the current URL is the canonical URL).
398+
399+
The canonical URL is computed by the canonical-url helper.
400+
Here's the logic that the helper uses:
401+
402+
* If the site.url is not set to an absolute path, no value is returned.
403+
* If the current page is found in the latest version, the canonical URL resolves to the URL of that page.
404+
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)
405+
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
406+
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
407+
* 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.
408+
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)
409+
** If the SUPPORTS_CURRENT_URL=true environment variable is set, the version segment in the URL is replaced with the word "current".
410+
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
411+
* 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).
412+
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
413+
** If the SUPPORTS_CURRENT_URL=true environment variable is set, it has no affect on this case.
414+
415+
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.
416+
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.
417+
363418
== Release the UI Bundle
364419

365420
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.

gulp.d/lib/gulp-prettier-eslint.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict'
22

3+
const log = require('fancy-log')
34
const { obj: map } = require('through2')
45
const PluginError = require('plugin-error')
56
const prettierEslint = require('prettier-eslint')
@@ -17,9 +18,9 @@ module.exports = () => {
1718
.concat(' file')
1819
.concat(report.unchanged === 1 ? '' : 's')
1920
.concat(' unchanged')
20-
console.log(`prettier-eslint: ${changed}; ${unchanged}`)
21+
log(`prettier-eslint: ${changed}; ${unchanged}`)
2122
} else {
22-
console.log(`prettier-eslint: left ${report.unchanged} file${report.unchanged === 1 ? '' : 's'} unchanged`)
23+
log(`prettier-eslint: left ${report.unchanged} file${report.unchanged === 1 ? '' : 's'} unchanged`)
2324
}
2425
})
2526

gulp.d/tasks/build-preview-pages.js

Lines changed: 141 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,65 +11,121 @@ const requireFromString = require('require-from-string')
1111
const vfs = require('vinyl-fs')
1212
const yaml = require('js-yaml')
1313

14-
const ASCIIDOC_ATTRIBUTES = {
15-
experimental: '',
16-
icons: 'font',
17-
sectanchors: '',
18-
'source-highlighter': 'highlight.js',
19-
}
14+
const ASCIIDOC_ATTRIBUTES = { experimental: '', icons: 'font', sectanchors: '', 'source-highlighter': 'highlight.js' }
2015

21-
module.exports = (src, previewSrc, previewDest, sink = () => map(), layouts = {}) => () =>
16+
module.exports = (src, previewSrc, previewDest, sink = () => map()) => (done) =>
2217
Promise.all([
2318
loadSampleUiModel(previewSrc),
2419
toPromise(
25-
merge(
26-
compileLayouts(src, layouts),
27-
registerPartials(src),
28-
registerHelpers(src),
29-
copyImages(previewSrc, previewDest)
30-
)
20+
merge(compileLayouts(src), registerPartials(src), registerHelpers(src), copyImages(previewSrc, previewDest))
3121
),
3222
])
33-
.then(([baseUiModel]) => ({ ...baseUiModel, env: process.env }))
34-
.then((baseUiModel) =>
23+
.then(([baseUiModel, { layouts }]) => [{ ...baseUiModel, env: process.env }, layouts])
24+
.then(([baseUiModel, layouts]) =>
3525
vfs
3626
.src('**/*.adoc', { base: previewSrc, cwd: previewSrc })
3727
.pipe(
3828
map((file, enc, next) => {
3929
const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc))
4030
const uiModel = { ...baseUiModel }
41-
uiModel.page = { ...uiModel.page }
4231
uiModel.siteRootPath = siteRootPath
4332
uiModel.siteRootUrl = path.join(siteRootPath, 'index.html')
4433
uiModel.uiRootPath = path.join(siteRootPath, '_')
4534
if (file.stem === '404') {
4635
uiModel.page = { layout: '404', title: 'Page Not Found' }
4736
} else {
37+
const pageModel = (uiModel.page = { ...uiModel.page })
4838
const doc = asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES })
49-
uiModel.page.attributes = Object.entries(doc.getAttributes())
50-
.filter(([name, val]) => name.startsWith('page-'))
51-
.reduce((accum, [name, val]) => {
52-
accum[name.substr(5)] = val
53-
return accum
54-
}, {})
55-
uiModel.page.layout = doc.getAttribute('page-layout', 'default')
56-
uiModel.page.title = doc.getDocumentTitle()
57-
uiModel.page.contents = Buffer.from(doc.convert())
58-
if (file.stem === 'home') {
59-
uiModel.page.component = { ...uiModel.page.component, name: 'home' }
39+
const attributes = doc.getAttributes()
40+
pageModel.layout = doc.getAttribute('page-layout', 'default')
41+
pageModel.title = doc.getDocumentTitle()
42+
pageModel.url = '/' + file.relative.slice(0, -5) + '.html'
43+
if (file.stem === 'home') pageModel.home = true
44+
const componentName = doc.getAttribute('page-component-name', pageModel.src.component)
45+
const versionString = doc.getAttribute(
46+
'page-version',
47+
doc.hasAttribute('page-component-name') ? undefined : pageModel.src.version
48+
)
49+
let component
50+
let componentVersion
51+
if (componentName) {
52+
component = pageModel.component = uiModel.site.components[componentName]
53+
componentVersion = pageModel.componentVersion = versionString
54+
? component.versions.find(({ version }) => version === versionString)
55+
: component.latest
56+
} else {
57+
component = pageModel.component = Object.values(uiModel.site.components)[0]
58+
componentVersion = pageModel.componentVersion = component.latest
59+
}
60+
pageModel.module = 'ROOT'
61+
pageModel.relativeSrcPath = file.relative
62+
pageModel.version = componentVersion.version
63+
pageModel.displayVersion = componentVersion.displayVersion
64+
pageModel.editUrl = pageModel.origin.editUrlPattern.replace('%s', file.relative)
65+
pageModel.navigation = componentVersion.navigation || []
66+
pageModel.breadcrumbs = findNavPath(pageModel.url, pageModel.navigation)
67+
if (pageModel.component.versions.length > 1) {
68+
pageModel.versions = pageModel.component.versions.map(({ version, displayVersion, url }, idx, arr) => {
69+
const pageVersion = { version, displayVersion: displayVersion || version, url }
70+
if (version === component.latest.version) pageVersion.latest = true
71+
if (idx === arr.length - 1) {
72+
delete pageVersion.url
73+
pageVersion.missing = true
74+
}
75+
return pageVersion
76+
})
6077
}
78+
pageModel.attributes = Object.entries({ ...attributes, ...componentVersion.asciidoc.attributes })
79+
.filter(([name, val]) => name.startsWith('page-'))
80+
.reduce((accum, [name, val]) => ({ ...accum, [name.substr(5)]: val }), {})
81+
pageModel.contents = Buffer.from(doc.convert())
6182
}
6283
file.extname = '.html'
63-
file.contents = Buffer.from(layouts[uiModel.page.layout](uiModel))
64-
next(null, file)
84+
try {
85+
file.contents = Buffer.from(layouts.get(uiModel.page.layout)(uiModel))
86+
next(null, file)
87+
} catch (e) {
88+
next(transformHandlebarsError(e, uiModel.page.layout))
89+
}
6590
})
6691
)
6792
.pipe(vfs.dest(previewDest))
93+
.on('error', (e) => done)
6894
.pipe(sink())
6995
)
7096

7197
function loadSampleUiModel (src) {
72-
return fs.readFile(ospath.join(src, 'ui-model.yml'), 'utf8').then((contents) => yaml.safeLoad(contents))
98+
return fs.readFile(ospath.join(src, 'ui-model.yml'), 'utf8').then((contents) => {
99+
const uiModel = yaml.safeLoad(contents)
100+
uiModel.env = process.env
101+
Object.entries(uiModel.site.components).forEach(([name, component]) => {
102+
component.name = name
103+
if (!component.versions) component.versions = [(component.latest = { url: '#' })]
104+
component.versions.forEach((version) => {
105+
Object.defineProperty(version, 'name', { value: component.name, enumerable: true })
106+
if (!('displayVersion' in version)) version.displayVersion = version.version
107+
if (!('asciidoc' in version)) version.asciidoc = { attributes: {} }
108+
})
109+
Object.defineProperties(component, {
110+
asciidoc: {
111+
get () {
112+
return this.latest.asciidoc
113+
},
114+
},
115+
title: {
116+
get () {
117+
return this.latest.title
118+
},
119+
},
120+
url: {
121+
get () {
122+
return this.latest.url
123+
},
124+
},
125+
})
126+
})
127+
return uiModel
128+
})
73129
}
74130

75131
function registerPartials (src) {
@@ -82,6 +138,9 @@ function registerPartials (src) {
82138
}
83139

84140
function registerHelpers (src) {
141+
handlebars.registerHelper('relativize', relativize)
142+
handlebars.registerHelper('resolvePage', resolvePage)
143+
handlebars.registerHelper('resolvePageURL', resolvePageURL)
85144
return vfs.src('helpers/*.js', { base: src, cwd: src }).pipe(
86145
map((file, enc, next) => {
87146
handlebars.registerHelper(file.stem, requireFromString(file.contents.toString()))
@@ -90,19 +149,65 @@ function registerHelpers (src) {
90149
)
91150
}
92151

93-
function compileLayouts (src, layouts) {
152+
function compileLayouts (src) {
153+
const layouts = new Map()
94154
return vfs.src('layouts/*.hbs', { base: src, cwd: src }).pipe(
95-
map(({ stem, contents, basename }, enc, next) => {
96-
layouts[stem] = handlebars.compile(contents.toString(), { srcName: basename, preventIndent: true })
97-
next()
98-
})
155+
map(
156+
(file, enc, next) => {
157+
const srcName = path.join(src, file.relative)
158+
layouts.set(file.stem, handlebars.compile(file.contents.toString(), { preventIndent: true, srcName }))
159+
next()
160+
},
161+
function (done) {
162+
this.push({ layouts })
163+
done()
164+
}
165+
)
99166
)
100167
}
101168

102169
function copyImages (src, dest) {
103170
return vfs.src('**/*.{png,svg}', { base: src, cwd: src }).pipe(vfs.dest(dest))
104171
}
105172

173+
function findNavPath (currentUrl, node = [], current_path = [], root = true) {
174+
for (const item of node) {
175+
const { url, items } = item
176+
if (url === currentUrl) {
177+
return current_path.concat(item)
178+
} else if (items) {
179+
const activePath = findNavPath(currentUrl, items, current_path.concat(item), false)
180+
if (activePath) return activePath
181+
}
182+
}
183+
if (root) return []
184+
}
185+
186+
function relativize (url) {
187+
return url ? (url.charAt() === '#' ? url : url.slice(1)) : '#'
188+
}
189+
190+
function resolvePage (spec, context = {}) {
191+
if (spec) return { pub: { url: resolvePageURL(spec) } }
192+
}
193+
194+
function resolvePageURL (spec, context = {}) {
195+
if (spec) return '/' + (spec = spec.split(':').pop()).slice(0, spec.lastIndexOf('.')) + '.html'
196+
}
197+
198+
function transformHandlebarsError ({ message, stack }, layout) {
199+
const m = stack.match(/^ *at Object\.ret \[as (.+?)\]/m)
200+
const templatePath = `src/${m ? 'partials/' + m[1] : 'layouts/' + layout}.hbs`
201+
const err = new Error(`${message}${~message.indexOf('\n') ? '\n^ ' : ' '}in UI template ${templatePath}`)
202+
err.stack = [err.toString()].concat(stack.substr(message.length + 8)).join('\n')
203+
return err
204+
}
205+
106206
function toPromise (stream) {
107-
return new Promise((resolve, reject) => stream.on('error', reject).on('finish', resolve))
207+
return new Promise((resolve, reject, data = {}) =>
208+
stream
209+
.on('error', reject)
210+
.on('data', (chunk) => chunk.constructor === Object && Object.assign(data, chunk))
211+
.on('finish', () => resolve(data))
212+
)
108213
}

0 commit comments

Comments
 (0)