Skip to content

Commit e2473a9

Browse files
authored
Merge branch 'cloud' into ui-fixes
2 parents 56791bd + 7153fa6 commit e2473a9

File tree

11 files changed

+608
-208
lines changed

11 files changed

+608
-208
lines changed

README.adoc

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
= Couchbase Documentation UI
22
// Variables:
3-
:current-release: cloud-11
3+
:current-release: cloud-18
44
// Settings:
55
:!example-caption:
66
:experimental:
@@ -21,7 +21,8 @@ endif::[]
2121
:url-project: https://github.com/{project-repo-name}
2222
:url-site: https://docs.couchbase.com
2323
:url-repo: git@github.com:{project-repo-name}.git
24-
:url-preview: https://cb-docs-ui.netlify.com
24+
//:url-preview: https://cb-docs-ui.netlify.com
25+
:url-preview: https://cloud--brave-saha-095114.netlify.app
2526
// External URIs:
2627
:url-antora: https://antora.org
2728
:url-git: https://git-scm.com
@@ -143,6 +144,22 @@ In your terminal, execute the following command:
143144
This command installs the dependencies listed in [.path]_package.json_ into the [.path]_node_modules/_ folder inside the project.
144145
This folder does not get included in the UI bundle and should _not_ be committed to the source control repository.
145146

147+
In order for the pro Font Awesome icons work, you must first add the following file to the root folder of the UI project:
148+
149+
..npmrc
150+
----
151+
@fortawesome:registry=https://npm.fontawesome.com/
152+
//npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
153+
----
154+
155+
Then pass the token to npm when running the `npm install` command:
156+
157+
FONTAWESOME_NPM_AUTH_TOKEN=xxxx npm install
158+
159+
If you don't supply this token when installing packages, the pro icons will not be installed, but the UI preview will still work.
160+
Any icon available in the free collection will still be found, but the pro icons will be missing.
161+
When an icon is missing, a missing icon is shown instead.
162+
146163
=== Preview the UI
147164

148165
The UI project is configured to preview offline.

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

Lines changed: 131 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@
33
const asciidoctor = require('asciidoctor.js')()
44
const fs = require('fs-extra')
55
const handlebars = require('handlebars')
6+
const iconPacks = {
7+
fas: (() => {
8+
try {
9+
return require('@fortawesome/pro-solid-svg-icons')
10+
} catch (e) {
11+
return require('@fortawesome/free-solid-svg-icons')
12+
}
13+
})(),
14+
far: (() => {
15+
try {
16+
return require('@fortawesome/pro-regular-svg-icons')
17+
} catch (e) {
18+
return require('@fortawesome/free-regular-svg-icons')
19+
}
20+
})(),
21+
fab: require('@fortawesome/free-brands-svg-icons'),
22+
}
23+
iconPacks.fa = iconPacks.fas
24+
const iconShims = require('@fortawesome/fontawesome-free/js/v4-shims').reduce((accum, it) => {
25+
accum[it[0]] = [it[1] || 'fas', it[2] || it[0]]
26+
return accum
27+
}, {})
628
const { obj: map } = require('through2')
729
const merge = require('merge-stream')
830
const ospath = require('path')
@@ -21,73 +43,83 @@ module.exports = (src, previewSrc, previewDest, sink = () => map()) => (done) =>
2143
),
2244
])
2345
.then(([baseUiModel, { layouts }]) => [{ ...baseUiModel, env: process.env }, layouts])
24-
.then(([baseUiModel, layouts]) =>
46+
.then(([baseUiModel, layouts, iconDefs = new Map()]) =>
2547
vfs
2648
.src('**/*.adoc', { base: previewSrc, cwd: previewSrc })
2749
.pipe(
28-
map((file, enc, next) => {
29-
const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc))
30-
const uiModel = { ...baseUiModel }
31-
uiModel.siteRootPath = siteRootPath
32-
uiModel.siteRootUrl = path.join(siteRootPath, 'index.html')
33-
uiModel.uiRootPath = path.join(siteRootPath, '_')
34-
if (file.stem === '404') {
35-
uiModel.page = { layout: '404', title: 'Page Not Found' }
36-
} else {
37-
const pageModel = (uiModel.page = { ...uiModel.page })
38-
const doc = asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES })
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
50+
map(
51+
(file, enc, next) => {
52+
const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc))
53+
const uiModel = {
54+
...baseUiModel,
55+
preview: true,
56+
siteRootPath,
57+
siteRootUrl: path.join(siteRootPath, 'index.html'),
58+
uiRootPath: path.join(siteRootPath, '_'),
59+
}
60+
if (file.stem === '404') {
61+
uiModel.page = { layout: '404', title: 'Page Not Found' }
5662
} else {
57-
component = pageModel.component = Object.values(uiModel.site.components)[0]
58-
componentVersion = pageModel.componentVersion = component.latest
63+
const pageModel = (uiModel.page = { ...uiModel.page })
64+
const doc = asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES })
65+
const attributes = doc.getAttributes()
66+
pageModel.layout = doc.getAttribute('page-layout', 'default')
67+
pageModel.title = doc.getDocumentTitle()
68+
pageModel.url = '/' + file.relative.slice(0, -5) + '.html'
69+
if (file.stem === 'home') pageModel.home = true
70+
const componentName = doc.getAttribute('page-component-name', pageModel.src.component)
71+
const versionString = doc.getAttribute(
72+
'page-version',
73+
doc.hasAttribute('page-component-name') ? undefined : pageModel.src.version
74+
)
75+
let component
76+
let componentVersion
77+
if (componentName) {
78+
component = pageModel.component = uiModel.site.components[componentName]
79+
componentVersion = pageModel.componentVersion = versionString
80+
? component.versions.find(({ version }) => version === versionString)
81+
: component.latest
82+
} else {
83+
component = pageModel.component = Object.values(uiModel.site.components)[0]
84+
componentVersion = pageModel.componentVersion = component.latest
85+
}
86+
pageModel.module = 'ROOT'
87+
pageModel.relativeSrcPath = file.relative
88+
pageModel.version = componentVersion.version
89+
pageModel.displayVersion = componentVersion.displayVersion
90+
pageModel.editUrl = pageModel.origin.editUrlPattern.replace('%s', file.relative)
91+
pageModel.navigation = componentVersion.navigation || []
92+
pageModel.breadcrumbs = findNavPath(pageModel.url, pageModel.navigation)
93+
if (pageModel.component.versions.length > 1) {
94+
pageModel.versions = pageModel.component.versions.map(
95+
({ version, displayVersion, url }, idx, arr) => {
96+
const pageVersion = { version, displayVersion: displayVersion || version, url }
97+
if (version === component.latest.version) pageVersion.latest = true
98+
if (idx === arr.length - 1) {
99+
delete pageVersion.url
100+
pageVersion.missing = true
101+
}
102+
return pageVersion
103+
}
104+
)
105+
}
106+
pageModel.attributes = Object.entries({ ...attributes, ...componentVersion.asciidoc.attributes })
107+
.filter(([name, val]) => name.startsWith('page-'))
108+
.reduce((accum, [name, val]) => ({ ...accum, [name.substr(5)]: val }), {})
109+
pageModel.contents = Buffer.from(doc.convert())
59110
}
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-
})
111+
file.extname = '.html'
112+
try {
113+
file.contents = Buffer.from(layouts.get(uiModel.page.layout)(uiModel))
114+
registerIconDefs(iconDefs, file)
115+
next(null, file)
116+
} catch (e) {
117+
next(transformHandlebarsError(e, uiModel.page.layout))
77118
}
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())
82-
}
83-
file.extname = '.html'
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-
}
90-
})
119+
},
120+
// NOTE parallel build overwrites default fontawesome-icon-defs.js, so we must use an alternate path
121+
() => writeIconDefs(iconDefs, ospath.join(previewDest, 'fontawesome-icon-defs.js'))
122+
)
91123
)
92124
.pipe(vfs.dest(previewDest))
93125
.on('error', (e) => done)
@@ -98,6 +130,7 @@ function loadSampleUiModel (src) {
98130
return fs.readFile(ospath.join(src, 'ui-model.yml'), 'utf8').then((contents) => {
99131
const uiModel = yaml.safeLoad(contents)
100132
uiModel.env = process.env
133+
if (process.env.DEPLOY_PRIME_URL) uiModel.site.url = process.env.DEPLOY_PRIME_URL
101134
Object.entries(uiModel.site.components).forEach(([name, component]) => {
102135
component.name = name
103136
if (!component.versions) component.versions = [(component.latest = { url: '#' })]
@@ -183,6 +216,37 @@ function findNavPath (currentUrl, node = [], current_path = [], root = true) {
183216
if (root) return []
184217
}
185218

219+
function registerIconDefs (iconDefs, file) {
220+
const contents = file.contents
221+
if (!contents.includes('<i class="fa')) return
222+
const stringContents = contents.toString()
223+
const iconNames = stringContents.match(/<i class="fa[brs]? fa-[^" ]+/g).map((it) => it.substr(10).replace('fa-', ''))
224+
if (!iconNames.length) return
225+
;[...new Set(iconNames)].reduce((accum, iconKey) => {
226+
if (!accum.has(iconKey)) {
227+
const [iconPrefix, iconName] = iconKey.split(' ').slice(0, 2)
228+
let iconDef = (iconPacks[iconPrefix] || {})['fa' + camelCase(iconName)]
229+
if (iconDef) {
230+
return accum.set(iconKey, { ...iconDef, prefix: iconPrefix })
231+
} else if (iconPrefix === 'fa') {
232+
const [realIconPrefix, realIconName] = iconShims[iconName] || []
233+
if (
234+
!accum.has((iconKey = `${realIconPrefix} ${realIconName}`)) &&
235+
realIconName &&
236+
(iconDef = (iconPacks[realIconPrefix] || {})['fa' + camelCase(realIconName)])
237+
) {
238+
return accum.set(iconKey, { ...iconDef, prefix: realIconPrefix })
239+
}
240+
}
241+
}
242+
return accum
243+
}, iconDefs)
244+
}
245+
246+
async function writeIconDefs (iconDefs, to) {
247+
return fs.writeFile(to, `window.FontAwesomeIconDefs = ${JSON.stringify([...iconDefs.values()])}\n`, 'utf8')
248+
}
249+
186250
function relativize (url) {
187251
return url ? (url.charAt() === '#' ? url : url.slice(1)) : '#'
188252
}
@@ -211,3 +275,9 @@ function toPromise (stream) {
211275
.on('finish', () => resolve(data))
212276
)
213277
}
278+
279+
function camelCase (str) {
280+
return str.replace(/(?:^|-)(.)/g, function (_, l) {
281+
return l.toUpperCase()
282+
})
283+
}

0 commit comments

Comments
 (0)