From 41dba622a5683fa3d08a1a980c5c4ebd08eba255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Flore-Th=C3=A9bault?= Date: Fri, 21 Jan 2022 12:54:02 +0100 Subject: [PATCH] chore: Update Dockerfile. (#2213) Co-authored-by: Florent BENOIT --- .../workflows/build-and-validate-on-pr.yaml | 30 +-- .github/workflows/build-container.yml | 43 ++++ .github/workflows/publish-netlify.yml | 25 +- .gitignore | 1 + Dockerfile | 22 +- antora-playbook-for-development.yml | 5 + gulpfile.js | 4 +- package.json | 8 - supplemental-ui/.htaccess | 0 supplemental-ui/css/search.css | 76 ++++++ supplemental-ui/js/search-ui.js | 229 ++++++++++++++++++ supplemental-ui/partials/footer-content.hbs | 20 -- supplemental-ui/partials/head-meta.hbs | 3 +- supplemental-ui/partials/header-content.hbs | 10 +- 14 files changed, 392 insertions(+), 84 deletions(-) create mode 100644 .github/workflows/build-container.yml delete mode 100644 package.json delete mode 100644 supplemental-ui/.htaccess create mode 100644 supplemental-ui/css/search.css create mode 100644 supplemental-ui/js/search-ui.js diff --git a/.github/workflows/build-and-validate-on-pr.yaml b/.github/workflows/build-and-validate-on-pr.yaml index a6eb14625e..1f7bc28ab0 100644 --- a/.github/workflows/build-and-validate-on-pr.yaml +++ b/.github/workflows/build-and-validate-on-pr.yaml @@ -8,14 +8,14 @@ # # Name is reused in `publish-netlify.yml` -name: "Build and validate PR" +name: "Build and validate pull request" on: - pull_request jobs: build: - name: link checker # This job name is set as mandatory in the GitHub configuration. + name: "Build and validate pull request" runs-on: ubuntu-20.04 container: "quay.io/eclipse/che-docs:latest" steps: @@ -36,6 +36,7 @@ jobs: run: | echo "::set-output name=yearweek::$(/bin/date -u "+%Y%U")" shell: bash + - name: Restore cache uses: actions/cache@v2 env: @@ -46,30 +47,21 @@ jobs: - name: Build using antora # and fail on warning id: antora-build - run: CI=true antora generate antora-playbook-for-development.yml --stacktrace 2>&1 | (tee | grep WARNING && exit 42 || exit 0) - - - name: Upload artifact doc-content - uses: actions/upload-artifact@v2 - with: - name: doc-content - path: build/site + run: CI=true antora generate antora-playbook-for-development.yml --stacktrace --log-failure-level=warn - - name: Store PR info for publish-netlify + - name: Store pull request details for publish-netlify run: | echo "${{ github.event.number }}" > PR_NUMBER echo "${{ github.event.pull_request.head.sha }}" > PR_SHA - - name: Upload artifact pull-request-number for publish-netlify - uses: actions/upload-artifact@v2 - with: - name: pull-request-number - path: PR_NUMBER - - - name: Upload artifact pull-request-sha for publish-netlify + - name: Upload artifact doc-content uses: actions/upload-artifact@v2 with: - name: pull-request-sha - path: PR_SHA + name: doc-content + path: | + build/site + PR_NUMBER + PR_SHA - name: Validate links using htmltest id: validate-links diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml new file mode 100644 index 0000000000..c4b78af3b1 --- /dev/null +++ b/.github/workflows/build-container.yml @@ -0,0 +1,43 @@ +# +# Copyright (c) 2022 Red Hat, Inc. +# This program and the accompanying materials are made +# available under the terms of the Eclipse Public License 2.0 +# which is available at https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# + +name: Build and publish container + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to docker.io + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + registry: docker.io + - name: Login to quay.io + uses: docker/login-action@v1 + with: + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_PASSWORD }} + registry: quay.io + - name: Docker Build and Push + run: | + SHORT_SHA1=$(git rev-parse --short=7 HEAD) + docker buildx build --platform linux/amd64 -f Dockerfile --push -t quay.io/eclipse/che-docs:${SHORT_SHA1} -t quay.io/eclipse/che-docs:next . diff --git a/.github/workflows/publish-netlify.yml b/.github/workflows/publish-netlify.yml index d21beded39..9095c16980 100644 --- a/.github/workflows/publish-netlify.yml +++ b/.github/workflows/publish-netlify.yml @@ -15,7 +15,7 @@ name: Publish doc-content using netlify on: workflow_run: workflows: - - "Build and validate PR" + - "Build and validate pull request" types: - completed @@ -31,31 +31,18 @@ jobs: name: doc-content path: content - - name: Download pull-request-number artifact - uses: dawidd6/action-download-artifact@v2 - with: - workflow: ${{ github.event.workflow_run.workflow_id }} - name: pull-request-number - - - name: Set PR_NUMBER variable + - name: Set PR_NUMBER and PR_SHA variables in GITHUB_ENV run: | - pr_number=$(cat "PR_NUMBER") + pr_number=$(cat "content/PR_NUMBER") if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then echo "pr number invalid" exit 1 fi echo "PR_NUMBER=$pr_number" >> $GITHUB_ENV - - - name: Download pull-request-sha artifact - uses: dawidd6/action-download-artifact@v2 - with: - workflow: ${{ github.event.workflow_run.workflow_id }} - name: pull-request-sha - - - name: Set PR_SHA variable - run: | - pr_sha=$(cat "PR_SHA") + rm content/PR_NUMBER + pr_sha=$(cat "content/PR_SHA") echo "PR_SHA=$pr_sha" >> $GITHUB_ENV + rm content/PR_SHA - name: Publish doc-content using netlify uses: netlify/actions/cli@master diff --git a/.gitignore b/.gitignore index 1695ab870e..1eb71d6a98 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ linkchecker-out.html .local .yarnrc yarn.lock +package.json diff --git a/Dockerfile b/Dockerfile index 8c6da42e09..3f138d3165 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN wget -qO- https://github.com/wjdp/htmltest/archive/refs/tags/v${HTMLTEST_VER FROM docker.io/library/golang:1.17-alpine3.13 as vale-builder WORKDIR /vale -ARG VALE_VERSION=2.12.1 +ARG VALE_VERSION=2.14.0 RUN wget -qO- https://github.com/errata-ai/vale/archive/v${VALE_VERSION}.tar.gz | tar --strip-components=1 -zxvf - \ && export ARCH="$(uname -m)" \ && if [[ ${ARCH} == "x86_64" ]]; \ @@ -39,7 +39,7 @@ RUN wget -qO- https://github.com/errata-ai/vale/archive/v${VALE_VERSION}.tar.gz && GOOS=linux GOARCH=${ARCH} CGO_ENABLED=0 go build -tags closed -ldflags "-X main.date=`date -u +%Y-%m-%dT%H:%M:%SZ` -X main.version=${VALE_VERSION}" -o bin/vale ./cmd/vale \ && /vale/bin/vale --version -FROM docker.io/library/alpine:3.13 +FROM docker.io/library/alpine:3.15 COPY --from=newdoc-builder /usr/local/cargo/bin/newdoc /usr/local/bin/newdoc COPY --from=vale-builder /vale/bin/vale /usr/local/bin/vale @@ -48,19 +48,20 @@ COPY --from=htmltest-builder /htmltest/bin/htmltest /usr/local/bin/htmltest EXPOSE 4000 EXPOSE 35729 -LABEL description="Tools to build Eclipse Che documentation: antora, asciidoctor.js, bash, curl, findutils, git, gulp, jinja2, jq, linkchecker, newdoc, vale, yq" \ - io.k8s.description="Tools to build Eclipse Che documentation: antora, asciidoctor.js, bash, curl, findutils, git, gulp, jinja2, jq, linkchecker, newdoc, vale, yq" \ +LABEL description="Tools to build Eclipse Che documentation." \ + io.k8s.description="Tools to build Eclipse Che documentation." \ io.k8s.display-name="Che-docs tools" \ license="Eclipse Public License - v 2.0" \ MAINTAINERS="Eclipse Che Documentation Team" \ maintainer="Eclipse Che Documentation Team" \ name="che-docs" \ - source="https://github.com/eclipse/che-docs/Dockerfile" \ + source="https://github.com/eclipse/che-docs/blob/main/Dockerfile" \ summary="Tools to build Eclipse Che documentation" \ URL="quay.io/eclipse/che-docs" \ vendor="Eclipse Che Documentation Team" \ - version="2021.11" + version="2022.01" +ARG ANTORA_VERSION=3.0.0 RUN apk add --no-cache --update \ bash \ curl \ @@ -76,20 +77,20 @@ RUN apk add --no-cache --update \ tar \ xmlstarlet \ yarn \ - && pip3 install --no-cache-dir --no-input jinja2-cli linkchecker yq \ - && yarnpkg global add --ignore-optional --non-interactive @antora/cli@latest @antora/site-generator-default@latest asciidoctor gulp gulp-connect \ + && pip3 install --no-cache-dir --no-input diagrams jinja2-cli yq \ + && yarnpkg global add --ignore-optional --non-interactive @antora/cli@${ANTORA_VERSION} @antora/site-generator@${ANTORA_VERSION} @antora/site-generator-default@latest @antora/lunr-extension asciidoctor gulp gulp-connect \ && rm -rf $(yarnpkg cache dir)/* \ && rm -rf /tmp/* \ && antora --version \ && asciidoctor --version \ && bash --version \ && curl --version \ + && curl --version \ && git --version \ && gulp --version \ && htmltest --version \ && jinja2 --version \ && jq --version \ - && linkchecker --version \ && newdoc --version \ && vale -v \ && yq --version @@ -97,6 +98,7 @@ RUN apk add --no-cache --update \ VOLUME /projects WORKDIR /projects ENV HOME="/projects" \ - NODE_PATH="/usr/local/share/.config/yarn/global/node_modules" + NODE_PATH="/usr/local/share/.config/yarn/global/node_modules" \ + USER_NAME=che-docs USER 1001 diff --git a/antora-playbook-for-development.yml b/antora-playbook-for-development.yml index 4db2427eec..a0c6542268 100644 --- a/antora-playbook-for-development.yml +++ b/antora-playbook-for-development.yml @@ -9,6 +9,9 @@ # # Use this Antora Playbook for development, to build current state in HEAD. +antora: + extensions: + - require: "@antora/lunr-extension" site: title: Eclipse Che Documentation # Disabling url on purpose to avoid htmltest crawling the live website. @@ -35,3 +38,5 @@ urls: redirect_facility: static runtime: cache_dir: ./.cache/antora + log: + level: info diff --git a/gulpfile.js b/gulpfile.js index 02a43244bd..30a08c4cce 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,13 +13,13 @@ const connect = require('gulp-connect') const util = require('util'); const exec = util.promisify(require('child_process').exec); const fs = require('fs') -const generator = require('@antora/site-generator-default') +const generator = require('@antora/site-generator') const { reload: livereload } = process.env.LIVERELOAD === 'true' ? require('gulp-connect') : {} const { parallel, series, src, watch } = require('gulp') const yaml = require('js-yaml') const playbookFilename = 'antora-playbook-for-development.yml' -const playbook = yaml.safeLoad(fs.readFileSync(playbookFilename, 'utf8')) +const playbook = yaml.load(fs.readFileSync(playbookFilename, 'utf8')) const outputDir = (playbook.output || {}).dir || './build/site' const serverConfig = { name: 'Preview Site', livereload, host: '0.0.0.0', port: 4000, root: outputDir } const antoraArgs = ['--playbook', playbookFilename] diff --git a/package.json b/package.json deleted file mode 100644 index 74b7dfa4d5..0000000000 --- a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "@antora/site-generator-default": "^2.3.4", - "gulp": "^4.0.2", - "gulp-cli": "^2.3.0", - "gulp-connect": "^5.7.0" - } -} diff --git a/supplemental-ui/.htaccess b/supplemental-ui/.htaccess deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/supplemental-ui/css/search.css b/supplemental-ui/css/search.css new file mode 100644 index 0000000000..bc341849bc --- /dev/null +++ b/supplemental-ui/css/search.css @@ -0,0 +1,76 @@ +.search-result-dropdown-menu { + position: absolute; + z-index: 100; + display: block; + right: 0; + left: inherit; + top: 100%; + border-radius: 4px; + margin: 6px 0 0; + padding: 0; + text-align: left; + height: auto; + background: transparent; + border: none; + max-width: 600px; + min-width: 500px; + box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2), 0 2px 3px 0 rgba(0, 0, 0, 0.1); + } + + @media screen and (max-width: 768px) { + .search-result-dropdown-menu { + min-width: calc(100vw - 3.75rem); + } + } + + .search-result-dataset { + position: relative; + border: 1px solid #d9d9d9; + background: #fff; + border-radius: 4px; + overflow: auto; + padding: 0 8px; + max-height: calc(100vh - 5.25rem); + line-height: 1.5; + } + + .search-result-item { + display: flex; + margin: 0.5rem 0; + } + + .search-result-document-title { + width: 33%; + border-right: 1px solid #ddd; + color: #02060c; + font-weight: 500; + font-size: 0.8rem; + padding: 0.5rem 0.5rem 0.5rem 0; + text-align: right; + position: relative; + word-wrap: break-word; + } + + .search-result-document-hit { + flex: 1; + font-size: 0.75rem; + color: #63676d; + } + + .search-result-document-hit > a { + color: inherit; + display: block; + padding: 0.55rem 0.25rem 0.55rem 0.75rem; + } + + .search-result-document-hit > a:hover { + background-color: rgba(69, 142, 225, 0.05); + } + + .search-result-highlight { + color: #174d8c; + background: rgba(143, 187, 237, 0.1); + padding: 0.1em 0.05em; + font-weight: 500; + } + \ No newline at end of file diff --git a/supplemental-ui/js/search-ui.js b/supplemental-ui/js/search-ui.js new file mode 100644 index 0000000000..144ddf7b49 --- /dev/null +++ b/supplemental-ui/js/search-ui.js @@ -0,0 +1,229 @@ +; (function (globalScope) { + /* eslint-disable no-var */ + var config = document.getElementById('search-ui-script').dataset + var snippetLength = parseInt(config.snippetLength || 100, 10) + var siteRootPath = config.siteRootPath || '' + appendStylesheet(config.stylesheet) + var searchInput = document.getElementById('search-input') + var searchResult = document.createElement('div') + searchResult.classList.add('search-result-dropdown-menu') + searchInput.parentNode.appendChild(searchResult) + + function appendStylesheet(href) { + if (!href) return + document.head.appendChild(Object.assign(document.createElement('link'), { rel: 'stylesheet', href: href })) + } + + function highlightText(doc, position) { + var hits = [] + var start = position[0] + var length = position[1] + + var text = doc.text + var highlightSpan = document.createElement('span') + highlightSpan.classList.add('search-result-highlight') + highlightSpan.innerText = text.substr(start, length) + + var end = start + length + var textEnd = text.length - 1 + var contextAfter = end + snippetLength > textEnd ? textEnd : end + snippetLength + var contextBefore = start - snippetLength < 0 ? 0 : start - snippetLength + if (start === 0 && end === textEnd) { + hits.push(highlightSpan) + } else if (start === 0) { + hits.push(highlightSpan) + hits.push(document.createTextNode(text.substr(end, contextAfter))) + } else if (end === textEnd) { + hits.push(document.createTextNode(text.substr(0, start))) + hits.push(highlightSpan) + } else { + hits.push(document.createTextNode('...' + text.substr(contextBefore, start - contextBefore))) + hits.push(highlightSpan) + hits.push(document.createTextNode(text.substr(end, contextAfter - end) + '...')) + } + return hits + } + + function highlightTitle(hash, doc, position) { + var hits = [] + var start = position[0] + var length = position[1] + + var highlightSpan = document.createElement('span') + highlightSpan.classList.add('search-result-highlight') + var title + if (hash) { + title = doc.titles.filter(function (item) { + return item.id === hash + })[0].text + } else { + title = doc.title + } + highlightSpan.innerText = title.substr(start, length) + + var end = start + length + var titleEnd = title.length - 1 + if (start === 0 && end === titleEnd) { + hits.push(highlightSpan) + } else if (start === 0) { + hits.push(highlightSpan) + hits.push(document.createTextNode(title.substr(length, titleEnd))) + } else if (end === titleEnd) { + hits.push(document.createTextNode(title.substr(0, start))) + hits.push(highlightSpan) + } else { + hits.push(document.createTextNode(title.substr(0, start))) + hits.push(highlightSpan) + hits.push(document.createTextNode(title.substr(end, titleEnd))) + } + return hits + } + + function highlightHit(metadata, hash, doc) { + var hits = [] + for (var token in metadata) { + var fields = metadata[token] + for (var field in fields) { + var positions = fields[field] + if (positions.position) { + var position = positions.position[0] // only higlight the first match + if (field === 'title') { + hits = highlightTitle(hash, doc, position) + } else if (field === 'text') { + hits = highlightText(doc, position) + } + } + } + } + return hits + } + + function createSearchResult(result, store, searchResultDataset) { + result.forEach(function (item) { + var url = item.ref + var hash + if (url.includes('#')) { + hash = url.substring(url.indexOf('#') + 1) + url = url.replace('#' + hash, '') + } + var doc = store[url] + var metadata = item.matchData.metadata + var hits = highlightHit(metadata, hash, doc) + searchResultDataset.appendChild(createSearchResultItem(doc, item, hits)) + }) + } + + function createSearchResultItem(doc, item, hits) { + var documentTitle = document.createElement('div') + documentTitle.classList.add('search-result-document-title') + documentTitle.innerText = doc.title + var documentHit = document.createElement('div') + documentHit.classList.add('search-result-document-hit') + var documentHitLink = document.createElement('a') + documentHitLink.href = siteRootPath + item.ref + documentHit.appendChild(documentHitLink) + hits.forEach(function (hit) { + documentHitLink.appendChild(hit) + }) + var searchResultItem = document.createElement('div') + searchResultItem.classList.add('search-result-item') + searchResultItem.appendChild(documentTitle) + searchResultItem.appendChild(documentHit) + searchResultItem.addEventListener('mousedown', function (e) { + e.preventDefault() + }) + return searchResultItem + } + + function createNoResult(text) { + var searchResultItem = document.createElement('div') + searchResultItem.classList.add('search-result-item') + var documentHit = document.createElement('div') + documentHit.classList.add('search-result-document-hit') + var message = document.createElement('strong') + message.innerText = 'No results found for query "' + text + '"' + documentHit.appendChild(message) + searchResultItem.appendChild(documentHit) + return searchResultItem + } + + function clearSearchResults(reset) { + if (reset === true) searchInput.value = '' + searchResult.innerHTML = '' + } + + function search(index, text) { + // execute an exact match search + var result = index.search(text) + if (result.length > 0) { + return result + } + // no result, use a begins with search + result = index.search(text + '*') + if (result.length > 0) { + return result + } + // no result, use a contains search + result = index.search('*' + text + '*') + return result + } + + function searchIndex(index, store, text) { + clearSearchResults(false) + if (text.trim() === '') { + return + } + var result = search(index, text) + var searchResultDataset = document.createElement('div') + searchResultDataset.classList.add('search-result-dataset') + searchResult.appendChild(searchResultDataset) + if (result.length > 0) { + createSearchResult(result, store, searchResultDataset) + } else { + searchResultDataset.appendChild(createNoResult(text)) + } + } + + function confineEvent(e) { + e.stopPropagation() + } + + function debounce(func, wait, immediate) { + var timeout + return function () { + var context = this + var args = arguments + var later = function () { + timeout = null + if (!immediate) func.apply(context, args) + } + var callNow = immediate && !timeout + clearTimeout(timeout) + timeout = setTimeout(later, wait) + if (callNow) func.apply(context, args) + } + } + + function initSearch(lunr, data) { + var index = Object.assign({ index: lunr.Index.load(data.index), store: data.store }) + var debug = 'URLSearchParams' in globalScope && new URLSearchParams(globalScope.location.search).has('lunr-debug') + searchInput.addEventListener( + 'keydown', + debounce(function (e) { + if (e.key === 'Escape' || e.key === 'Esc') return clearSearchResults(true) + try { + var query = searchInput.value + if (!query) return clearSearchResults() + searchIndex(index.index, index.store, searchInput.value) + } catch (err) { + if (debug) console.debug('Invalid search query: ' + query + ' (' + err.message + ')') + } + }, 100) + ) + searchInput.addEventListener('click', confineEvent) + searchResult.addEventListener('click', confineEvent) + document.documentElement.addEventListener('click', clearSearchResults) + } + + globalScope.initSearch = initSearch +})(typeof globalThis !== 'undefined' ? globalThis : window) diff --git a/supplemental-ui/partials/footer-content.hbs b/supplemental-ui/partials/footer-content.hbs index 24e44eba9e..8d58ab28e2 100644 --- a/supplemental-ui/partials/footer-content.hbs +++ b/supplemental-ui/partials/footer-content.hbs @@ -5,23 +5,3 @@ Eclipse Public License | Legal Resources - -{{#if env.ALGOLIA_API_KEY}} - - - -{{/if}} diff --git a/supplemental-ui/partials/head-meta.hbs b/supplemental-ui/partials/head-meta.hbs index e3e8932978..e2d3d5c9f8 100644 --- a/supplemental-ui/partials/head-meta.hbs +++ b/supplemental-ui/partials/head-meta.hbs @@ -3,4 +3,5 @@ {{#if page.componentVersion.prerelease }} -{{/if}} \ No newline at end of file +{{/if}} + \ No newline at end of file diff --git a/supplemental-ui/partials/header-content.hbs b/supplemental-ui/partials/header-content.hbs index 8b7015b1cf..fcde6641c7 100644 --- a/supplemental-ui/partials/header-content.hbs +++ b/supplemental-ui/partials/header-content.hbs @@ -1,4 +1,4 @@ -