From 841ccc5fa583709dd1a192be206b72570ad69154 Mon Sep 17 00:00:00 2001 From: daudix Date: Wed, 16 Oct 2024 22:19:22 +0300 Subject: [PATCH] Add support for Fuse.js search (fixes #101) --- CHANGELOG.md | 3 +- config.toml | 5 +- sass/_nav.scss | 14 +- static/fuse.js | 9 ++ templates/article.html | 11 ++ templates/partials/head.html | 9 +- .../{search.html => search_elasticlunr.html} | 31 ++--- templates/partials/search_fuse.html | 127 ++++++++++++++++++ 8 files changed, 184 insertions(+), 25 deletions(-) create mode 100644 static/fuse.js rename templates/partials/{search.html => search_elasticlunr.html} (86%) create mode 100644 templates/partials/search_fuse.html diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2af8d..23e9180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `classic-article-list` mod for returning the classic article list style. -- Support `fediverse:creator` meta tag. +- Add `fediverse:creator` meta tag. +- Add support for Fuse.js search (#101). ### Changed diff --git a/config.toml b/config.toml index fd3d64c..f449b55 100644 --- a/config.toml +++ b/config.toml @@ -35,7 +35,8 @@ smart_punctuation = true bottom_footnotes = true [search] -index_format = "elasticlunr_json" +# index_format = "elasticlunr_json" +index_format = "fuse_json" [languages.ar] title = "Duckquill" @@ -136,6 +137,8 @@ show_reading_time = true # Whether to show a share button in articles. # Uses https://shareopenly.org. show_share_button = true +# Whether to show the "Read Also" section with articles linked to in the article +show_backlinks = true # Whether to enable the KaTeX library for rendering LaTeX. # Note: This will make your page significantly heavier. # Instead, consider enabling it per page/section. diff --git a/sass/_nav.scss b/sass/_nav.scss index c9766df..5ce63a0 100644 --- a/sass/_nav.scss +++ b/sass/_nav.scss @@ -398,11 +398,19 @@ } span { - margin-block-start: 0.5rem; - border-block-start: max(1px, 0.0625rem) solid var(--fg-muted-2); - padding-block-start: 0.25rem; color: var(--fg-muted-5); + &:first-of-type, + &.more-matches { + margin-block-start: 0.5rem; + border-block-start: max(1px, 0.0625rem) solid var(--fg-muted-2); + padding-block-start: 0.25rem; + } + + &.more-matches { + font-size: var(--font-size-small); + } + strong { color: var(--fg-color); } diff --git a/static/fuse.js b/static/fuse.js new file mode 100644 index 0000000..1f534ad --- /dev/null +++ b/static/fuse.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2023 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?$.getFn:n,o=t.fieldNormWeight,c=void 0===o?$.fieldNormWeight:o;r(this,e),this.norm=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(F).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,m(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();m(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?$.getFn:r,o=n.fieldNormWeight,c=void 0===o?$.fieldNormWeight:o,a=new R({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(A)),a.setSources(t),a.create(),a}function N(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?$.distance:s,h=t.ignoreLocation,l=void 0===h?$.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}var W=32;function T(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?$.location:i,c=r.distance,a=void 0===c?$.distance:c,s=r.threshold,u=void 0===s?$.threshold:s,h=r.findAllMatches,l=void 0===h?$.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?$.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?$.includeMatches:v,y=r.ignoreLocation,p=void 0===y?$.ignoreLocation:y;if(t.length>W)throw new Error("Pattern length exceeds max of ".concat(W,"."));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,w=b,S=d>1||g,L=S?Array(M):[];(m=e.indexOf(t,w))>-1;){var _=N(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(_,x),w=m+k,S)for(var O=0;O=P;D-=1){var K=D-1,q=n[e.charAt(K)];if(S&&(L[K]=+!!q),z[D]=(z[D+1]<<1|1)&q,E&&(z[D]|=(j[D+1]|j[D])<<1|1|j[D+1]),z[D]&C&&(A=N(t,{errors:E,currentLocation:K,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=A,(w=K)<=b)break;P=Math.max(1,2*b-w)}}if(N(t,{errors:E+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;j=z}var B={isMatch:w>=0,score:Math.max(.001,A)};if(S){var J=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:$.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}(L,d);J.length?g&&(B.indices=J):B.isMatch=!1}return B}function z(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?$.location:o,a=i.threshold,s=void 0===a?$.threshold:a,u=i.distance,h=void 0===u?$.distance:u,l=i.includeMatches,f=void 0===l?$.includeMatches:l,d=i.findAllMatches,v=void 0===d?$.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?$.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?$.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?$.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:z(e),startIndex:t})},x=this.pattern.length;if(x>W){for(var w=0,S=x%W,L=x-S;w1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?$.location:c,s=o.threshold,u=void 0===s?$.threshold:s,h=o.distance,l=void 0===h?$.distance:h,f=o.includeMatches,d=void 0===f?$.includeMatches:f,v=o.findAllMatches,g=void 0===v?$.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?$.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?$.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?$.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new D(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(K),X=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(K),Y=[B,X,U,V,H,G,J,Q],Z=Y.length,ee=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/,te=new Set([Q.type,X.type]),ne=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?$.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?$.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?$.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?$.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?$.findAllMatches:f,v=n.location,g=void 0===v?$.location:v,y=n.threshold,p=void 0===y?$.threshold:y,m=n.distance,k=void 0===m?$.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(ee).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n;return ue(e)||(e=he(e)),function e(n){var i=Object.keys(n),o=function(e){return!!e[ae]}(n);if(!o&&i.length>1&&!ue(n))return e(he(n));if(function(e){return!g(e)&&b(e)&&!ue(e)}(n)){var c=o?n[ae]:i[0],a=o?n[se]:n[c];if(!m(a))throw new Error(function(e){return"Invalid value for key ".concat(e)}(c));var s={keyId:C(c),pattern:a};return r&&(s.searcher=ie(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];g(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u}(e)}function fe(e,t){var n=e.matches;t.matches=[],x(n)&&n.forEach((function(e){if(x(e.indices)&&e.indices.length){var n={indices:e.indices,value:e.value};e.key&&(n.key=e.key.src),e.idx>-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function de(e,t){t.score=e.score}var ve=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},$),i),this.options.useExtendedSearch,this._keyStore=new j(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof R))throw new Error("Incorrect 'index' type");this._myIndex=t||P(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){x(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{}).limit,n=void 0===t?-1:t,r=this.options,i=r.includeMatches,o=r.includeScore,c=r.shouldSort,a=r.sortFn,s=r.ignoreFieldNorm,u=m(e)?m(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return function(e,t){var n=t.ignoreFieldNorm,r=void 0===n?$.ignoreFieldNorm:n;e.forEach((function(e){var t=1;e.matches.forEach((function(e){var n=e.key,i=e.norm,o=e.score,c=n?n.weight:null;t*=Math.pow(0===o&&c?Number.EPSILON:o,(c||1)*(r?1:i))})),e.score=t}))}(u,{ignoreFieldNorm:s}),c&&u.sort(a),k(n)&&n>-1&&(u=u.slice(0,n)),function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?$.includeMatches:r,o=n.includeScore,c=void 0===o?$.includeScore:o,a=[];return i&&a.push(fe),c&&a.push(de),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}(u,this._docs,{includeMatches:i,includeScore:o})}},{key:"_searchStringList",value:function(e){var t=ie(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(x(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=le(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?$.getFn:n,i=t.fieldNormWeight,o=void 0===i?$.fieldNormWeight:i,c=e.keys,a=e.records,s=new R({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ve.config=$,function(){re.push.apply(re,arguments)}(ne),ve},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/templates/article.html b/templates/article.html index 9b0b602..a2e8c9a 100644 --- a/templates/article.html +++ b/templates/article.html @@ -117,6 +117,17 @@ {{ page.content | safe }} +{%- if config.extra.show_backlinks and page.backlinks | length > 0 -%} +

{{ macros_translate::translate(key="backlinks", default="Backlinks", language_strings=language_strings) }}

+ +{%- endif -%} + {%- if page.extra.comments.id -%} {% include "partials/comments.html" %} {%- endif -%} diff --git a/templates/partials/head.html b/templates/partials/head.html index 9ace957..9aa8bd8 100644 --- a/templates/partials/head.html +++ b/templates/partials/head.html @@ -92,8 +92,13 @@ {%- endif %} {%- if config.build_search_index %} - {%- include "partials/search.html" %} - {%- set scripts = scripts | concat(with=["elasticlunr.min.js"]) %} + {%- if config.search.index_format == "elasticlunr_json" -%} + {%- include "partials/search_elasticlunr.html" %} + {%- set scripts = scripts | concat(with=["elasticlunr.min.js"]) %} + {%- elif config.search.index_format == "fuse_json" -%} + {%- include "partials/search_fuse.html" %} + {%- set scripts = scripts | concat(with=["fuse.js"]) %} + {%- endif -%} {%- endif %} {%- if config.extra.nav.show_theme_switcher %} diff --git a/templates/partials/search.html b/templates/partials/search_elasticlunr.html similarity index 86% rename from templates/partials/search.html rename to templates/partials/search_elasticlunr.html index 5e6fb5f..a934fd2 100644 --- a/templates/partials/search.html +++ b/templates/partials/search_elasticlunr.html @@ -132,8 +132,9 @@ } function initSearch() { - var $searchInput = document.getElementById("search-bar"); - var $searchResults = document.getElementById("search-results"); + var searchBar = document.getElementById("search-bar"); + var searchContainer = document.getElementById("search-container"); + var searchResults = document.getElementById("search-results"); var MAX_ITEMS = 10; var options = { @@ -159,13 +160,13 @@ return res; } - $searchInput.addEventListener("keyup", debounce(async function () { - var term = $searchInput.value.trim(); + searchBar.addEventListener("keyup", debounce(async function () { + var term = searchBar.value.trim(); if (term === currentTerm) { return; } - $searchResults.style.display = term === "" ? "none" : "flex"; - $searchResults.innerHTML = ""; + searchResults.style.display = term === "" ? "none" : "flex"; + searchResults.innerHTML = ""; currentTerm = term; if (term === "") { return; @@ -173,18 +174,19 @@ var results = (await initIndex()).search(term, options); if (results.length === 0) { - $searchResults.style.display = "none"; + searchResults.style.display = "none"; return; } for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) { - $searchResults.innerHTML += formatSearchResultItem(results[i], term.split(" ")); + searchResults.innerHTML += formatSearchResultItem(results[i], term.split(" ")); } }, 150)); - window.addEventListener('click', function (e) { - if ($searchResults.style.display == "flex" && !$searchResults.contains(e.target)) { - $searchResults.style.display = "none"; + document.addEventListener("keydown", function(event) { + if (event.key === "/") { + event.preventDefault(); + toggleSearch(); } }); } @@ -197,13 +199,6 @@ searchBar.focus(); } - document.addEventListener("keydown", function(event) { - if (event.key === "/") { - event.preventDefault(); - toggleSearch(); - } - }); - if (document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll) ) { diff --git a/templates/partials/search_fuse.html b/templates/partials/search_fuse.html new file mode 100644 index 0000000..7043370 --- /dev/null +++ b/templates/partials/search_fuse.html @@ -0,0 +1,127 @@ +{#- Based on https://codeberg.org/daudix/duckquill/issues/101#issuecomment-2377169 -#} + +