diff --git a/.idea/modules.xml b/.idea/modules.xml index a1a74d60..7894a652 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + - \ No newline at end of file + diff --git a/README.md b/README.md index 0dc93c8c..a48f1307 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@

- - Spring + + SAYA Platform

-[![On Push](https://github.com/spring-io/antora-ui-spring/actions/workflows/push.yml/badge.svg?branch=main)](https://github.com/spring-io/antora-ui-spring/actions/workflows/push.yml) +[![On Push](https://github.com/3cortextechnologies/saya-antora-ui/actions/workflows/push.yml/badge.svg?branch=main)](https://github.com/3cortextechnologies/saya-antora-ui/actions/workflows/push.yml) -This project generates and packages the static resources that Spring uses for document production. +This project generates and packages the static resources that SAYA uses for document production. -This project is based on [Antora](https://antora.org). +This project is based on [Spring Antora UI](https://github.com/spring-io/antora-ui-spring). ## Development Quickstart @@ -20,7 +20,7 @@ A more comprehensive tutorial can be found in the documentation at [docs.antora. ### Prerequisites -To preview and bundle the Antora Spring UI, you need the following software on your computer: +To preview and bundle the SAYA Antora UI, you need the following software on your computer: * [git](https://git-scm.com/) (command: `git`) * [Node.js](https://nodejs.org/) (commands: `node` and `npm`) @@ -28,7 +28,7 @@ To preview and bundle the Antora Spring UI, you need the following software on y ### Preview the UI -The Spring Antora UI project is configured to preview offline. +The SAYA Antora UI project is configured to preview offline. The files in the `preview-src/` folder provide the sample content that allow you to see the UI in action. In this folder, you'll primarily find pages written in AsciiDoc. These pages provide a representative sample and kitchen sink of content from the real site. @@ -76,31 +76,6 @@ gulp bundle:pack The UI bundle will again be available at `build/ui-bundle.zip`. -## Extensions to the UI - -### Related Documentation - -The UI presents a list of related documentation and that documentation can be filtered using two attributes: - -* page-related-doc-categories - The categories to be included in the related documentation -* page-related-doc-projects - The project ids to be included in the related documentation - -For a complete listing of valid categories and ids view [related_projects.js](https://github.com/spring-io/antora-ui-spring/blob/main/src/helpers/related_projects.js) - -The configuration is typically specified in asciidoc attributes section of the `antora-playbook.yml`: - -``` -asciidoc: - attributes: - # Include the projects with the security category - page-related-doc-categories: security - # Include the projects with ids framework and graphql - page-related-doc-projects: framework,graphql -``` - -The Related Documentation links to the `All Docs...` page. -To include this resource, ensure that the [antora-extensions](https://github.com/spring-io/antora-extensions/blob/main/README.adoc) is using 1.7.0+ and the [Static Page Extension](https://github.com/spring-io/antora-extensions/blob/main/README.adoc#static-page) is included. - ## Authors Development of Antora is led and sponsored by [OpenDevise Inc](https://opendevise.com/). @@ -110,4 +85,3 @@ Development of Antora is led and sponsored by [OpenDevise Inc](https://opendevis Copyright (C) 2017-present OpenDevise Inc. and the Antora Project. Use of this software is granted under the terms of the [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0). -See [LICENSE](https://github.com/spring-io/antora-ui-spring/blob/feat/gh-226/LICENSE) to find the full license text. diff --git a/gulp.d/tasks/build-preview-search-index.js b/gulp.d/tasks/build-preview-search-index.js new file mode 100644 index 00000000..716e733e --- /dev/null +++ b/gulp.d/tasks/build-preview-search-index.js @@ -0,0 +1,92 @@ +'use strict' + +const Asciidoctor = require('@asciidoctor/core')() +const fs = require('fs') +const { promises: fsp } = fs +const ospath = require('path') +const yaml = require('js-yaml') + +module.exports = (src, previewSrc, previewDest) => async function buildPreviewSearchIndex () { + const adocFiles = await collectAdocFiles(previewSrc) + const docs = [] + + for (const adocPath of adocFiles) { + const adocContents = await fsp.readFile(adocPath, 'utf8') + const doc = Asciidoctor.load(adocContents, { safe: 'safe' }) + const title = doc.getDocumentTitle() || ospath.basename(adocPath, '.adoc') + const html = doc.convert() + const text = html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim() + + const pageModel = await loadPageModel(adocPath) + let url = (pageModel && pageModel.url) || '' + + if (!url) { + const rel = ospath.relative(previewSrc, adocPath).replace(/\\/g, '/') + url = '/' + rel.replace(/\.adoc$/, '.html') + } + + const titles = extractSectionTitles(doc, text, title) + + docs.push({ url, title, text, titles }) + } + + const jsDir = previewDest + await fsp.mkdir(jsDir, { recursive: true }) + + const indexJs = [ + 'window.antoraLunr = window.antoraLunr || {};', + 'window.antoraLunr.docs = ' + JSON.stringify(docs, null, 2) + ';', + '', + ].join('\n') + + await fsp.writeFile(ospath.join(jsDir, 'search-index.js'), indexJs, 'utf8') +} + +function extractSectionTitles (doc, fullText, defaultTitle) { + const titles = [] + + const sections = doc.findBy({ context: 'section' }) || [] + + sections.forEach((sect) => { + const title = sect.getTitle && sect.getTitle() + const id = sect.getId && sect.getId() + const html = sect.convert() + const text = html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim() + + if (title || text) { + titles.push({ id: id || null, title: title || defaultTitle, text }) + } + }) + + if (!titles.length) { + titles.push({ id: null, title: defaultTitle, text: fullText }) + } + + return titles +} + +async function collectAdocFiles (dir) { + const entries = await fsp.readdir(dir, { withFileTypes: true }) + const files = [] + + for (const entry of entries) { + const fullPath = ospath.join(dir, entry.name) + if (entry.isDirectory()) { + files.push(...(await collectAdocFiles(fullPath))) + } else if (entry.isFile() && entry.name.endsWith('.adoc')) { + files.push(fullPath) + } + } + + return files +} + +async function loadPageModel (adocPath) { + const ymlPath = adocPath + '.yml' + try { + const contents = await fsp.readFile(ymlPath, 'utf8') + return yaml.load(contents) + } catch (e) { + return undefined + } +} diff --git a/gulpfile.js b/gulpfile.js index bff17796..47f7feaf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -113,10 +113,15 @@ const buildPreviewPagesTask = createTask({ call: task.buildPreviewPages(srcDir, previewSrcDir, previewDestDir, livereload), }) +const buildPreviewSearchIndexTask = createTask({ + name: 'preview:build-search-index', + call: task.buildPreviewSearchIndex(srcDir, previewSrcDir, previewDestDir), +}) + const previewBuildTask = createTask({ name: 'preview:build', desc: 'Process and stage the UI assets and generate pages for the preview', - call: parallel(buildTask, buildPreviewPagesTask), + call: parallel(buildTask, buildPreviewPagesTask, buildPreviewSearchIndexTask), }) const previewServeTask = createTask({ diff --git a/package-lock.json b/package-lock.json index 78a6da51..94ee4507 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "antora-ui-spring", + "name": "saya-antora-ui", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "antora-ui-spring", + "name": "saya-antora-ui", "license": "MPL-2.0", "dependencies": { "@algolia/client-search": "^4.17.0", @@ -2978,9 +2978,9 @@ "dev": true }, "node_modules/caniuse-lite": { - "version": "1.0.30001517", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", - "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -2995,7 +2995,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/caw": { "version": "2.0.1", diff --git a/package.json b/package.json index 50d19165..025845b0 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,10 @@ { - "name": "antora-ui-spring", - "description": "Produces the UI bundle for projects on docs.spring.io that are built with Antora", - "homepage": "https://docs.spring.io", - "license": "MPL-2.0", + "name": "saya-antora-ui", + "description": "Produces the UI bundle for projects on sayadev.3cortex.com/docs that are built with Antora", + "homepage": "https://sayaplatform.com", "repository": { "type": "git", - "url": "https://github.com/spring-io/antora-ui-spring" + "url": "https://github.com/3cortextechnologies/saya-antora-ui" }, "scripts": { "start": "gulp preview", diff --git a/preview-src/samples/content/index.adoc b/preview-src/samples/content/index.adoc index c6c25032..df77a250 100644 --- a/preview-src/samples/content/index.adoc +++ b/preview-src/samples/content/index.adoc @@ -67,7 +67,7 @@ Content after === URLs -The homepage for the Asciidoctor Project is https://asciidoctor.org. +The homepage for the Asciidoctor Project is https://asciidoctor.org. Ask questions on the http://discuss.asciidoctor.org/[*mailing list*]. @@ -82,10 +82,10 @@ The hail-and-rainbow protocol can be initiated at five levels: double, tertiary, supernumerary, supermassive, and apocalyptic party. A bold statement! -Another outrageous statement.footnote:disclaimer[] +Another outrageous statement.footnote:disclaimer[] -footnote:[The double hail-and-rainbow level makes my toes tingle.] -footnote:disclaimer[Opinions are my own.] +footnote:[The double hail-and-rainbow level makes my toes tingle.] +footnote:disclaimer[Opinions are my own.] [.impact] @@ -95,10 +95,10 @@ footnote:disclaimer[Opinions are my own.] ==== Alone -[#img-sunset] -.A mountain sunset -[link=https://www.flickr.com/photos/javh/5448336655] -image::/_/img/spring-logo.svg[Spring,300,200] +[#img-sunset] +.A mountain sunset +[link=https://www.flickr.com/photos/javh/5448336655] +image::/_/images/saya-logo.png[Spring,300,200] ==== Inline @@ -115,11 +115,11 @@ You can find image:https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg[L -- [.left] .Image A -image::/_/img/spring-logo.svg[A,240,180] +image::/_/images/saya-logo.png[A,240,180] [.left] .Image B -image::/_/img/spring-logo.svg[B,240,180] +image::/_/images/saya-logo.png[B,240,180] -- Text below images. @@ -127,20 +127,20 @@ Text below images. === Sizing images (1) -image::/_/img/spring-logo.svg[Spring,640,480] +image::/_/images/saya-logo.png[Spring,640,480] === Sizing images (2) -image::/_/img/spring-logo.svg[Spring,50%] +image::/_/images/saya-logo.png[Spring,50%] === Taming SVGs -image::/_/img/spring-logo.svg[Static,300] +image::/_/images/saya-logo.png[Static,300] -image::/_/img/spring-logo.svg[Interactive,300,opts=interactive] +image::/_/images/saya-logo.png[Interactive,300,opts=interactive] -image::/_/img/spring-logo.svg[Embedded,300,opts=inline] +image::/_/images/saya-logo.png[Embedded,300,opts=inline] === Image invert dark mode @@ -158,9 +158,9 @@ But **u**ltimate victory could only be won if we divined the *_true name_* of th === Quotation Marks and Apostrophes -"`What kind of charm?`" Lazarus asked. "`An odoriferous one or a mineral one?`" +"`What kind of charm?`" Lazarus asked. "`An odoriferous one or a mineral one?`" -Kizmet shrugged. "`The note from Olaf's desk says '`wormwood and licorice,`' but these could be normal groceries for werewolves.`" +Kizmet shrugged. "`The note from Olaf's desk says '`wormwood and licorice,`' but these could be normal groceries for werewolves.`" === Subscript and Superscript @@ -460,7 +460,7 @@ fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterCh ==== Multi-lines -[IMPORTANT] +[IMPORTANT] .title ==== This is an important admonition @@ -599,8 +599,8 @@ This is a stem block. ==== Multi-lines -.Gettysburg Address -[quote, Abraham Lincoln, Address delivered at the dedication of the Cemetery at Gettysburg] +.Gettysburg Address +[quote, Abraham Lincoln, Address delivered at the dedication of the Cemetery at Gettysburg] ____ Four score and seven years ago our fathers brought forth on this continent a new nation... @@ -643,8 +643,8 @@ But nothing can be changed until it is faced. ==== Multi-lines -.AsciiDoc history -**** +.AsciiDoc history +**** AsciiDoc was first released in Nov 2002 by Stuart Rackham. It was designed from the start to be a shorthand syntax for producing professional documents like DocBook and LaTeX. @@ -686,15 +686,15 @@ on little cat feet. === Simple table -|==== +|==== -| Cell in column 1, row 1 | Cell in column 2, row 1 +| Cell in column 1, row 1 | Cell in column 2, row 1 | Cell in column 1, row 2 | Cell in column 2, row 2 | Cell in column 1, row 3 | Cell in column 2, row 3 -|==== +|==== === Number of columns diff --git a/src/css/main.css b/src/css/main.css index 60b16678..c688b17c 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -47,8 +47,8 @@ margin: 0 auto; } -#springlogo, -#springlogo-foot { +#sayalogo, +#sayalogo-foot { height: 45px; max-width: 250px; } @@ -280,7 +280,7 @@ html.dark-theme #modal-versions .version-toggle span { } #modal-versions .current { - background-color: #80ea6e; + background-color: #df3d80; color: #111; align-items: center; border-radius: 4px; diff --git a/src/css/modal.css b/src/css/modal.css index b95df722..ecdd4e81 100644 --- a/src/css/modal.css +++ b/src/css/modal.css @@ -125,7 +125,7 @@ padding: 3rem; } .modal__container { - max-width: 600px; + max-width: 720px; min-width: 50%; width: auto; } diff --git a/src/css/nav.css b/src/css/nav.css index e6e47d40..60349d2a 100644 --- a/src/css/nav.css +++ b/src/css/nav.css @@ -265,7 +265,7 @@ html.dark-theme button.browse-version:hover { .nav-panel-menu .context .version { display: inline-block; - background-color: #80ea6e; + background-color: #df3d80; color: #111; align-items: center; border-radius: 4px; diff --git a/src/css/toolbar.css b/src/css/toolbar.css index 2a7bfbf3..35064323 100644 --- a/src/css/toolbar.css +++ b/src/css/toolbar.css @@ -52,7 +52,10 @@ html.dark-theme .nav-toggle { } @media screen and (min-width: 1024px) { - .nav-toggle, + .nav-toggle { + display: none; + } + .toolbar { display: none; } diff --git a/src/css/vars.css b/src/css/vars.css index 4864a6b9..83f245c9 100644 --- a/src/css/vars.css +++ b/src/css/vars.css @@ -29,7 +29,7 @@ --font-family-special: "Metropolis", "Helvetica", "Arial", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; --font-weight: 400; --monospace-font-family: "SFMono-Regular", "Consolas", "Liberation Mono", "Menlo", monospace; - --body-background-color: white; + --body-background-color: #f4f3f9; --body-background: var(--body-background-color); --panel-background-color: #f6f8fa; --panel-background: var(--panel-background-color); @@ -47,7 +47,7 @@ --code-link-font-color: var(--link-font-color); --scrollbar-thumb-color: silver; --scrollbar_hover-thumb-color: silver; - --mark-background-color: #80ea6e; + --mark-background-color: #df3d80; --selected-background-color: #191e1e; --caption-font-style: italic; @@ -140,7 +140,7 @@ --navbar-menu_hover-background: var(--navbar-menu-background); /* Nav */ - --nav-background: var(--color-white); + --nav-background: var(--body-background-color); --nav-border-color: var(--color-gray-10); --nav-line-height: 1.35; --nav-heading-font-color: var(--color-jet-30); @@ -149,7 +149,7 @@ --nav-secondary-background: var(--color-smoke-70); /* Toolbar */ - --toolbar-background: var(--color-white); + --toolbar-background: var(--body-background-color); --toolbar-border-color: var(--nav-panel-divider-color); --toolbar-font-color: var(--color-gray-70); --toolbar-muted-color: var(--color-gray-30); diff --git a/src/css/vendor/page-search.css b/src/css/vendor/page-search.css index b3f629bb..26e1ec09 100644 --- a/src/css/vendor/page-search.css +++ b/src/css/vendor/page-search.css @@ -150,3 +150,40 @@ .dark-theme .page-search .ais-SearchBox-input:focus { border-color: var(--color-accent-3); } + +/* Hierarchical search results: parent page + child headings */ +.hit-parent .hit-name { + font-weight: 600; + margin-bottom: 0.25rem; +} + +.hit-children { + list-style: none; + margin: 0 0 0.75rem 1.25rem; + padding: 0.25rem 0 0.5rem 0; +} + +.hit-child { + margin: 0.15rem 0; +} + +.hit-child-link { + display: block; + color: var(--body-font-color); + text-decoration: none; +} + +.hit-child-link:hover .hit-child-title { + text-decoration: underline; +} + +.hit-child-title { + font-size: 0.9rem; + margin-bottom: 0.1rem; +} + +.hit-child-description { + font-size: 0.85rem; + margin: 0; + color: rgba(0, 0, 0, 0.7); +} diff --git a/src/css/vendor/search.css b/src/css/vendor/search.css index eb79a0b6..b6db80af 100644 --- a/src/css/vendor/search.css +++ b/src/css/vendor/search.css @@ -329,6 +329,43 @@ a.ais-Hits-item:hover { background-color: white; } +/* Hierarchical search results: parent page + child headings */ +.hit-parent .hit-name { + font-weight: 600; + margin-bottom: 0.25rem; +} + +.hit-children { + list-style: none; + margin: 0 0 0.75rem 1.25rem; + padding: 0.25rem 0 0.5rem 0; +} + +.hit-child { + margin: 0.15rem 0; +} + +.hit-child-link { + display: block; + color: var(--body-font-color); + text-decoration: none; +} + +.hit-child-link:hover .hit-child-title { + text-decoration: underline; +} + +.hit-child-title { + font-size: 0.9rem; + margin-bottom: 0.1rem; +} + +.hit-child-description { + font-size: 0.85rem; + margin: 0; + color: rgba(0, 0, 0, 0.7); +} + @media screen and (max-width: 1023.5px) { .DocSearch-Button { border-left-width: 1px; diff --git a/src/img/algolia-dark.svg b/src/img/algolia-dark.svg deleted file mode 100644 index 2db0d77c..00000000 --- a/src/img/algolia-dark.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/img/algolia-light.svg b/src/img/algolia-light.svg deleted file mode 100644 index cd76294c..00000000 --- a/src/img/algolia-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/img/search-by-algolia-dark-background.svg b/src/img/search-by-algolia-dark-background.svg deleted file mode 100644 index f8c944a7..00000000 --- a/src/img/search-by-algolia-dark-background.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/img/search-by-algolia-light-background.svg b/src/img/search-by-algolia-light-background.svg deleted file mode 100644 index f1c841e7..00000000 --- a/src/img/search-by-algolia-light-background.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/js/vendor/page-search.js b/src/js/vendor/page-search.js index ffc11791..167836ed 100644 --- a/src/js/vendor/page-search.js +++ b/src/js/vendor/page-search.js @@ -1,115 +1,218 @@ /* eslint-disable no-undef */ ;(function () { - const config = document.getElementById('page-search-script').dataset - const client = algoliasearch(config.appId, config.apiKey) + let initialized = false + let index = null + let store = {} + let usingAntoraIndex = false let selected = null - const search = instantsearch({ - indexName: config.indexName, - searchClient: client, - }) + // Initialize Lunr index from the generated search-index.js + const initSearch = () => { + if (initialized) return + if (typeof lunr === 'undefined' || typeof antoraLunr === 'undefined') return - let lastRenderArgs - - const infiniteHits = instantsearch.connectors.connectInfiniteHits((renderArgs, isFirstRender) => { - const { hits, showMore, widgetParams } = renderArgs - const { container } = widgetParams - lastRenderArgs = renderArgs - selected = null - if (isFirstRender) { - const sentinel = document.createElement('div') - container.appendChild(document.createElement('ul')) - container.appendChild(sentinel) + if (antoraLunr.index && antoraLunr.store) { + // Main Antora site: use Antora's pre-built index, but map refs to documents + index = lunr.Index.load(antoraLunr.index) + store = (antoraLunr.store && antoraLunr.store.documents) || {} + usingAntoraIndex = true + initialized = true return } - const _hits = [...hits] - if (container.querySelector('#page-showmore')) { - container.removeChild(container.querySelector('#page-showmore')) + + if (Array.isArray(antoraLunr.docs)) { + // Preview: build a Lunr index from our docs array, ref=url + const docs = antoraLunr.docs + store = {} + index = lunr(function () { + this.ref('url') + this.field('title') + this.field('text') + docs.forEach((doc) => { + store[doc.url] = doc + this.add(doc) + }) + }) + usingAntoraIndex = false + initialized = true } - if (container.querySelector('#page-nomore')) { - container.removeChild(container.querySelector('#page-nomore')) + } + + const highlightText = (text, terms) => { + if (!text || !terms || terms.length === 0) return text + const escapedTerms = terms.map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + const regex = new RegExp(`(${escapedTerms.join('|')})`, 'gi') + return text.replace(regex, '$1') + } + + const truncateText = (text, maxLength = 200) => { + if (!text || text.length <= maxLength) return text + return text.substring(0, maxLength) + '...' + } + + const buildSnippet = (text, terms, radius = 100, maxLength = 200) => { + if (!text) return '' + if (!Array.isArray(terms) || terms.length === 0) return truncateText(text, maxLength) + + const lowerText = text.toLowerCase() + const lowerTerms = terms.map((t) => (t || '').toLowerCase()).filter(Boolean) + if (!lowerTerms.length) return truncateText(text, maxLength) + + let matchIndex = -1 + let matchLength = 0 + + lowerTerms.forEach((term) => { + const idx = lowerText.indexOf(term) + if (idx !== -1 && (matchIndex === -1 || idx < matchIndex)) { + matchIndex = idx + matchLength = term.length + } + }) + + if (matchIndex === -1) return truncateText(text, maxLength) + + const start = Math.max(0, matchIndex - radius) + const end = Math.min(text.length, matchIndex + matchLength + radius) + const snippet = text.substring(start, end) + + const prefix = start > 0 ? '… ' : '' + const suffix = end < text.length ? ' …' : '' + return `${prefix}${snippet}${suffix}` + } + + const getDocFromResult = (result) => { + if (usingAntoraIndex) { + // Antora lunr-extension uses refs like '108-1'; the page document is keyed by the first segment + const ref = String(result.ref) + const pageId = ref.split('-')[0] + return store[pageId] + } + + // Preview: refs are URLs matching store[url] + return store[result.ref] + } + + const performSearch = (query) => { + if (!index || !query.trim()) return [] + + try { + const searchTerms = query.trim().split(/\s+/) + const results = index.search(query.trim() + '*') + + return results.map((result) => { + const doc = getDocFromResult(result) + return { + ...doc, + ref: result.ref, + score: result.score, + searchTerms: searchTerms, + } + }) + } catch (e) { + // If wildcard search fails, try without wildcard + try { + const searchTerms = query.trim().split(/\s+/) + const results = index.search(query.trim()) + return results.map((result) => { + const doc = getDocFromResult(result) + return { + ...doc, + ref: result.ref, + score: result.score, + searchTerms: searchTerms, + } + }) + } catch (e2) { + console.error('Search error:', e2) + return [] + } } + } + + const renderResults = (hits, container) => { + selected = null - const nbHits = renderArgs.results.nbHits if (hits.length === 0) { container.querySelector('ul').innerHTML = '
  • No result
  • ' document.querySelector('#page-counter').style.display = 'none' - } else { - document.querySelector('#page-counter').innerHTML = `${nbHits} result${ - nbHits > 1 ? 's' : '' - } found` - document.querySelector('#page-counter').style.display = 'block' - container.querySelector('ul').innerHTML = _hits - .map((hit) => { - let content = '' - let breadcrumbs = '' - let label = '' - - if (hit.content) { - content = `

    - ${instantsearch.snippet({ hit: hit, attribute: 'content' })} -

    ` - } + return + } - if (hit.breadcrumbs) { - breadcrumbs = `
    - ${hit.breadcrumbs - .map((chain) => { - const arr = chain.split('|') - return `${arr[0]}` - }) - .join(' > ')} -
    ` + const nbHits = hits.length + const counterEl = document.querySelector('#page-counter') + counterEl.innerHTML = `${nbHits} result${nbHits > 1 ? 's' : ''} found` + document.querySelector('#page-counter').style.display = 'block' + + container.querySelector('ul').innerHTML = hits + .map((hit) => { + const titles = Array.isArray(hit.titles) ? hit.titles : [] + const searchTerms = Array.isArray(hit.searchTerms) ? hit.searchTerms : [] + + const rawParentTitle = hit.title || 'Untitled' + const parentTitle = highlightText(rawParentTitle, searchTerms) + + const lowerTerms = searchTerms.map((t) => t.toLowerCase()) + const matchesTerms = (t) => { + if (!t || !lowerTerms.length) return false + const raw = t.text || '' + const snippet = buildSnippet(raw, searchTerms) || '' + const haystack = `${t.title || ''} ${snippet}`.toLowerCase() + if (!haystack.trim()) return false + return lowerTerms.some((term) => term && haystack.includes(term)) + } + + // For Antora index, hit.ref is like '108-1' where the second segment is the section id + let matchedChildId = null + if (usingAntoraIndex && hit.ref) { + const refStr = String(hit.ref) + const parts = refStr.split('-') + if (parts.length > 1) { + const maybeId = parseInt(parts[1], 10) + if (!isNaN(maybeId)) matchedChildId = maybeId } + } - label = Object.keys(hit.hierarchy) - .map((key, index) => { - if (index > 0 && hit) { - return instantsearch.highlight({ hit: hit, attribute: 'hierarchy.' + key }) - } - return null - }) - .filter((item) => !!item) - .join(' - ') - - return `
  • - -
    - ${label} -
    - ${breadcrumbs} - ${content} -
    -
  • ` - }) - .join('') - - // container.querySelectorAll('ul a').forEach((a) => { - // a.addEventListener('click', () => { - // MicroModal.close('modal-1') - // }) - // }) - - if (!lastRenderArgs.isLastPage) { - const more = document.createElement('div') - const link = document.createElement('a') - more.setAttribute('id', 'page-showmore') - link.addEventListener('click', () => { - showMore() - return false - }) - link.innerHTML = 'Show more' - more.appendChild(link) - container.appendChild(more) - } else { - const noMore = document.createElement('div') - noMore.setAttribute('id', 'page-nomore') - noMore.innerHTML = 'No more result' - container.appendChild(noMore) - } - } - }) + let filteredTitles = titles.filter((t) => matchesTerms(t)) + + if (!filteredTitles.length && matchedChildId != null) { + const matched = titles.find((t) => t && t.id === matchedChildId) + if (matched) filteredTitles = [matched] + } + + const childrenHtml = filteredTitles.length + ? `` + : '' + + return `
  • + +
    + ${parentTitle} +
    +
    + ${childrenHtml} +
  • ` + }) + .join('') + } const selectHit = (newSelected) => { const hits = document.querySelectorAll('#page-hits>ul>li>a') @@ -122,94 +225,98 @@ selected = newSelected } - if (selected) { - hits[selected].scrollIntoView() + if (selected !== null && hits[selected]) { + hits[selected].scrollIntoView({ block: 'nearest' }) } } - const openHit = (index) => { + const openHit = (idx) => { const hits = document.querySelectorAll('#page-hits>ul>li>a') - if (hits[index]) { - hits[index].click() + if (hits[idx]) { + hits[idx].click() } } - const input = document.createElement('input') - const renderSearchBox = (renderOptions, isFirstRender) => { - const { query, refine, clear, isSearchStalled, widgetParams } = renderOptions - if (isFirstRender) { - input.classList.add('ais-SearchBox-input') - input.placeholder = 'Search in all Spring Documentation' - const loadingIndicator = document.createElement('span') - loadingIndicator.textContent = 'Loading...' - const button = document.createElement('button') - button.classList.add('ais-SearchBox-reset') - button.innerHTML = - '' - input.addEventListener('keydown', (event) => { - switch (event.keyCode) { - case 40: // Down - event.preventDefault() - if (selected === null) { - selectHit(0) - } else { - selectHit(selected + 1) - } - break - case 38: // Up - event.preventDefault() - if (selected === null) { - selectHit(0) - } - selectHit(Math.max(selected - 1, 0)) - break - case 13: // Enter - event.preventDefault() - if (selected !== null) { - openHit(selected) - } - break - case 9: // Tab - event.preventDefault() - break + const setupPageSearch = () => { + initSearch() + + const searchbox = document.querySelector('#page-searchbox') + const hitsContainer = document.querySelector('#page-hits') + + if (!searchbox || !hitsContainer) return + + // Create search input + const input = document.createElement('input') + input.classList.add('ais-SearchBox-input') + input.placeholder = 'Search documentation...' + + // Create clear button + const button = document.createElement('button') + button.classList.add('ais-SearchBox-reset') + button.innerHTML = + '' + + // Create results list + hitsContainer.appendChild(document.createElement('ul')) + + // Debounce search + let debounceTimer + input.addEventListener('input', (event) => { + clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => { + const query = event.target.value + if (query.trim()) { + const results = performSearch(query) + renderResults(results, hitsContainer) + } else { + hitsContainer.querySelector('ul').innerHTML = '' + document.querySelector('#page-counter').style.display = 'none' } - }) - input.addEventListener('input', (event) => { - refine(event.target.value) - }) - button.addEventListener('click', () => { - clear() - }) - widgetParams.container.appendChild(input) - widgetParams.container.appendChild(loadingIndicator) - widgetParams.container.appendChild(button) - } - widgetParams.container.querySelector('input').value = query - widgetParams.container.querySelector('span').hidden = !isSearchStalled + }, 150) + }) + + input.addEventListener('keydown', (event) => { + const hits = document.querySelectorAll('#page-hits>ul>li>a') + switch (event.keyCode) { + case 40: // Down + event.preventDefault() + if (selected === null) { + selectHit(0) + } else if (selected < hits.length - 1) { + selectHit(selected + 1) + } + break + case 38: // Up + event.preventDefault() + if (selected === null) { + selectHit(0) + } else { + selectHit(Math.max(selected - 1, 0)) + } + break + case 13: // Enter + event.preventDefault() + if (selected !== null) { + openHit(selected) + } + break + } + }) + + button.addEventListener('click', () => { + input.value = '' + hitsContainer.querySelector('ul').innerHTML = '' + document.querySelector('#page-counter').style.display = 'none' + }) + + searchbox.appendChild(input) + searchbox.appendChild(button) + input.focus() } - const searchBox = instantsearch.connectors.connectSearchBox(renderSearchBox) - - // selected - search.addWidgets([ - instantsearch.widgets.configure({ - facetFilters: ['isLatestVersion:true'], - attributesToSnippet: ['content'], - attributesToHighlight: ['hierarchy'], - distinct: true, - }), - searchBox({ - container: document.querySelector('#page-searchbox'), - }), - infiniteHits({ - container: document.querySelector('#page-hits'), - }), - ]) - - search.start() - input.focus() + setupPageSearch() })() diff --git a/src/js/vendor/search.bundle.js b/src/js/vendor/search.bundle.js index 622bfd53..19125160 100644 --- a/src/js/vendor/search.bundle.js +++ b/src/js/vendor/search.bundle.js @@ -2,125 +2,226 @@ ;(function () { const isMac = () => navigator.platform.indexOf('Mac') > -1 - let loaded = false - const config = document.getElementById('search-script').dataset - const client = algoliasearch(config.appId, config.apiKey) + let initialized = false + let index = null + let store = {} + let usingAntoraIndex = false let selected = null - const search = instantsearch({ - indexName: config.indexName, - searchClient: client, - }) - - let lastRenderArgs + // Initialize Lunr index from the generated search-index.js + const initSearch = () => { + if (initialized) return + if (typeof lunr === 'undefined' || typeof antoraLunr === 'undefined') return - const decodeHtmlEntities = (str) => { - return str.replaceAll('&lt;', '<').replaceAll('&gt;', '>') - } + if (antoraLunr.index && antoraLunr.store) { + // Main Antora site: use Antora's pre-built index, but map refs to documents + index = lunr.Index.load(antoraLunr.index) + store = (antoraLunr.store && antoraLunr.store.documents) || {} + usingAntoraIndex = true + initialized = true + return + } - const infiniteHits = instantsearch.connectors.connectInfiniteHits((renderArgs, isFirstRender) => { - const { hits, showMore, widgetParams } = renderArgs - const { container } = widgetParams - lastRenderArgs = renderArgs - selected = null - if (isFirstRender) { - const sentinel = document.createElement('div') - container.appendChild(document.createElement('ul')) - container.appendChild(sentinel) - const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting && !lastRenderArgs.isLastPage) { - showMore() - } + if (Array.isArray(antoraLunr.docs)) { + // Preview: build a Lunr index from our docs array, ref=url + const docs = antoraLunr.docs + store = {} + index = lunr(function () { + this.ref('url') + this.field('title') + this.field('text') + docs.forEach((doc) => { + store[doc.url] = doc + this.add(doc) }) }) - observer.observe(sentinel) - return + usingAntoraIndex = false + initialized = true } - const _hits = [...hits] - if (container.querySelector('#showmore')) { - container.removeChild(container.querySelector('#showmore')) + } + + const highlightText = (text, terms) => { + if (!text || !terms || terms.length === 0) return text + const escapedTerms = terms.map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + const regex = new RegExp(`(${escapedTerms.join('|')})`, 'gi') + return text.replace(regex, '$1') + } + + const truncateText = (text, maxLength = 200) => { + if (!text || text.length <= maxLength) return text + return text.substring(0, maxLength) + '...' + } + + const buildSnippet = (text, terms, radius = 100, maxLength = 200) => { + if (!text) return '' + if (!Array.isArray(terms) || terms.length === 0) return truncateText(text, maxLength) + + const lowerText = text.toLowerCase() + const lowerTerms = terms.map((t) => (t || '').toLowerCase()).filter(Boolean) + if (!lowerTerms.length) return truncateText(text, maxLength) + + let matchIndex = -1 + let matchLength = 0 + + lowerTerms.forEach((term) => { + const idx = lowerText.indexOf(term) + if (idx !== -1 && (matchIndex === -1 || idx < matchIndex)) { + matchIndex = idx + matchLength = term.length + } + }) + + if (matchIndex === -1) return truncateText(text, maxLength) + + const start = Math.max(0, matchIndex - radius) + const end = Math.min(text.length, matchIndex + matchLength + radius) + const snippet = text.substring(start, end) + + const prefix = start > 0 ? '… ' : '' + const suffix = end < text.length ? ' …' : '' + return `${prefix}${snippet}${suffix}` + } + + const getDocFromResult = (result) => { + if (usingAntoraIndex) { + // Antora lunr-extension uses refs like '108-1'; the page document is keyed by the first segment + const ref = String(result.ref) + const pageId = ref.split('-')[0] + return store[pageId] } - if (container.querySelector('#nomore')) { - container.removeChild(container.querySelector('#nomore')) + + // Preview: refs are URLs matching store[url] + return store[result.ref] + } + + const performSearch = (query) => { + if (!index || !query.trim()) return [] + + try { + const searchTerms = query.trim().split(/\s+/) + const results = index.search(query.trim() + '*') + + return results.map((result) => { + const doc = getDocFromResult(result) + + return { + ...doc, + ref: result.ref, + score: result.score, + searchTerms: searchTerms, + } + }) + } catch (e) { + // If wildcard search fails, try without wildcard + try { + const searchTerms = query.trim().split(/\s+/) + const results = index.search(query.trim()) + + return results.map((result) => { + const doc = getDocFromResult(result) + + return { + ...doc, + ref: result.ref, + score: result.score, + searchTerms: searchTerms, + } + }) + } catch (e2) { + console.error('Search error:', e2) + return [] + } } + } + + const renderResults = (hits, container) => { + selected = null - const nbHits = renderArgs.results.nbHits if (hits.length === 0) { container.querySelector('ul').innerHTML = '
  • No result
  • ' document.querySelector('#counter').style.display = 'none' - } else { - document.querySelector('#counter').innerHTML = `${nbHits} result${nbHits > 1 ? 's' : ''} found` - document.querySelector('#counter').style.display = 'block' - container.querySelector('ul').innerHTML = _hits - .map((hit) => { - let content = '' - let breadcrumbs = '' - let label = '' - - if (hit.content) { - content = `

    - ${instantsearch.snippet({ hit: hit, attribute: 'content' })} -

    ` - } + return + } + + const nbHits = hits.length + document.querySelector('#counter').innerHTML = `${nbHits} result${nbHits > 1 ? 's' : ''} found` + document.querySelector('#counter').style.display = 'block' + + container.querySelector('ul').innerHTML = hits + .map((hit) => { + const titles = Array.isArray(hit.titles) ? hit.titles : [] + const searchTerms = Array.isArray(hit.searchTerms) ? hit.searchTerms : [] + + const rawParentTitle = hit.title || 'Untitled' + const parentTitle = highlightText(rawParentTitle, searchTerms) - if (hit.breadcrumbs) { - breadcrumbs = `
    - ${hit.breadcrumbs - .map((chain) => { - const arr = chain.split('|') - return `${arr[0]}` - }) - .join(' > ')} -
    ` + const lowerTerms = searchTerms.map((t) => t.toLowerCase()) + const matchesTerms = (t) => { + if (!t || !lowerTerms.length) return false + const raw = t.text || '' + const snippet = buildSnippet(raw, searchTerms) || '' + const haystack = `${t.title || ''} ${snippet}`.toLowerCase() + if (!haystack.trim()) return false + return lowerTerms.some((term) => term && haystack.includes(term)) + } + + // For Antora index, hit.ref is like '108-1' where the second segment is the section id + let matchedChildId = null + if (usingAntoraIndex && hit.ref) { + const refStr = String(hit.ref) + const parts = refStr.split('-') + if (parts.length > 1) { + const maybeId = parseInt(parts[1], 10) + if (!isNaN(maybeId)) matchedChildId = maybeId } - label = Object.keys(hit.hierarchy) - .map((key, index) => { - if (index > 0 && hit) { - return instantsearch.highlight({ hit: hit, attribute: 'hierarchy.' + key }) - } - return null - }) - .filter((item) => !!item) - .join(' - ') - - return `
  • - -
    - ${decodeHtmlEntities(label)} -
    - ${decodeHtmlEntities(breadcrumbs)} - ${decodeHtmlEntities(content)} -
    -
  • ` - }) - .join('') + } - container.querySelectorAll('ul a').forEach((a) => { - a.addEventListener('click', () => { - MicroModal.close('modal-1') - }) + let filteredTitles = titles.filter((t) => matchesTerms(t)) + + if (!filteredTitles.length && matchedChildId != null) { + const matched = titles.find((t) => t && t.id === matchedChildId) + if (matched) filteredTitles = [matched] + } + + const childrenHtml = filteredTitles.length + ? `` + : '' + + return `
  • + +
    + ${parentTitle} +
    +
    + ${childrenHtml} +
  • ` }) + .join('') - if (!lastRenderArgs.isLastPage) { - const more = document.createElement('div') - const link = document.createElement('a') - more.setAttribute('id', 'showmore') - link.addEventListener('click', () => { - showMore() - return false - }) - link.innerHTML = 'Show more' - more.appendChild(link) - container.appendChild(more) - } else { - const noMore = document.createElement('div') - noMore.setAttribute('id', 'nomore') - noMore.innerHTML = 'No more result' - container.appendChild(noMore) - } - } - }) + container.querySelectorAll('ul a').forEach((a) => { + a.addEventListener('click', () => { + MicroModal.close('modal-1') + }) + }) + } const selectHit = (newSelected) => { const hits = document.querySelectorAll('#hits>ul>li>a') @@ -133,103 +234,114 @@ selected = newSelected } - if (selected) { - hits[selected].scrollIntoView() + if (selected !== null && hits[selected]) { + hits[selected].scrollIntoView({ block: 'nearest' }) } } - const openHit = (index) => { + const openHit = (idx) => { const hits = document.querySelectorAll('#hits>ul>li>a') - if (hits[index]) { - hits[index].click() + if (hits[idx]) { + hits[idx].click() MicroModal.close('modal-1') } } - const renderSearchBox = (renderOptions, isFirstRender) => { - const { query, refine, isSearchStalled, widgetParams } = renderOptions - if (isFirstRender) { - const input = document.createElement('input') - input.classList.add('ais-SearchBox-input') - input.placeholder = `Search in the current documentation ${config.pageGeneration}` - const loadingIndicator = document.createElement('span') - loadingIndicator.textContent = 'Loading...' - const button = document.createElement('button') - button.classList.add('ais-SearchBox-reset') - button.innerHTML = - '' - input.addEventListener('keydown', (event) => { - switch (event.keyCode) { - case 40: // Down - event.preventDefault() - if (selected === null) { - selectHit(0) - } else { - selectHit(selected + 1) - } - break - case 38: // Up - event.preventDefault() - if (selected === null) { - selectHit(0) - } - selectHit(Math.max(selected - 1, 0)) - break - case 13: // Enter - event.preventDefault() - if (selected !== null) { - openHit(selected) - } - break - case 9: // Tab - event.preventDefault() - break + const setupSearchUI = () => { + const searchbox = document.querySelector('#searchbox') + const hitsContainer = document.querySelector('#hits') + + if (!searchbox || !hitsContainer) return + + // Create search input + const input = document.createElement('input') + input.classList.add('ais-SearchBox-input') + input.placeholder = 'Search documentation...' + + // Create close button + const button = document.createElement('button') + button.classList.add('ais-SearchBox-reset') + button.innerHTML = + '' + + // Create results list + hitsContainer.appendChild(document.createElement('ul')) + + // Debounce search + let debounceTimer + input.addEventListener('input', (event) => { + clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => { + const query = event.target.value + if (query.trim()) { + const results = performSearch(query) + renderResults(results, hitsContainer) + } else { + hitsContainer.querySelector('ul').innerHTML = '' + document.querySelector('#counter').style.display = 'none' } - }) - input.addEventListener('input', (event) => { - refine(event.target.value) - }) - button.addEventListener('click', () => { - MicroModal.close('modal-1') - }) - widgetParams.container.appendChild(input) - widgetParams.container.appendChild(loadingIndicator) - widgetParams.container.appendChild(button) - } - widgetParams.container.querySelector('input').value = query - widgetParams.container.querySelector('span').hidden = !isSearchStalled - } + }, 150) + }) + + input.addEventListener('keydown', (event) => { + const hits = document.querySelectorAll('#hits>ul>li>a') + switch (event.keyCode) { + case 40: // Down + event.preventDefault() + if (selected === null) { + selectHit(0) + } else if (selected < hits.length - 1) { + selectHit(selected + 1) + } + break + case 38: // Up + event.preventDefault() + if (selected === null) { + selectHit(0) + } else { + selectHit(Math.max(selected - 1, 0)) + } + break + case 13: // Enter + event.preventDefault() + if (selected !== null) { + openHit(selected) + } + break + case 27: // Escape + event.preventDefault() + MicroModal.close('modal-1') + break + } + }) - const searchBox = instantsearch.connectors.connectSearchBox(renderSearchBox) - - // selected - search.addWidgets([ - instantsearch.widgets.configure({ - facetFilters: [`generation:${config.pageGeneration}`, `component:${config.pageComponent}`], - attributesToSnippet: ['content'], - attributesToHighlight: ['hierarchy'], - distinct: true, - }), - searchBox({ - container: document.querySelector('#searchbox'), - }), - infiniteHits({ - container: document.querySelector('#hits'), - }), - ]) + button.addEventListener('click', () => { + MicroModal.close('modal-1') + }) + + searchbox.appendChild(input) + searchbox.appendChild(button) + } const open = () => { - if (!loaded) { - search.start() - loaded = true + initSearch() + if (!document.querySelector('#searchbox input')) { + setupSearchUI() } selectHit(null) MicroModal.show('modal-1', { disableScroll: true, + onShow: () => { + const input = document.querySelector('#searchbox input') + if (input) { + input.focus() + input.select() + } + }, }) } diff --git a/src/partials/article-search.hbs b/src/partials/article-search.hbs index 854d8f92..e3e69ad3 100644 --- a/src/partials/article-search.hbs +++ b/src/partials/article-search.hbs @@ -6,28 +6,10 @@
    -
    - - - - -
    +
    - \ No newline at end of file + diff --git a/src/partials/article-spring-projects.hbs b/src/partials/article-spring-projects.hbs index 51c90812..9ae05b79 100644 --- a/src/partials/article-spring-projects.hbs +++ b/src/partials/article-spring-projects.hbs @@ -1,12 +1,3 @@ -
    -
    -

    Spring Projects

    - -
    diff --git a/src/partials/footer-content.hbs b/src/partials/footer-content.hbs index e16688b0..27f5ae4f 100644 --- a/src/partials/footer-content.hbs +++ b/src/partials/footer-content.hbs @@ -1,13 +1,17 @@ diff --git a/src/partials/head-icons.hbs b/src/partials/head-icons.hbs index 9ac5cb16..7634ab1f 100644 --- a/src/partials/head-icons.hbs +++ b/src/partials/head-icons.hbs @@ -1 +1 @@ - + diff --git a/src/partials/head-scripts.extend.hbs b/src/partials/head-scripts.extend.hbs new file mode 100644 index 00000000..e69de29b diff --git a/src/partials/head-styles.extend.hbs b/src/partials/head-styles.extend.hbs new file mode 100644 index 00000000..e69de29b diff --git a/src/partials/head.hbs b/src/partials/head.hbs index 7dd18b2a..8d62cc70 100644 --- a/src/partials/head.hbs +++ b/src/partials/head.hbs @@ -2,6 +2,8 @@ {{> head-title}} {{> head-info}} {{> head-styles}} +{{> head-styles.extend}} {{> head-meta}} {{> head-scripts}} +{{> head-scripts.extend}} {{> head-icons}} diff --git a/src/partials/header-content.hbs b/src/partials/header-content.hbs index 15f70b43..76e7bb85 100644 --- a/src/partials/header-content.hbs +++ b/src/partials/header-content.hbs @@ -3,10 +3,10 @@ diff --git a/src/partials/nav-search.hbs b/src/partials/nav-search.hbs index 6f0750c9..9544e968 100644 --- a/src/partials/nav-search.hbs +++ b/src/partials/nav-search.hbs @@ -1,4 +1,3 @@ -{{#if env.ALGOLIA_API_KEY}} -{{/if}} diff --git a/src/partials/nav.hbs b/src/partials/nav.hbs index b3af7d40..b01523c2 100644 --- a/src/partials/nav.hbs +++ b/src/partials/nav.hbs @@ -11,7 +11,7 @@
    @@ -23,4 +23,4 @@ document.body.classList.add('nav-sm') } }(localStorage && localStorage.getItem('sidebar') === 'close') - \ No newline at end of file + diff --git a/src/partials/related-project.hbs b/src/partials/related-project.hbs deleted file mode 100644 index fb1f26b2..00000000 --- a/src/partials/related-project.hbs +++ /dev/null @@ -1,19 +0,0 @@ -
  • -{{#if ./children}} - -{{/if}} -{{#if ./href}} - - {{./text}} - -{{else}} - {{./text}} -{{/if}} -{{#if ./children}} - -{{/if}} -
  • diff --git a/src/partials/related-projects.hbs b/src/partials/related-projects.hbs deleted file mode 100644 index b64e7a87..00000000 --- a/src/partials/related-projects.hbs +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/src/partials/search.hbs b/src/partials/search.hbs index 73ec7e25..e3cb053b 100644 --- a/src/partials/search.hbs +++ b/src/partials/search.hbs @@ -1,20 +1,9 @@ -{{#if env.ALGOLIA_API_KEY}} - - diff --git a/src/partials/version.hbs b/src/partials/version.hbs index 9810fdb5..846f01f8 100644 --- a/src/partials/version.hbs +++ b/src/partials/version.hbs @@ -15,7 +15,6 @@ {{{./title}}} - {{~#if (notEmpty ./versionTree.stable)}}
    @@ -28,18 +27,6 @@
    {{/if}} - {{~#if (notEmpty ./versionTree.preview)}} -
    -
    - -
    - {{> version-nav ./versionTree.preview}} -
    - {{/if}} - {{~#if (notEmpty ./versionTree.snapshot)}}
    @@ -51,14 +38,11 @@ {{> version-nav ./versionTree.snapshot}}
    {{/if}} - + {{/each}}
    -
    - {{> related-projects}} -
    diff --git a/test/08-copy-versioned-url-test.js b/test/08-copy-versioned-url-test.js index f3b1ceee..d1d4e0a6 100644 --- a/test/08-copy-versioned-url-test.js +++ b/test/08-copy-versioned-url-test.js @@ -19,7 +19,7 @@ describe('08-copy-versioned-url', () => { let window beforeEach(async () => { - versionedUrl = 'https://docs.spring.io/spring-security/reference/index.html' + versionedUrl = 'https://sayadev.3cortex.com/docs/products/25.11.0/index.html' button = { classes: [], click: function () {