-
-
View Category
-
- {% for item in site.data[data_source].categories %}
- -
- {{ item.category }}
-
- {% endfor %}
-
-
+
+
View Category
+
+ {% for item in site.data[data_source].categories %}
+ -
+ {{ item.category }}
+
+ {% endfor %}
+
+
-
-
-
-
Quarkus Cheat Sheet
-
-
-
-
-
-
-
-
- Sorry, no guides matched your search. Please try again.
-
-
-
- -
-
- {% raw %}
- -
-
-
-
-
-
-
-
Quarkiverse Hub
-
-
- {% endraw %}
-
-
-
-
-
- Loading...
-
-
+
+
+
+
Quarkus Cheat Sheet
+
+
+
-
-
+
+
-
Sorry, no guides matched your search. Please try again.
+
-
diff --git a/_layouts/base.html b/_layouts/base.html
index d5ed30c76d..06e612bcb4 100755
--- a/_layouts/base.html
+++ b/_layouts/base.html
@@ -1,6 +1,5 @@
-
{% assign page_title_version_suffix = '' %}
{% if page.url contains '/guides/' %}
{% assign versioned_page = page.url | startswith: '/version/' %}
@@ -15,13 +14,25 @@
{% assign page_title_starts_with_quarkus = false %}
{% assign page_title_ends_with_quarkus = false %}
{% endif %}
+{% if page.search_wc or layout.search_wc %}
+ {% assign search_wc = true %}
+{% endif %}
+{% assign remote_search_script = site.search.script-mode | equals: 'direct' %}
+{% if search_wc %}
+ {% if remote_search_script %}
+ {% assign search_script = site.search.host | append: site.search.script-path %}
+ {% assign search_script_src = search_script %}
+ {% else %}
+ {% assign search_script_src = site.search.cached-script-file | relative_url %}
+ {% endif %}
+{% endif %}
{{ page.title }}{{ page_title_version_suffix }}{% unless page_title_starts_with_quarkus or page_title_ends_with_quarkus %} - Quarkus{% endunless %}
-
+
@@ -58,6 +69,10 @@
{% endfor %}
+
+ {% if search_wc %}
+
+ {% endif %}
diff --git a/_layouts/documentation.html b/_layouts/documentation.html
index fc1c0374ee..53902641ae 100644
--- a/_layouts/documentation.html
+++ b/_layouts/documentation.html
@@ -1,5 +1,6 @@
---
layout: base
+search_wc: true
---
{% assign versioned_page = page.url | startswith: '/version/' %}
{% if versioned_page %}
diff --git a/_plugins/copy-search-wc.rb b/_plugins/copy-search-wc.rb
new file mode 100644
index 0000000000..650052b759
--- /dev/null
+++ b/_plugins/copy-search-wc.rb
@@ -0,0 +1,40 @@
+require 'open-uri'
+
+module Jekyll
+ class CopySearchScript < Jekyll::Plugin
+ Jekyll::Hooks.register :site, :post_write do |site|
+ script_mode = site.config['search']['script-mode']
+ script_path = site.config['search']['script-path']
+ search_host = site.config['search']['host']
+ script_copy_from_url = URI(search_host + script_path)
+
+ if script_mode == 'cached'
+ if search_host.nil? || search_host.empty?
+ raise "Error: The search host URL is not configured."
+ end
+ cached_script_file = site.config['search']['cached-script-file']
+ assets_path = File.join(site.config['destination'], cached_script_file)
+ if script_copy_from_url.host == 'localhost' && File.exist?('/.dockerenv')
+ script_copy_from_url.host = 'host.docker.internal'
+ end
+ begin
+ URI.open(script_copy_from_url) do |response|
+ content = response.read
+ if response.status[0] != '200'
+ raise "Error: The search wc '#{script_copy_from_url}' is not accessible. Status: #{response.status[0]} - #{response.status[1]}"
+ elsif content.nil? || content.empty?
+ raise "Error: The search wc '#{script_copy_from_url}' is empty."
+ else
+ content_without_sourcemaps = content.gsub(/\/\/# sourceMappingURL=.*\.map/, '')
+ File.delete(assets_path) if File.exist?(assets_path)
+ File.open(assets_path, 'w') { |file| file.write(content_without_sourcemaps) }
+ puts "The search wc '#{script_copy_from_url}' has been copied to '#{assets_path}'."
+ end
+ end
+ rescue OpenURI::HTTPError => e
+ raise "Error: The search wc '#{script_copy_from_url}' is not accessible. Status: #{e.io.status[0]} - #{e.io.status[1]}"
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/_plugins/strings.rb b/_plugins/strings.rb
index fe618d5472..3dc1783b09 100644
--- a/_plugins/strings.rb
+++ b/_plugins/strings.rb
@@ -1,5 +1,10 @@
module Jekyll
- module StringsFilter
+ module StringsFilter
+
+ def equals(text, query)
+ return text == query
+ end
+
def startswith(text, query)
return text.start_with? query
end
@@ -9,5 +14,5 @@ def endswith(text, query)
end
end
end
-
+
Liquid::Template.register_filter(Jekyll::StringsFilter)
diff --git a/_sass/layouts/documentation.scss b/_sass/layouts/documentation.scss
index b7d9e6ea9b..f8818a735d 100644
--- a/_sass/layouts/documentation.scss
+++ b/_sass/layouts/documentation.scss
@@ -6,7 +6,7 @@ Styles for the documentation index page
-/*=============== Documenation Filter bar & Guide Bar div styles =================== */
+/*=============== Documentation Filter bar & Guide Bar div styles =================== */
.flexfilterbar {
@@ -200,6 +200,7 @@ Styles for the documentation index page
/*=============== Documentation Main Page - docs list =================== */
+
.docslist {
margin: 2rem 0;
@@ -242,25 +243,6 @@ Styles for the documentation index page
padding: 0;
}
- .description {
- margin: 1rem 0 0 90px;
- font-size: 1rem;
- line-height: 1.3rem;
-
- p:last-child {
- margin-bottom: .5rem;
- }
- }
-
- .content-highlights {
- margin: 1rem 0 0 90px;
- p {
- font-size: .7rem;
- line-height: .8rem;
- opacity: 0.8;
- margin-bottom: .5rem;
- }
- }
&.quarkiverse .origin {
padding-left: 120px;
@@ -278,40 +260,5 @@ Styles for the documentation index page
left: 90px;
}
- span.highlighted {
- font-weight: bold;
- color: inherit;
- line-height: inherit;
- }
- }
-
- .tutorialbkg {
- background: url($baseurl + '/assets/images/documentation/docsicon-tutorials.svg') no-repeat;
- background-size: 80px 80px;
- }
-
- .guidebkg {
- background: url($baseurl + '/assets/images/documentation/docsicon-referencedocs.svg') no-repeat;
- background-size: 80px 80px;
- }
-
- .howtobkg {
- background: url($baseurl + '/assets/images/documentation/docsicon-guides.svg') no-repeat;
- background-size: 80px 80px;
- }
-
- .conceptsbkg {
- background: url($baseurl + '/assets/images/documentation/docsicon-concepts.svg') no-repeat;
- background-size: 80px 80px;
- }
-
- .pdfbkg {
- background: url($baseurl + '/assets/images/documentation/docsicon-pdf.svg') no-repeat;
- background-size: 80px 80px;
- }
-
- .referencebkg {
- background: url($baseurl + '/assets/images/documentation/docsicon-referencedocs.svg') no-repeat;
- background-size: 80px 80px;
}
}
diff --git a/_sass/layouts/guides.scss b/_sass/layouts/guides.scss
index cadd506da4..954788f2f3 100644
--- a/_sass/layouts/guides.scss
+++ b/_sass/layouts/guides.scss
@@ -1,3 +1,4 @@
+
.guides-configuration-reference {
background: $white;
}
@@ -132,62 +133,37 @@ div.guides-configuration-reference {
}
-/*=============== Styles for Guides Search Results =================== */
+/*=============== Styles for Guides Search =================== */
+qs-form > section {
+ display: none;
+}
-.guides {
- ul.list > li {
- margin-bottom: 3rem;
- }
+qs-target {
+ qs-guide {
+ grid-column: span 4;
+ margin: 1rem 0rem 1rem 0rem;
- &.hidden, & .hidden {
- display: none;
- }
-
- &.vuejs {
- /* Hide by default, for people who disable javascript */
- display: none;
- &.vuejs-enabled {
- /* Display when this class is added (which means javascript and vuejs are enabled) */
- display: block;
+ @media screen and (max-width: 1300px) {
+ grid-column: span 6;
}
- .fade-in-enter-active {
- transition-property: opacity;
- transition-duration: .2s;
- transition-timing-function: ease-in;
- }
- .fade-in-leave-active {
- /* No transition out, to avoid displaying the "empty" placeholder next to the "loading" placeholder */
- }
- .fade-in-enter-from,
- .fade-in-leave-to {
- opacity: 0;
+ @media screen and (max-width: 768px) {
+ grid-column: span 12;
+ margin: 1rem 0rem 1rem 0rem;
}
- &.results {
- &.empty .empty-placeholder, &.loading .loading-placeholder {
- padding: 2rem;
- font-size: 1.2rem;
- line-height: 1.5;
- font-weight: 400;
- font-style: italic;
- text-align: center;
- }
- &.empty .empty-placeholder {
- background: $yellow;
- }
- &.loading {
- /* Avoid flickering when we switch from a long list to "loading" and back */
- min-height: 800px;
- }
+ @media screen and (max-width: 480px) {
+ grid-column: span 12;
}
}
+}
+
+.guides {
- .categories,
- .keywords {
- display:none;
+ ul.list > li {
+ margin-bottom: 3rem;
}
.origin {
diff --git a/assets/javascript/guides-app.js b/assets/javascript/guides-app.js
deleted file mode 100644
index 4ec08e4960..0000000000
--- a/assets/javascript/guides-app.js
+++ /dev/null
@@ -1,301 +0,0 @@
-import { createApp } from './vue.esm-browser.prod.js'
-
-// https://blog.logrocket.com/debounce-throttle-vue/
-function debounce(wait, fn) {
- let timer;
- return function(...args) {
- if (timer) {
- clearTimeout(timer) // clear any pre-existing timer
- }
- const context = this // get the current context
- timer = setTimeout(() => {
- fn.apply(context, args) // call the function if time expires
- }, wait)
- }
-}
-
-function concat(fn1, fn2) {
- return function(...args) {
- fn1.apply(this, args)
- fn2.apply(this, args)
- }
-}
-
-const appSelector = '#guides-app'
-const appElement = document.querySelector(appSelector);
-
-const app = createApp({
- props: {
- searchApiServer: String,
- quarkusVersion: String,
- language: String,
- initialTimeout: Number,
- moreTimeout: Number,
- minChars: Number
- },
- data() {
- return {
- loading: false,
- search: {
- input: {
- },
- page: null,
- result: {
- hits: [],
- hasMoreHits: false
- }
- },
- guidesPathToCardHtmlElement: {
- }
- }
- },
- watch: {
- 'search.input.q': {
- handler: concat(
- // Without debouncing, we want to cancel the previous search and mark the view as "loading",
- // so that we don't mistakenly display "no results" while debouncing the initial search.
- // See https://github.com/quarkusio/search.quarkus.io/issues/200
- function(newValue, oldValue) {
- this.resetAndMarkLoading()
- },
- // "debounce" makes sure we only run search ~300ms after the user is done typing the text
- // WARNING: we really do want to debounce here, NOT in setters,
- // because debouncing in setters leads to data in input forms being refreshed after the timeout,
- // causing problems when typing text relatively fast.
- debounce(300, function(newValue, oldValue) {
- this.resetAndSearch()
- })
- )
- },
- 'search.input.categories': {
- handler(newValue, oldValue) {
- this.resetAndSearch()
- }
- }
- },
- computed: {
- text: {
- get() {
- return this.search.input.q
- },
- set(val) {
- this.search.input.q = val
- }
- },
- category: {
- get() {
- // Turn "no entry" into "", because that's how the select box refers to "all categories"
- return this.search.input.categories || ""
- },
- set(val) {
- if (val) {
- this.search.input.categories = val
- }
- // Turn null/"" into "no entry", because not specifying the category
- // is how we get the search service to return all categories
- else {
- delete this.search.input.categories
- }
- }
- },
- hasInput() {
- return this.search.input.q && this.search.input.q.length >= this.minChars || this.search.input.categories
- },
- hasInputWithTooFewChars() {
- return this.search.input.q && this.search.input.q.length < this.minChars
- },
- hasHits() {
- return this.search.result.hits.length > 0
- },
- searchHits() {
- return this.search.result.hits
- }
- },
- mounted() {
- const appElement = this.$el.parentElement
-
- // Retrieve cards from the static HTML, so that we can use them to display search results.
- let cardSelector = '.docs-card'
- let cards = appElement.querySelectorAll(cardSelector)
- if (cards.length == 0) {
- // Fallback for older versions of the docs
- cardSelector = '.card'
- cards = appElement.querySelectorAll(cardSelector)
- }
- this.guidesPathToCardHtmlElement = new Map(Array.from(cards)
- .map(element => {
- const link = element.querySelector('h4 a')
- if (link) {
- // new versions:
- const url = link.getAttribute('href');
- return [
- new URL(link.href).pathname,
- {
- url: url,
- title: link.innerHTML,
- type: [...element.classList]
- .filter(clazz => clazz.endsWith("bkg"))
- .map(clazz => clazz.substring(0, clazz.length - "bkg".length))
- .at(0),
- summary: element.querySelector('div .description').innerHTML,
- keywords: element.querySelector('div .keywords').innerHTML,
- categories: element.querySelector('div .categories').innerHTML,
- origin: element.querySelector('div .origin')?.innerHTML
- }];
- } else {
- // older Quarkus versions:
- const url = element.querySelector('a').getAttribute('href')
- return [
- url,
- {
- url: url,
- title: element.querySelector('p.title').innerHTML,
- summary: element.querySelector('div.description').innerHTML,
- keywords: element.querySelector('div.keywords').innerHTML,
- origin: element.querySelector('div.origin')?.innerHTML
- }];
- }
- }))
-
- // Load more results on scroll
- document.addEventListener('scroll', e => {
- if (!this.search.result.hasMoreHits) {
- // No more hits to fetch.
- return
- }
- const resultCards = appElement.querySelectorAll('.results ' + cardSelector)
- const lastResultCard = resultCards.length == 0 ? null : resultCards[resultCards.length - 1]
- if (!lastResultCard) {
- // No result card is being displayed at the moment.
- return
- }
- const scrollElement = document.documentElement // Scroll bar is on the element
- const bottomOfViewport = scrollElement.scrollTop + scrollElement.clientHeight
- const topOfLastResultCard = lastResultCard.offsetTop
- if (bottomOfViewport >= topOfLastResultCard) {
- this.searchMore()
- }
- })
- },
- methods: {
- async resetAndMarkLoading() {
- if (this.loading) {
- this.loading.abort()
- }
- this.loading = new AbortController()
- this._resetResults()
- },
- async resetAndSearch() {
- this.resetAndMarkLoading()
- await this._searchBatch(this.loading, this.initialTimeout)
- },
- async searchMore() {
- if (this.loading) {
- return // Already searching
- }
- this.loading = new AbortController();
- this.search.page = this.search.page + 1
- await this._searchBatch(this.loading, this.moreTimeout)
- },
- _resetResults() {
- this.search.page = 0
- this.search.result.hits = []
- this.search.result.hasMoreHits = false
- },
- async _searchBatch(controller, timeout) {
- try {
- if (!this.hasInput) {
- // No input => no search
- return
- }
- if (this.hasInputWithTooFewChars) {
- throw 'Too few characters'
- }
- const queryParams = {
- page: this.search.page,
- version: this.quarkusVersion,
- language: this.language,
- contentSnippets: 2,
- contentSnippetsLength: 120,
- highlightCssClass: 'highlighted'
- }
- Object.assign(queryParams, this.search.input)
- const result = await this._jsonFetch(controller, 'GET', queryParams, timeout)
- this.search.result.hits = this.search.result.hits.concat(this._processHits(result.hits))
- this.search.result.hasMoreHits = result.hits.length > 0
- }
- catch(error) {
- console.error('Could not run search: ' + error)
- if (this.loading != controller) {
- // A concurrent search erased ours; most likely input changed while waiting for results.
- // Ignore this search and let the concurrent one reset the data as it sees fit.
- return
- }
- this._resetResults()
- // Fall back to Javascript in-page search
- const hits = this._searchUsingJavascript()
- this.search.result.hits = hits
- }
- finally {
- if (this.loading == controller) {
- this.loading = null
- }
- }
- },
- _processHits(serverHits) {
- return serverHits.map(hit => {
- hit.content = hit?.content.map(paragraph => `...${paragraph}...`)
- return hit;
- })
- },
- async _jsonFetch(controller, method, queryParams, timeout) {
- const timeoutId = setTimeout(() => controller.abort(), timeout)
- const response = await fetch(`${this.searchApiServer}api/guides/search?${new URLSearchParams( queryParams )}`, {
- method: method,
- signal: controller.signal,
- body: null
- })
- clearTimeout(timeoutId)
- if (response.ok) {
- return await response.json()
- }
- else {
- throw 'Response status is ' + response.status + '; response: ' + await response.text()
- }
- },
- _searchUsingJavascript() {
- const terms = this.search.input.q ? this.search.input.q.split(' ').map(token => token.trim()) : null
- const categories = this.search.input.categories
-
- return Array.from(this.guidesPathToCardHtmlElement)
- .filter(([path, card]) => this._javascriptFilter(card, terms, categories))
- .map(([_, card]) => card)
- },
- _javascriptFilter(card, terms, categories) {
- let match = true
- if (match && categories) {
- match = this._containsAllCaseInsensitive(card.categories, categories)
- }
- if (match && terms) {
- match = this._containsAllCaseInsensitive(`${card.keywords}${card.summary}${card.title}${card.categories}`, terms)
- }
- return match
- },
- _containsAllCaseInsensitive(elem, terms) {
- const text = (elem ? elem : '').toLowerCase();
- for (let i in terms) {
- if (text.indexOf(terms[i].toLowerCase()) < 0) {
- return false
- }
- }
- return true
- }
- }
-},
-// Pass data-* elements as props: https://stackoverflow.com/a/64010905/6692043
-{ ...appElement.dataset }
-)
-app.mount(appSelector)
-app.config.errorHandler = (err, instance, info) => {
- console.error(err)
-}