diff --git a/Gemfile b/Gemfile index 43603483d9..1917aed1d1 100644 --- a/Gemfile +++ b/Gemfile @@ -36,3 +36,6 @@ gem "wdm", "~> 0.1.0" if Gem.win_platform? gem "webrick", "~> 1.7" + +# Copy search-wc.js from the search-wc server +gem "open-uri", "~> 0.4.1" diff --git a/Gemfile.lock b/Gemfile.lock index b5fee9299a..662aae5d23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,6 +6,7 @@ GEM asciidoctor (2.0.15) colorator (1.1.0) concurrent-ruby (1.1.8) + date (3.3.4) em-websocket (0.5.2) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) @@ -61,6 +62,10 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) + open-uri (0.4.1) + stringio + time + uri pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.6) @@ -72,9 +77,13 @@ GEM safe_yaml (1.0.5) sassc (2.4.0) ffi (~> 1.9) + stringio (3.1.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + time (0.3.0) + date unicode-display_width (1.7.0) + uri (0.13.0) webrick (1.7.0) PLATFORMS @@ -88,6 +97,7 @@ DEPENDENCIES jekyll-feed (~> 0.6) jekyll-paginate-v2 minima (~> 2.0) + open-uri (~> 0.4.1) tzinfo-data webrick (~> 1.7) diff --git a/_config.yml b/_config.yml index 4157a40ed6..6de7d3f204 100755 --- a/_config.yml +++ b/_config.yml @@ -26,8 +26,14 @@ github_fork_url: "https://github.com/quarkusio/quarkus" # See https://github.com/quarkusio/search.quarkus.io/blob/main/src/main/java/io/quarkus/search/app/entity/Language.java language: en search: + # The script mode is direct or cached. Cached means the search script is copied from the remote service. + script-mode: "cached" # The URL of the remote search service - url: "https://search.quarkus.io/" + host: "https://search.quarkus.io" + # The path to the search script (relative from the search.host) + script-path: "/static/bundle/main.js" + # Where to copy the search script file for cached mode + cached-script-file: "assets/javascript/search-wc.js" # The amount of time before we give up on a pending remote search and fall back to Javascript search. # The search service itself is reasonably fast on a decent machine (with curl: ~100ms median, ~150ms 90th percentile). # but it's slower on prod machines (with curl: ~200ms median, ~400ms 90th percentile), @@ -57,6 +63,9 @@ plugins: - jekyll-archives - jekyll-auto-authors +keep_files: + - assets/javascripts/search-wc.js + sass: style: compressed diff --git a/_config_dev.yml b/_config_dev.yml index 82d17b1a38..13d6a95678 100755 --- a/_config_dev.yml +++ b/_config_dev.yml @@ -2,4 +2,9 @@ # We don't want to use the production instance of search.quarkus.io for development. search: - url: "http://localhost:8080/" + +# Use "direct" to directly use the local instance of search.quarkus.io +# Use "cached" to use a local copy of the search script + script-mode: "cached" + host: "http://localhost:8080" + diff --git a/_includes/index-doc-item.html b/_includes/index-doc-item.html index 42b63fe95d..95b30929d4 100644 --- a/_includes/index-doc-item.html +++ b/_includes/index-doc-item.html @@ -1,17 +1,21 @@ {% assign is_external_guide = include.url | startswith: 'http' %} {% if is_external_guide %} - {% assign guide_url = include.url %} +{% assign guide_url = include.url %} {% elsif include.docversion == 'latest' %} - {% assign guide_url = site.baseurl | append: include.url %} +{% assign guide_url = site.baseurl | append: include.url %} {% else %} - {% assign guide_url = site.baseurl | append: '/version/' | append: include.docversion | append: include.url %} +{% assign guide_url = site.baseurl | append: '/version/' | append: include.docversion | append: include.url %} {% endif %} -
+ +

{{ include.title }}

{{ include.summary | markdownify }}
-
{{ include.keywords }}
-
{{ include.categories }}
- {% if include.origin == 'quarkiverse-hub' -%} -
Quarkiverse Hub
- {% endif -%} -
+ + diff --git a/_includes/index-docs.html b/_includes/index-docs.html index 9201787f8f..1dd1503eea 100644 --- a/_includes/index-docs.html +++ b/_includes/index-docs.html @@ -3,162 +3,140 @@ {% assign index = site.data.versioned[docversion_index].index %} {% assign categories = index.quarkus.categories %} {% assign by_type = index.quarkus | map: "types" | first %} - -
-
-
-
- -
-
- -
-
- -
-
- -
-
+ + +

{{ page.title }}

-
-
- -
- Sorry, no guides matched your search. Please try again. -
-
-
- {% raw %} -
-

-
-
-

-
-
- {% endraw %} - -
- Loading... -
-
-
-
-
+ -
- {% include index-docs-merge.html type="tutorial" %} - {% if values %} -
-
-

Tutorials

-

Short and focused exercises to get you going quickly.

-
-
- {% for guide in values %} - {% include index-doc-item.html class="tutorialbkg" docversion=docversion + +
+ {% include index-docs-merge.html type="tutorial" %} + {% if values %} +
+
+

Tutorials

+

Short and focused exercises to get you going quickly.

+
+
+ {% for guide in values %} + {% include index-doc-item.html type="tutorial" docversion=docversion title=guide.title url=guide.url summary=guide.summary keywords=guide.keywords categories=guide.categories origin=guide.origin %} - {% endfor %} -
-
- {% endif %} - {% include index-docs-merge.html type="howto" %} - {% if values %} -
-
-

How-to Guides

-

Step-by-step guides to covering key tasks, real world operations and common problems.

+ {% endfor %} +
-
- {% for guide in values %} - {% include index-doc-item.html class="howtobkg" docversion=docversion + {% endif %} + {% include index-docs-merge.html type="howto" %} + {% if values %} +
+
+

How-to Guides

+

Step-by-step guides to covering key tasks, real world operations and common problems.

+
+
+ {% for guide in values %} + {% include index-doc-item.html type="howto" docversion=docversion title=guide.title url=guide.url summary=guide.summary keywords=guide.keywords categories=guide.categories origin=guide.origin %} - {% endfor %} -
-
- {% endif %} - {% include index-docs-merge.html type="concepts" %} - {% if values %} -
-
-

Concepts

-

Explanations of some of the larger concepts and technologies involved with Quarkus.

+ {% endfor %} +
-
- {% for guide in values %} - {% include index-doc-item.html class="conceptsbkg" docversion=docversion + {% endif %} + {% include index-docs-merge.html type="concepts" %} + {% if values %} +
+
+

Concepts

+

Explanations of some of the larger concepts and technologies involved with Quarkus.

+
+
+ {% for guide in values %} + {% include index-doc-item.html type="concepts" docversion=docversion title=guide.title url=guide.url summary=guide.summary keywords=guide.keywords categories=guide.categories origin=guide.origin %} - {% endfor %} -
-
- {% endif %} - {% include index-docs-merge.html type="reference" %} - {% if values %} -
-
-

References

-

Technical Resource that covers tools, components, and commands. The encyclopedia for Quarkus.

+ {% endfor %} +
-
-
-

Quarkus Cheat Sheet

- -
-
+ {% endif %} + {% include index-docs-merge.html type="reference" %} + {% if values %} +
+
+

References

+

Technical Resource that covers tools, components, and commands. The encyclopedia for Quarkus.

- {% for guide in values %} - {% include index-doc-item.html class="referencebkg" docversion=docversion +
+ {% include index-doc-item.html type="pdf" docversion=docversion + title="Quarkus Cheat + Sheet" url="https://lordofthejars.github.io/quarkus-cheat-sheet/" summary="Download full cheatsheet as PDF." %} + {% for guide in values %} + {% include index-doc-item.html type="reference" docversion=docversion title=guide.title url=guide.url summary=guide.summary keywords=guide.keywords categories=guide.categories origin=guide.origin %} - {% endfor %} -
-
- {% endif %} - {% include index-docs-merge.html type="guide" %} - {% if values %} -
-
-

General Guides

-

Other Quarkus Guides

+ {% endfor %} +
-
- {% for guide in values %} - {% include index-doc-item.html class="guidebkg" docversion=docversion + {% endif %} + {% include index-docs-merge.html type="guide" %} + {% if values %} +
+
+

General Guides

+

Other Quarkus Guides

+
+
+ {% for guide in values %} + {% include index-doc-item.html type="guides" docversion=docversion title=guide.title url=guide.url summary=guide.summary keywords=guide.keywords categories=guide.categories origin=guide.origin %} - {% endfor %} + {% endfor %} +
+ {% endif %}
- {% endif %} -
+
- \ No newline at end of file + diff --git a/_includes/index-guides.html b/_includes/index-guides.html index 40306dd576..11c1d9c25a 100644 --- a/_includes/index-guides.html +++ b/_includes/index-guides.html @@ -8,124 +8,87 @@ data-initial-timeout="{{ site.search.initial-timeout }}" data-more-timeout="{{ site.search.more-timeout }}" data-min-chars="{{ site.search.min-chars }}"> +
- + {% for version in site.data.versions.documentation %} + + {% endfor %}
+
+
+

{{ page.title }}

+
-
-

{{ page.title }}

-
+ + +
-
-

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 %} +
+
- -
- -
- Sorry, no guides matched your search. Please try again. -
-
-
    -
  • -
      - {% raw %} -
    • - -

      -
      -
      -

      -
      -
      - - Quarkiverse Hub -
      -
    • - {% endraw %} -
    -
  • -
- -
- Loading... -
-
+
+ - -
+
+
    - {% for item in site.data[data_source].categories %} + {% for item in site.data[data_source].categories %}
  • {{ item.category }}

    -
      - {% for guide in item.guides %} - {% assign is_external_guide = guide.url | startswith: 'http' %} - {% if is_external_guide %} - {% assign guide_url = guide.url %} - {% elsif docversion == 'latest' %} - {% assign guide_url = site.baseurl | append: guide.url %} - {% else %} - {% assign guide_url = site.baseurl | append: '/version/' | append: docversion | append: guide.url %} - {% endif %} -
    • - -

      {{ guide.title }}

      -
      {{ guide.description | markdownify }}
      -
      {{ guide.keywords }}
      - {% if guide.origin == 'quarkiverse-hub' %} -
      - - Quarkiverse Hub -
      - {% endif %} -
    • - {% endfor %} -
    +
    + {% for guide in item.guides %} + {% include index-doc-item.html docversion=docversion + title=guide.title url=guide.url summary=guide.description + keywords=guide.keywords categories=guide.categories origin=guide.origin %} + {% endfor %} +
  • - {% endfor %} + {% endfor %}
-
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) -}