diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..3e8c1dc --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/jekyll +{ + "name": "Jekyll", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/jekyll:2-bullseye", + + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers-contrib/features/node-asdf:0": {} + } + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "jekyll --version" + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2b740bf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +# Unix-style newlines with a newline ending every file +end_of_line = lf +insert_final_newline = true + +[*.{js,css,scss}] +quote_type = single + +[*.{yml,yaml}] +quote_type = double + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d425a5c..2d7e608 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,9 +4,8 @@ jobs: build_deploy: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@master - with: - ref: refs/heads/master + - name: Checkout + uses: actions/checkout@v4 - name: install run: | npm install @@ -22,6 +21,7 @@ jobs: # record: false # # cache-key: node-v${{ matrix.node }}-on-${{ runner.os }}-hash-${{ hashFiles('package-lock.json') }} - name: publish + if: github.ref == 'refs/heads/master' env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: | diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..dceda2d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "semi": false +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c112f5b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "ms-vscode-remote.remote-containers", + "github.vscode-github-actions" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 0127993..32d69bd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # [Simple-Jekyll-Search](https://www.npmjs.com/package/simple-jekyll-search) -[![Build Status](https://img.shields.io/travis/christian-fei/Simple-Jekyll-Search/master.svg?)](https://travis-ci.org/christian-fei/Simple-Jekyll-Search) -[![dependencies Status](https://img.shields.io/david/christian-fei/Simple-Jekyll-Search.svg)](https://david-dm.org/christian-fei/Simple-Jekyll-Search) -[![devDependencies Status](https://img.shields.io/david/dev/christian-fei/Simple-Jekyll-Search.svg)](https://david-dm.org/christian-fei/Simple-Jekyll-Search?type=dev) - A JavaScript library to add search functionality to any Jekyll blog. ## Use case @@ -328,7 +324,3 @@ Thanks to all [contributors](https://github.com/christian-fei/Simple-Jekyll-Sear [@kremalicious](https://github.com/kremalicious) [@tibotiber](https://github.com/tibotiber) and many others! - -## Stargazers over time - -[![Stargazers over time](https://starchart.cc/christian-fei/Simple-Jekyll-Search.svg)](https://starchart.cc/christian-fei/Simple-Jekyll-Search) diff --git a/dest/simple-jekyll-search.js b/dest/simple-jekyll-search.js index 364163b..df066db 100644 --- a/dest/simple-jekyll-search.js +++ b/dest/simple-jekyll-search.js @@ -1,5 +1,5 @@ /*! - * Simple-Jekyll-Search + * Simple-Jekyll-Search 1.12.0 * Copyright 2015-2024, Christian Fei * Licensed under the MIT License. */ @@ -79,23 +79,29 @@ function FuzzySearchStrategy () { var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy() +const segmenter = new Intl.Segmenter([], { granularity: 'word' }) + function LiteralSearchStrategy () { this.matches = function (str, crit) { - if (!str) return false + if (!str) { + return false + } str = str.trim().toLowerCase() + crit = crit.trim().toLowerCase() - let exact = false - if (crit.endsWith(' ')) { - exact = true - } + let critArray = [] if (crit.startsWith('"') && crit.endsWith('"')) { - exact = true - crit = crit.substring(1, crit.length - 1) + critArray = [crit.substring(1, crit.length - 1)] + } else { + const segmentedText = segmenter.segment(crit) + critArray = [...segmentedText] + .filter((s) => s.isWordLike) + .map((s) => s.segment) } - crit = crit.toLowerCase() - crit = exact ? [crit] : crit.split(' ') - return crit.filter(word => str.indexOf(word) >= 0).length === crit.length + const filter = critArray.filter((word) => str.indexOf(word) >= 0) + + return filter.length === critArray.length // true if it found all the words } } diff --git a/dest/simple-jekyll-search.min.js b/dest/simple-jekyll-search.min.js index 6aa6048..9964f7a 100644 --- a/dest/simple-jekyll-search.min.js +++ b/dest/simple-jekyll-search.min.js @@ -1,6 +1,6 @@ /*! - * Simple-Jekyll-Search + * Simple-Jekyll-Search 1.12.0 * Copyright 2015-2024, Christian Fei * Licensed under the MIT License. */ -!function(){"use strict";var o={compile:function(r){return i.template.replace(i.pattern,function(t,e){var n=i.middleware(e,r[e],i.template,r.query);return void 0!==n?n:r[e]||t})},setOptions:function(t){i.pattern=t.pattern||i.pattern,i.template=t.template||i.template,"function"==typeof t.middleware&&(i.middleware=t.middleware)}};const i={};i.pattern=/\{(.*?)\}/g,i.template="",i.middleware=function(){};var n=function(t,e){var n=e.length,r=t.length;if(n0<=e.indexOf(t)).length===t.length}};var u={put:function(t){if(f(t))return d(t);if(function(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function(n){var r=[];l();for(let t=0,e=n.length;t{title}',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,debounceTime:null,exclude:[],onSearch:Function.prototype},n;const T=function(t,e){e?(clearTimeout(n),n=setTimeout(t,e)):t.call()},x=["searchInput","resultsContainer","json"],j=t({required:x});function w(t){u.put(t),i.searchInput.addEventListener("input",function(t){-1===[13,16,20,37,38,39,40,91].indexOf(t.which)&&(g(),T(function(){O(t.target.value)},i.debounceTime))})}function g(){i.resultsContainer.innerHTML=""}function v(t){i.resultsContainer.innerHTML+=t}function O(t){var e;(e=t)&&0e.isWordLike).map(e=>e.segment)).filter(e=>0<=t.indexOf(e)).length===n.length}};const u=new Intl.Segmenter([],{granularity:"word"});var c={put:function(e){if(d(e))return p(e);if(function(e){return Boolean(e)&&"[object Array]"===Object.prototype.toString.call(e)}(e))return function(n){var r=[];f();for(let e=0,t=n.length;e{title}',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,debounceTime:null,exclude:[],onSearch:Function.prototype},n;const x=function(e,t){t?(clearTimeout(n),n=setTimeout(e,t)):e.call()},j=["searchInput","resultsContainer","json"],q=e({required:j});function w(e){c.put(e),i.searchInput.addEventListener("input",function(e){-1===[13,16,20,37,38,39,40,91].indexOf(e.which)&&(v(),x(function(){O(e.target.value)},i.debounceTime))})}function v(){i.resultsContainer.innerHTML=""}function S(e){i.resultsContainer.innerHTML+=e}function O(e){var t;(t=e)&&0 s.isWordLike) + .map((s) => s.segment) } - crit = crit.toLowerCase() - crit = exact ? [crit] : crit.split(' ') - return crit.filter(word => str.indexOf(word) >= 0).length === crit.length + const filter = critArray.filter((word) => str.indexOf(word) >= 0) + + return filter.length === critArray.length // true if it found all the words } } diff --git a/example/js/simple-jekyll-search.min.js b/example/js/simple-jekyll-search.min.js index 6aa6048..9964f7a 100644 --- a/example/js/simple-jekyll-search.min.js +++ b/example/js/simple-jekyll-search.min.js @@ -1,6 +1,6 @@ /*! - * Simple-Jekyll-Search + * Simple-Jekyll-Search 1.12.0 * Copyright 2015-2024, Christian Fei * Licensed under the MIT License. */ -!function(){"use strict";var o={compile:function(r){return i.template.replace(i.pattern,function(t,e){var n=i.middleware(e,r[e],i.template,r.query);return void 0!==n?n:r[e]||t})},setOptions:function(t){i.pattern=t.pattern||i.pattern,i.template=t.template||i.template,"function"==typeof t.middleware&&(i.middleware=t.middleware)}};const i={};i.pattern=/\{(.*?)\}/g,i.template="",i.middleware=function(){};var n=function(t,e){var n=e.length,r=t.length;if(n0<=e.indexOf(t)).length===t.length}};var u={put:function(t){if(f(t))return d(t);if(function(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function(n){var r=[];l();for(let t=0,e=n.length;t{title}',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,debounceTime:null,exclude:[],onSearch:Function.prototype},n;const T=function(t,e){e?(clearTimeout(n),n=setTimeout(t,e)):t.call()},x=["searchInput","resultsContainer","json"],j=t({required:x});function w(t){u.put(t),i.searchInput.addEventListener("input",function(t){-1===[13,16,20,37,38,39,40,91].indexOf(t.which)&&(g(),T(function(){O(t.target.value)},i.debounceTime))})}function g(){i.resultsContainer.innerHTML=""}function v(t){i.resultsContainer.innerHTML+=t}function O(t){var e;(e=t)&&0e.isWordLike).map(e=>e.segment)).filter(e=>0<=t.indexOf(e)).length===n.length}};const u=new Intl.Segmenter([],{granularity:"word"});var c={put:function(e){if(d(e))return p(e);if(function(e){return Boolean(e)&&"[object Array]"===Object.prototype.toString.call(e)}(e))return function(n){var r=[];f();for(let e=0,t=n.length;e{title}',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,debounceTime:null,exclude:[],onSearch:Function.prototype},n;const x=function(e,t){t?(clearTimeout(n),n=setTimeout(e,t)):e.call()},j=["searchInput","resultsContainer","json"],q=e({required:j});function w(e){c.put(e),i.searchInput.addEventListener("input",function(e){-1===[13,16,20,37,38,39,40,91].indexOf(e.which)&&(v(),x(function(){O(e.target.value)},i.debounceTime))})}function v(){i.resultsContainer.innerHTML=""}function S(e){i.resultsContainer.innerHTML+=e}function O(e){var t;(t=e)&&0 dest/simple-jekyll-search.js", + "browserify": "browserify -p browser-pack-flat/plugin src/index.js | node scripts/stamp.js ${npm_package_version} > dest/simple-jekyll-search.js", "build": "npm run browserify && npm run uglify", "dist": "npm run build && npm run copy-example-code", "test": "ava", diff --git a/scripts/stamp.js b/scripts/stamp.js index 9a3bd75..88f23db 100644 --- a/scripts/stamp.js +++ b/scripts/stamp.js @@ -4,11 +4,13 @@ 'use strict' +const version = process.argv[2] || '' + const year = new Date().getFullYear() const stampTop = `/*! - * Simple-Jekyll-Search + * Simple-Jekyll-Search ${version} * Copyright 2015-${year}, Christian Fei * Licensed under the MIT License. */ diff --git a/src/SearchStrategies/LiteralSearchStrategy.js b/src/SearchStrategies/LiteralSearchStrategy.js index 0bcb4b9..3bf03ba 100644 --- a/src/SearchStrategies/LiteralSearchStrategy.js +++ b/src/SearchStrategies/LiteralSearchStrategy.js @@ -2,22 +2,28 @@ module.exports = new LiteralSearchStrategy() +const segmenter = new Intl.Segmenter([], { granularity: 'word' }) + function LiteralSearchStrategy () { this.matches = function (str, crit) { - if (!str) return false + if (!str) { + return false + } str = str.trim().toLowerCase() + crit = crit.trim().toLowerCase() - let exact = false - if (crit.endsWith(' ')) { - exact = true - } + let critArray = [] if (crit.startsWith('"') && crit.endsWith('"')) { - exact = true - crit = crit.substring(1, crit.length - 1) + critArray = [crit.substring(1, crit.length - 1)] + } else { + const segmentedText = segmenter.segment(crit) + critArray = [...segmentedText] + .filter((s) => s.isWordLike) + .map((s) => s.segment) } - crit = crit.toLowerCase() - crit = exact ? [crit] : crit.split(' ') - return crit.filter(word => str.indexOf(word) >= 0).length === crit.length + const filter = critArray.filter((word) => str.indexOf(word) >= 0) + + return filter.length === critArray.length // true if it found all the words } } diff --git a/tests/SearchStrategies/LiteralSearchStrategy.test.js b/tests/SearchStrategies/LiteralSearchStrategy.test.js index 742827d..0dbb0c4 100644 --- a/tests/SearchStrategies/LiteralSearchStrategy.test.js +++ b/tests/SearchStrategies/LiteralSearchStrategy.test.js @@ -14,10 +14,6 @@ test('matches a word that is contained in the search criteria (multiple words)', t.deepEqual(LiteralSearchStrategy.matches('hello world test search text', 'hello text world'), true) }) -test('matches exact words when exacts words with space in the search criteria', t => { - t.deepEqual(LiteralSearchStrategy.matches('hello world test search text', 'hello world '), true) -}) - test('matches exact words when exacts words with quotes in the search criteria', t => { t.deepEqual(LiteralSearchStrategy.matches('hello world test search text', '"hello world"'), true) }) @@ -26,14 +22,14 @@ test('matches exact last words when exacts words with quotes in the search crite t.deepEqual(LiteralSearchStrategy.matches('hello world test search text', '"search text"'), true) }) -test('does not matches multiple words if not exact words with space in the search criteria', t => { - t.deepEqual(LiteralSearchStrategy.matches('hello world test search text', 'hello text world '), false) -}) - test('matches a word that is partially contained in the search criteria', t => { t.deepEqual(LiteralSearchStrategy.matches('this tasty tester text', 'test'), true) }) -test('does not matches a word that is partially contained in the search criteria when followed by a space', t => { - t.deepEqual(LiteralSearchStrategy.matches('this tasty tester text', 'test '), false) +test('matches when search criteria has puncuation', t => { + t.deepEqual(LiteralSearchStrategy.matches('hello world test search text', 'hello, world!'), true) +}) + +test('does not match when only only one word of search criteria is in the text', t => { + t.deepEqual(LiteralSearchStrategy.matches('hello world test search text', 'hello muppet'), false) })