From 99611933571bf88eb21590789f836c8045c2fac3 Mon Sep 17 00:00:00 2001 From: David VanScott Date: Sat, 6 Nov 2021 07:46:09 -0400 Subject: [PATCH] Add ability to define search synonyms (#51) --- CHANGELOG.md | 5 ++- README.md | 78 +++++++++++++++++++++++++++++++-------- public/spotlight.js | 2 +- resources/js/spotlight.js | 2 +- src/Spotlight.php | 1 + src/SpotlightCommand.php | 7 ++++ src/stubs/spotlight.stub | 6 +++ 7 files changed, 83 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ceb0be..1f6ffe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,15 @@ All notable changes to `spotlight` will be documented in this file. +## 1.1.0 +- Add ability to define synonyms when searching for commands + ## 1.0.0 - Alpine v3 support - Make prompt placeholder translatable ## 0.1.8 -- Fuse.js is now included in the Javascript bundle. +- Fuse.js is now included in the Javascript bundle. - You can disable the Javascript in the config file and require the Javascript in your bundler `require('vendor/livewire-ui/spotlight/resources/js/spotlight');` ## 0.1.7 diff --git a/README.md b/README.md index 82017be..5566514 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,9 @@ application. Toggle Spotlight ``` @@ -80,10 +81,6 @@ name and description will be visible when searching through commands. To help you get started you can use the `php artisan make:spotlight ` command. ```php -redirect('/settings/billing'); + } +} +``` + +When searching, users can now enter "credit card" and they'll be shown a search result for the View Billing Settings command. + ## How to define command dependencies In some cases your command might require dependencies. Let's say we want to create a new user and add it to a specific @@ -198,12 +220,10 @@ class CreateUser extends SpotlightCommand }); } - public function execute(Spotlight $spotlight, Team $team, string $name) { $spotlight->emit('openModal', 'user-create', ['team' => $team->id, 'name' => $name]); } - } ``` @@ -265,9 +285,36 @@ class CreateUser extends SpotlightCommand } ``` +If you need to do logic that can't be done in a service provider (for example, any logic that needs to use the currently authenticated user) to determine if your command should be shown in the Spotlight component, you can add a `shouldBeShown` method on your command. You can type-hint any dependencies you need and they'll be resolved out of the container for you. (Note: you will still need to register your command in your config file or in a service provider.) + +```php +use Illuminate\Http\Request; +use LivewireUI\Spotlight\Spotlight; +use LivewireUI\Spotlight\SpotlightCommand; + +class CreateUser extends SpotlightCommand +{ + protected string $name = 'Create user'; + + protected string $description = 'Create new team user'; + + public function execute(Spotlight $spotlight) + { + $spotlight->emit('openModal', 'user-create'); + } + + public function shouldBeShown(Request $request): bool + { + return $request->user()->can('create user'); + } +} +``` + ## Configuration -You can customize Spotlight via the `livewire-ui-spotlight.php` config file. This includes some additional options like including CSS if you don't use TailwindCSS for your application. To publish the config run the vendor:publish command: + +You can customize Spotlight via the `livewire-ui-spotlight.php` config file. This includes some additional options like including CSS if you don't use TailwindCSS for your application. To publish the config run the `vendor:publish` command: + ```shell php artisan vendor:publish --tag=livewire-ui-spotlight-config ``` @@ -317,7 +364,7 @@ return [ | */ 'include_css' => false, - + /* |-------------------------------------------------------------------------- | Include JS @@ -357,6 +404,7 @@ return [ Wire Elements is open-sourced software licensed under the [MIT license](LICENSE.md). ## Manage your Laravel Horizon Instances With Observer + All your favorite Laravel Horizon features (and a few new ones) are packed into a single desktop application. A must-have productivity booster for every Laravel developer. Click here to get Observer diff --git a/public/spotlight.js b/public/spotlight.js index 5be0ae2..19c7ca2 100644 --- a/public/spotlight.js +++ b/public/spotlight.js @@ -1 +1 @@ -(()=>{"use strict";var e,t={48:()=>{function e(e){return Array.isArray?Array.isArray(e):"[object Array]"===o(e)}function t(e){return"string"==typeof e}function n(e){return"number"==typeof e}function s(e){return!0===e||!1===e||function(e){return i(e)&&null!==e}(e)&&"[object Boolean]"==o(e)}function i(e){return"object"==typeof e}function r(e){return null!=e}function c(e){return!e.trim().length}function o(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const h=Object.prototype.hasOwnProperty;class a{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let n=l(e);t+=n.weight,this._keys.push(n),this._keyMap[n.id]=n,t+=n.weight})),this._keys.forEach((e=>{e.weight/=t}))}get(e){return this._keyMap[e]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}}function l(n){let s=null,i=null,r=null,c=1;if(t(n)||e(n))r=n,s=u(n),i=d(n);else{if(!h.call(n,"name"))throw new Error((e=>`Missing ${e} property in key`)("name"));const e=n.name;if(r=e,h.call(n,"weight")&&(c=n.weight,c<=0))throw new Error((e=>`Property 'weight' in key '${e}' must be a positive integer`)(e));s=u(e),i=d(e)}return{path:s,id:i,weight:c,src:r}}function u(t){return e(t)?t:t.split(".")}function d(t){return e(t)?t.join("."):t}var p={isCaseSensitive:!1,includeScore:!1,keys:[],shouldSort:!0,sortFn:(e,t)=>e.score===t.score?e.idx{if(r(i))if(c[l]){const u=i[c[l]];if(!r(u))return;if(l===c.length-1&&(t(u)||n(u)||s(u)))o.push(function(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-1/0?"-0":t}(e)}(u));else if(e(u)){h=!0;for(let e=0,t=u.length;e{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,t(this.docs[0])?this.docs.forEach(((e,t)=>{this._addString(e,t)})):this.docs.forEach(((e,t)=>{this._addObject(e,t)})),this.norm.clear())}add(e){const n=this.size();t(e)?this._addString(e,n):this._addObject(e,n)}removeAt(e){this.records.splice(e,1);for(let t=e,n=this.size();t{let h=this.getFn(n,s.path);if(r(h))if(e(h)){let n=[];const s=[{nestedArrIndex:-1,value:h}];for(;s.length;){const{nestedArrIndex:i,value:o}=s.pop();if(r(o))if(t(o)&&!c(o)){let e={v:o,i,n:this.norm.get(o)};n.push(e)}else e(o)&&o.forEach(((e,t)=>{s.push({nestedArrIndex:t,value:e})}))}i.$[o]=n}else if(!c(h)){let e={v:h,n:this.norm.get(h)};i.$[o]=e}})),this.records.push(i)}toJSON(){return{keys:this.keys,records:this.records}}}function m(e,t,{getFn:n=p.getFn}={}){const s=new f({getFn:n});return s.setKeys(e.map(l)),s.setSources(t),s.create(),s}function y(e,{errors:t=0,currentLocation:n=0,expectedLocation:s=0,distance:i=p.distance,ignoreLocation:r=p.ignoreLocation}={}){const c=t/e.length;if(r)return c;const o=Math.abs(s-n);return i?c+o/i:o?1:c}const M=32;function x(e,t,n,{location:s=p.location,distance:i=p.distance,threshold:r=p.threshold,findAllMatches:c=p.findAllMatches,minMatchCharLength:o=p.minMatchCharLength,includeMatches:h=p.includeMatches,ignoreLocation:a=p.ignoreLocation}={}){if(t.length>M)throw new Error(`Pattern length exceeds max of ${M}.`);const l=t.length,u=e.length,d=Math.max(0,Math.min(s,u));let g=r,f=d;const m=o>1||h,x=m?Array(u):[];let v;for(;(v=e.indexOf(t,f))>-1;){let e=y(t,{currentLocation:v,expectedLocation:d,distance:i,ignoreLocation:a});if(g=Math.min(e,g),f=v+l,m){let e=0;for(;e=h;r-=1){let c=r-1,o=n[e.charAt(c)];if(m&&(x[c]=+!!o),M[r]=(M[r+1]<<1|1)&o,s&&(M[r]|=(k[r+1]|k[r])<<1|1|k[r+1]),M[r]&S&&(L=y(t,{errors:s,currentLocation:c,expectedLocation:d,distance:i,ignoreLocation:a}),L<=g)){if(g=L,f=c,f<=d)break;h=Math.max(1,2*d-f)}}if(y(t,{errors:s+1,currentLocation:d,expectedLocation:d,distance:i,ignoreLocation:a})>g)break;k=M}const C={isMatch:f>=0,score:Math.max(.001,L)};if(m){const e=function(e=[],t=p.minMatchCharLength){let n=[],s=-1,i=-1,r=0;for(let c=e.length;r=t&&n.push([s,i]),s=-1)}return e[r-1]&&r-s>=t&&n.push([s,r-1]),n}(x,o);e.length?h&&(C.indices=e):C.isMatch=!1}return C}function v(e){let t={};for(let n=0,s=e.length;n{this.chunks.push({pattern:e,alphabet:v(e),startIndex:t})},l=this.pattern.length;if(l>M){let e=0;const t=l%M,n=l-t;for(;e{const{isMatch:g,score:f,indices:m}=x(e,t,d,{location:s+p,distance:i,threshold:r,findAllMatches:c,minMatchCharLength:o,includeMatches:n,ignoreLocation:h});g&&(u=!0),l+=f,g&&m&&(a=[...a,...m])}));let d={isMatch:u,score:u?l/this.chunks.length:1};return u&&n&&(d.indices=a),d}}class L{constructor(e){this.pattern=e}static isMultiMatch(e){return w(e,this.multiRegex)}static isSingleMatch(e){return w(e,this.singleRegex)}search(){}}function w(e,t){const n=e.match(t);return n?n[1]:null}class S extends L{constructor(e,{location:t=p.location,threshold:n=p.threshold,distance:s=p.distance,includeMatches:i=p.includeMatches,findAllMatches:r=p.findAllMatches,minMatchCharLength:c=p.minMatchCharLength,isCaseSensitive:o=p.isCaseSensitive,ignoreLocation:h=p.ignoreLocation}={}){super(e),this._bitapSearch=new k(e,{location:t,threshold:n,distance:s,includeMatches:i,findAllMatches:r,minMatchCharLength:c,isCaseSensitive:o,ignoreLocation:h})}static get type(){return"fuzzy"}static get multiRegex(){return/^"(.*)"$/}static get singleRegex(){return/^(.*)$/}search(e){return this._bitapSearch.searchIn(e)}}class C extends L{constructor(e){super(e)}static get type(){return"include"}static get multiRegex(){return/^'"(.*)"$/}static get singleRegex(){return/^'(.*)$/}search(e){let t,n=0;const s=[],i=this.pattern.length;for(;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,s.push([t,n-1]);const r=!!s.length;return{isMatch:r,score:r?0:1,indices:s}}}const _=[class extends L{constructor(e){super(e)}static get type(){return"exact"}static get multiRegex(){return/^="(.*)"$/}static get singleRegex(){return/^=(.*)$/}search(e){const t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},C,class extends L{constructor(e){super(e)}static get type(){return"prefix-exact"}static get multiRegex(){return/^\^"(.*)"$/}static get singleRegex(){return/^\^(.*)$/}search(e){const t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"inverse-prefix-exact"}static get multiRegex(){return/^!\^"(.*)"$/}static get singleRegex(){return/^!\^(.*)$/}search(e){const t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"inverse-suffix-exact"}static get multiRegex(){return/^!"(.*)"\$$/}static get singleRegex(){return/^!(.*)\$$/}search(e){const t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"suffix-exact"}static get multiRegex(){return/^"(.*)"\$$/}static get singleRegex(){return/^(.*)\$$/}search(e){const t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"inverse-exact"}static get multiRegex(){return/^!"(.*)"$/}static get singleRegex(){return/^!(.*)$/}search(e){const t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},S],I=_.length,$=/ +(?=([^\"]*\"[^\"]*\")*[^\"]*$)/;const b=new Set([S.type,C.type]);class O{constructor(e,{isCaseSensitive:t=p.isCaseSensitive,includeMatches:n=p.includeMatches,minMatchCharLength:s=p.minMatchCharLength,ignoreLocation:i=p.ignoreLocation,findAllMatches:r=p.findAllMatches,location:c=p.location,threshold:o=p.threshold,distance:h=p.distance}={}){this.query=null,this.options={isCaseSensitive:t,includeMatches:n,minMatchCharLength:s,findAllMatches:r,ignoreLocation:i,location:c,threshold:o,distance:h},this.pattern=t?e:e.toLowerCase(),this.query=function(e,t={}){return e.split("|").map((e=>{let n=e.trim().split($).filter((e=>e&&!!e.trim())),s=[];for(let e=0,i=n.length;e!(!e[R]&&!e[D]),q=e=>({[R]:Object.keys(e).map((t=>({[t]:e[t]})))});function P(n,s,{auto:r=!0}={}){const c=n=>{let o=Object.keys(n);const h=(e=>!!e[F])(n);if(!h&&o.length>1&&!N(n))return c(q(n));if((t=>!e(t)&&i(t)&&!N(t))(n)){const e=h?n[F]:o[0],i=h?n[j]:n[e];if(!t(i))throw new Error((e=>`Invalid value for key ${e}`)(e));const c={keyId:d(e),pattern:i};return r&&(c.searcher=A(i,s)),c}let a={children:[],operator:o[0]};return o.forEach((t=>{const s=n[t];e(s)&&s.forEach((e=>{a.children.push(c(e))}))})),a};return N(n)||(n=q(n)),c(n)}function z(e,t){const n=e.matches;t.matches=[],r(n)&&n.forEach((e=>{if(!r(e.indices)||!e.indices.length)return;const{indices:n,value:s}=e;let i={indices:n,value:s};e.key&&(i.key=e.key.src),e.idx>-1&&(i.refIndex=e.idx),t.matches.push(i)}))}function J(e,t){t.score=e.score}class K{constructor(e,t={},n){this.options={...p,...t},this.options.useExtendedSearch,this._keyStore=new a(this.options.keys),this.setCollection(e,n)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof f))throw new Error("Incorrect 'index' type");this._myIndex=t||m(this.options.keys,this._docs,{getFn:this.options.getFn})}add(e){r(e)&&(this._docs.push(e),this._myIndex.add(e))}remove(e=(()=>!1)){const t=[];for(let n=0,s=this._docs.length;n{let n=1;e.matches.forEach((({key:e,norm:s,score:i})=>{const r=e?e.weight:null;n*=Math.pow(0===i&&r?Number.EPSILON:i,(r||1)*(t?1:s))})),e.score=n}))}(a,{ignoreFieldNorm:h}),c&&a.sort(o),n(s)&&s>-1&&(a=a.slice(0,s)),function(e,t,{includeMatches:n=p.includeMatches,includeScore:s=p.includeScore}={}){const i=[];return n&&i.push(z),s&&i.push(J),e.map((e=>{const{idx:n}=e,s={item:t[n],refIndex:n};return i.length&&i.forEach((t=>{t(e,s)})),s}))}(a,this._docs,{includeMatches:i,includeScore:r})}_searchStringList(e){const t=A(e,this.options),{records:n}=this._myIndex,s=[];return n.forEach((({v:e,i:n,n:i})=>{if(!r(e))return;const{isMatch:c,score:o,indices:h}=t.searchIn(e);c&&s.push({item:e,idx:n,matches:[{score:o,value:e,norm:i,indices:h}]})})),s}_searchLogical(e){const t=P(e,this.options),n=(e,t,s)=>{if(!e.children){const{keyId:n,searcher:i}=e,r=this._findMatches({key:this._keyStore.get(n),value:this._myIndex.getValueForItemAtKeyId(t,n),searcher:i});return r&&r.length?[{idx:s,item:t,matches:r}]:[]}switch(e.operator){case R:{const i=[];for(let r=0,c=e.children.length;r{if(r(e)){let r=n(t,e,s);r.length&&(i[s]||(i[s]={idx:s,item:e,matches:[]},c.push(i[s])),r.forEach((({matches:e})=>{i[s].matches.push(...e)})))}})),c}_searchObjectList(e){const t=A(e,this.options),{keys:n,records:s}=this._myIndex,i=[];return s.forEach((({$:e,i:s})=>{if(!r(e))return;let c=[];n.forEach(((n,s)=>{c.push(...this._findMatches({key:n,value:e[s],searcher:t}))})),c.length&&i.push({idx:s,item:e,matches:c})})),i}_findMatches({key:t,value:n,searcher:s}){if(!r(n))return[];let i=[];if(e(n))n.forEach((({v:e,i:n,n:c})=>{if(!r(e))return;const{isMatch:o,score:h,indices:a}=s.searchIn(e);o&&i.push({score:h,key:t,value:e,idx:n,norm:c,indices:a})}));else{const{v:e,n:r}=n,{isMatch:c,score:o,indices:h}=s.searchIn(e);c&&i.push({score:o,key:t,value:e,norm:r,indices:h})}return i}}K.version="6.4.6",K.createIndex=m,K.parseIndex=function(e,{getFn:t=p.getFn}={}){const{keys:n,records:s}=e,i=new f({getFn:t});return i.setKeys(n),i.setIndexRecords(s),i},K.config=p,K.parseQuery=P,function(...e){E.push(...e)}(O);const Q=K;window.LivewireUISpotlight=function(e){return{inputPlaceholder:e.placeholder,searchEngine:"commands",commands:e.commands,commandSearch:null,selectedCommand:null,dependencySearch:null,dependencyQueryResults:window.Livewire.find(e.componentId).entangle("dependencyQueryResults"),requiredDependencies:[],currentDependency:null,resolvedDependencies:{},init:function(){var t=this;this.commandSearch=new Q(this.commands,{threshold:.3,keys:["name","description"]}),this.dependencySearch=new Q([],{threshold:.3,keys:["name","description"]}),this.$watch("dependencyQueryResults",(function(e){t.dependencySearch.setCollection(e)})),this.$watch("input",(function(e){0===e.length&&(t.selected=0),null!==t.selectedCommand&&null!==t.currentDependency&&"search"===t.currentDependency.type&&t.$wire.searchDependency(t.selectedCommand.id,t.currentDependency.id,e,t.resolvedDependencies)})),this.$watch("isOpen",(function(n){!1===n&&setTimeout((function(){t.input="",t.inputPlaceholder=e.placeholder,t.searchEngine="commands",t.resolvedDependencies={},t.selectedCommand=null,t.currentDependency=null,t.selectedCommand=null,t.requiredDependencies=[]}),300)}))},isOpen:!1,toggleOpen:function(){var e=this;this.isOpen?this.isOpen=!1:(this.input="",this.isOpen=!0,setTimeout((function(){e.$refs.input.focus()}),100))},input:"",filteredItems:function(){return"commands"===this.searchEngine?this.commandSearch.search(this.input).map((function(e,t){return[e,t]})):"search"===this.searchEngine?this.dependencySearch.search(this.input).map((function(e,t){return[e,t]})):[]},selectUp:function(){var e=this;this.selected=Math.max(0,this.selected-1),this.$nextTick((function(){e.$refs.results.children[e.selected+1].scrollIntoView({block:"nearest"})}))},selectDown:function(){var e=this;this.selected=Math.min(this.filteredItems().length-1,this.selected+1),this.$nextTick((function(){e.$refs.results.children[e.selected+1].scrollIntoView({block:"nearest"})}))},go:function(e){var t,n=this;(null===this.selectedCommand&&(this.selectedCommand=this.commands.find((function(t){return t.id===(e||n.filteredItems()[n.selected][0].item.id)})),this.requiredDependencies=JSON.parse(JSON.stringify(this.selectedCommand.dependencies))),null!==this.currentDependency)&&(t="search"===this.currentDependency.type?e||this.filteredItems()[this.selected][0].item.id:this.input,this.resolvedDependencies[this.currentDependency.id]=t);this.requiredDependencies.length>0?(this.input="",this.currentDependency=this.requiredDependencies.pop(),this.inputPlaceholder=this.currentDependency.placeholder,this.searchEngine="search"===this.currentDependency.type&&"search"):(this.isOpen=!1,this.$wire.execute(this.selectedCommand.id,this.resolvedDependencies))},selected:0}}},578:()=>{}},n={};function s(e){var i=n[e];if(void 0!==i)return i.exports;var r=n[e]={exports:{}};return t[e](r,r.exports,s),r.exports}s.m=t,e=[],s.O=(t,n,i,r)=>{if(!n){var c=1/0;for(a=0;a=r)&&Object.keys(s.O).every((e=>s.O[e](n[h])))?n.splice(h--,1):(o=!1,r0&&e[a-1][2]>r;a--)e[a]=e[a-1];e[a]=[n,i,r]},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={569:0,880:0};s.O.j=t=>0===e[t];var t=(t,n)=>{var i,r,[c,o,h]=n,a=0;for(i in o)s.o(o,i)&&(s.m[i]=o[i]);if(h)var l=h(s);for(t&&t(n);as(48)));var i=s.O(void 0,[880],(()=>s(578)));i=s.O(i)})(); \ No newline at end of file +(()=>{"use strict";var e,t={48:()=>{function e(e){return Array.isArray?Array.isArray(e):"[object Array]"===o(e)}function t(e){return"string"==typeof e}function n(e){return"number"==typeof e}function s(e){return!0===e||!1===e||function(e){return i(e)&&null!==e}(e)&&"[object Boolean]"==o(e)}function i(e){return"object"==typeof e}function r(e){return null!=e}function c(e){return!e.trim().length}function o(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const h=Object.prototype.hasOwnProperty;class a{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let n=l(e);t+=n.weight,this._keys.push(n),this._keyMap[n.id]=n,t+=n.weight})),this._keys.forEach((e=>{e.weight/=t}))}get(e){return this._keyMap[e]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}}function l(n){let s=null,i=null,r=null,c=1;if(t(n)||e(n))r=n,s=u(n),i=d(n);else{if(!h.call(n,"name"))throw new Error((e=>`Missing ${e} property in key`)("name"));const e=n.name;if(r=e,h.call(n,"weight")&&(c=n.weight,c<=0))throw new Error((e=>`Property 'weight' in key '${e}' must be a positive integer`)(e));s=u(e),i=d(e)}return{path:s,id:i,weight:c,src:r}}function u(t){return e(t)?t:t.split(".")}function d(t){return e(t)?t.join("."):t}var p={isCaseSensitive:!1,includeScore:!1,keys:[],shouldSort:!0,sortFn:(e,t)=>e.score===t.score?e.idx{if(r(i))if(c[l]){const u=i[c[l]];if(!r(u))return;if(l===c.length-1&&(t(u)||n(u)||s(u)))o.push(function(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-1/0?"-0":t}(e)}(u));else if(e(u)){h=!0;for(let e=0,t=u.length;e{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,t(this.docs[0])?this.docs.forEach(((e,t)=>{this._addString(e,t)})):this.docs.forEach(((e,t)=>{this._addObject(e,t)})),this.norm.clear())}add(e){const n=this.size();t(e)?this._addString(e,n):this._addObject(e,n)}removeAt(e){this.records.splice(e,1);for(let t=e,n=this.size();t{let h=this.getFn(n,s.path);if(r(h))if(e(h)){let n=[];const s=[{nestedArrIndex:-1,value:h}];for(;s.length;){const{nestedArrIndex:i,value:o}=s.pop();if(r(o))if(t(o)&&!c(o)){let e={v:o,i,n:this.norm.get(o)};n.push(e)}else e(o)&&o.forEach(((e,t)=>{s.push({nestedArrIndex:t,value:e})}))}i.$[o]=n}else if(!c(h)){let e={v:h,n:this.norm.get(h)};i.$[o]=e}})),this.records.push(i)}toJSON(){return{keys:this.keys,records:this.records}}}function m(e,t,{getFn:n=p.getFn}={}){const s=new f({getFn:n});return s.setKeys(e.map(l)),s.setSources(t),s.create(),s}function y(e,{errors:t=0,currentLocation:n=0,expectedLocation:s=0,distance:i=p.distance,ignoreLocation:r=p.ignoreLocation}={}){const c=t/e.length;if(r)return c;const o=Math.abs(s-n);return i?c+o/i:o?1:c}const M=32;function x(e,t,n,{location:s=p.location,distance:i=p.distance,threshold:r=p.threshold,findAllMatches:c=p.findAllMatches,minMatchCharLength:o=p.minMatchCharLength,includeMatches:h=p.includeMatches,ignoreLocation:a=p.ignoreLocation}={}){if(t.length>M)throw new Error(`Pattern length exceeds max of ${M}.`);const l=t.length,u=e.length,d=Math.max(0,Math.min(s,u));let g=r,f=d;const m=o>1||h,x=m?Array(u):[];let v;for(;(v=e.indexOf(t,f))>-1;){let e=y(t,{currentLocation:v,expectedLocation:d,distance:i,ignoreLocation:a});if(g=Math.min(e,g),f=v+l,m){let e=0;for(;e=h;r-=1){let c=r-1,o=n[e.charAt(c)];if(m&&(x[c]=+!!o),M[r]=(M[r+1]<<1|1)&o,s&&(M[r]|=(k[r+1]|k[r])<<1|1|k[r+1]),M[r]&S&&(L=y(t,{errors:s,currentLocation:c,expectedLocation:d,distance:i,ignoreLocation:a}),L<=g)){if(g=L,f=c,f<=d)break;h=Math.max(1,2*d-f)}}if(y(t,{errors:s+1,currentLocation:d,expectedLocation:d,distance:i,ignoreLocation:a})>g)break;k=M}const C={isMatch:f>=0,score:Math.max(.001,L)};if(m){const e=function(e=[],t=p.minMatchCharLength){let n=[],s=-1,i=-1,r=0;for(let c=e.length;r=t&&n.push([s,i]),s=-1)}return e[r-1]&&r-s>=t&&n.push([s,r-1]),n}(x,o);e.length?h&&(C.indices=e):C.isMatch=!1}return C}function v(e){let t={};for(let n=0,s=e.length;n{this.chunks.push({pattern:e,alphabet:v(e),startIndex:t})},l=this.pattern.length;if(l>M){let e=0;const t=l%M,n=l-t;for(;e{const{isMatch:g,score:f,indices:m}=x(e,t,d,{location:s+p,distance:i,threshold:r,findAllMatches:c,minMatchCharLength:o,includeMatches:n,ignoreLocation:h});g&&(u=!0),l+=f,g&&m&&(a=[...a,...m])}));let d={isMatch:u,score:u?l/this.chunks.length:1};return u&&n&&(d.indices=a),d}}class L{constructor(e){this.pattern=e}static isMultiMatch(e){return w(e,this.multiRegex)}static isSingleMatch(e){return w(e,this.singleRegex)}search(){}}function w(e,t){const n=e.match(t);return n?n[1]:null}class S extends L{constructor(e,{location:t=p.location,threshold:n=p.threshold,distance:s=p.distance,includeMatches:i=p.includeMatches,findAllMatches:r=p.findAllMatches,minMatchCharLength:c=p.minMatchCharLength,isCaseSensitive:o=p.isCaseSensitive,ignoreLocation:h=p.ignoreLocation}={}){super(e),this._bitapSearch=new k(e,{location:t,threshold:n,distance:s,includeMatches:i,findAllMatches:r,minMatchCharLength:c,isCaseSensitive:o,ignoreLocation:h})}static get type(){return"fuzzy"}static get multiRegex(){return/^"(.*)"$/}static get singleRegex(){return/^(.*)$/}search(e){return this._bitapSearch.searchIn(e)}}class C extends L{constructor(e){super(e)}static get type(){return"include"}static get multiRegex(){return/^'"(.*)"$/}static get singleRegex(){return/^'(.*)$/}search(e){let t,n=0;const s=[],i=this.pattern.length;for(;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,s.push([t,n-1]);const r=!!s.length;return{isMatch:r,score:r?0:1,indices:s}}}const _=[class extends L{constructor(e){super(e)}static get type(){return"exact"}static get multiRegex(){return/^="(.*)"$/}static get singleRegex(){return/^=(.*)$/}search(e){const t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},C,class extends L{constructor(e){super(e)}static get type(){return"prefix-exact"}static get multiRegex(){return/^\^"(.*)"$/}static get singleRegex(){return/^\^(.*)$/}search(e){const t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"inverse-prefix-exact"}static get multiRegex(){return/^!\^"(.*)"$/}static get singleRegex(){return/^!\^(.*)$/}search(e){const t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"inverse-suffix-exact"}static get multiRegex(){return/^!"(.*)"\$$/}static get singleRegex(){return/^!(.*)\$$/}search(e){const t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"suffix-exact"}static get multiRegex(){return/^"(.*)"\$$/}static get singleRegex(){return/^(.*)\$$/}search(e){const t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}},class extends L{constructor(e){super(e)}static get type(){return"inverse-exact"}static get multiRegex(){return/^!"(.*)"$/}static get singleRegex(){return/^!(.*)$/}search(e){const t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},S],I=_.length,$=/ +(?=([^\"]*\"[^\"]*\")*[^\"]*$)/;const b=new Set([S.type,C.type]);class O{constructor(e,{isCaseSensitive:t=p.isCaseSensitive,includeMatches:n=p.includeMatches,minMatchCharLength:s=p.minMatchCharLength,ignoreLocation:i=p.ignoreLocation,findAllMatches:r=p.findAllMatches,location:c=p.location,threshold:o=p.threshold,distance:h=p.distance}={}){this.query=null,this.options={isCaseSensitive:t,includeMatches:n,minMatchCharLength:s,findAllMatches:r,ignoreLocation:i,location:c,threshold:o,distance:h},this.pattern=t?e:e.toLowerCase(),this.query=function(e,t={}){return e.split("|").map((e=>{let n=e.trim().split($).filter((e=>e&&!!e.trim())),s=[];for(let e=0,i=n.length;e!(!e[R]&&!e[D]),q=e=>({[R]:Object.keys(e).map((t=>({[t]:e[t]})))});function P(n,s,{auto:r=!0}={}){const c=n=>{let o=Object.keys(n);const h=(e=>!!e[F])(n);if(!h&&o.length>1&&!N(n))return c(q(n));if((t=>!e(t)&&i(t)&&!N(t))(n)){const e=h?n[F]:o[0],i=h?n[j]:n[e];if(!t(i))throw new Error((e=>`Invalid value for key ${e}`)(e));const c={keyId:d(e),pattern:i};return r&&(c.searcher=A(i,s)),c}let a={children:[],operator:o[0]};return o.forEach((t=>{const s=n[t];e(s)&&s.forEach((e=>{a.children.push(c(e))}))})),a};return N(n)||(n=q(n)),c(n)}function z(e,t){const n=e.matches;t.matches=[],r(n)&&n.forEach((e=>{if(!r(e.indices)||!e.indices.length)return;const{indices:n,value:s}=e;let i={indices:n,value:s};e.key&&(i.key=e.key.src),e.idx>-1&&(i.refIndex=e.idx),t.matches.push(i)}))}function J(e,t){t.score=e.score}class K{constructor(e,t={},n){this.options={...p,...t},this.options.useExtendedSearch,this._keyStore=new a(this.options.keys),this.setCollection(e,n)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof f))throw new Error("Incorrect 'index' type");this._myIndex=t||m(this.options.keys,this._docs,{getFn:this.options.getFn})}add(e){r(e)&&(this._docs.push(e),this._myIndex.add(e))}remove(e=(()=>!1)){const t=[];for(let n=0,s=this._docs.length;n{let n=1;e.matches.forEach((({key:e,norm:s,score:i})=>{const r=e?e.weight:null;n*=Math.pow(0===i&&r?Number.EPSILON:i,(r||1)*(t?1:s))})),e.score=n}))}(a,{ignoreFieldNorm:h}),c&&a.sort(o),n(s)&&s>-1&&(a=a.slice(0,s)),function(e,t,{includeMatches:n=p.includeMatches,includeScore:s=p.includeScore}={}){const i=[];return n&&i.push(z),s&&i.push(J),e.map((e=>{const{idx:n}=e,s={item:t[n],refIndex:n};return i.length&&i.forEach((t=>{t(e,s)})),s}))}(a,this._docs,{includeMatches:i,includeScore:r})}_searchStringList(e){const t=A(e,this.options),{records:n}=this._myIndex,s=[];return n.forEach((({v:e,i:n,n:i})=>{if(!r(e))return;const{isMatch:c,score:o,indices:h}=t.searchIn(e);c&&s.push({item:e,idx:n,matches:[{score:o,value:e,norm:i,indices:h}]})})),s}_searchLogical(e){const t=P(e,this.options),n=(e,t,s)=>{if(!e.children){const{keyId:n,searcher:i}=e,r=this._findMatches({key:this._keyStore.get(n),value:this._myIndex.getValueForItemAtKeyId(t,n),searcher:i});return r&&r.length?[{idx:s,item:t,matches:r}]:[]}switch(e.operator){case R:{const i=[];for(let r=0,c=e.children.length;r{if(r(e)){let r=n(t,e,s);r.length&&(i[s]||(i[s]={idx:s,item:e,matches:[]},c.push(i[s])),r.forEach((({matches:e})=>{i[s].matches.push(...e)})))}})),c}_searchObjectList(e){const t=A(e,this.options),{keys:n,records:s}=this._myIndex,i=[];return s.forEach((({$:e,i:s})=>{if(!r(e))return;let c=[];n.forEach(((n,s)=>{c.push(...this._findMatches({key:n,value:e[s],searcher:t}))})),c.length&&i.push({idx:s,item:e,matches:c})})),i}_findMatches({key:t,value:n,searcher:s}){if(!r(n))return[];let i=[];if(e(n))n.forEach((({v:e,i:n,n:c})=>{if(!r(e))return;const{isMatch:o,score:h,indices:a}=s.searchIn(e);o&&i.push({score:h,key:t,value:e,idx:n,norm:c,indices:a})}));else{const{v:e,n:r}=n,{isMatch:c,score:o,indices:h}=s.searchIn(e);c&&i.push({score:o,key:t,value:e,norm:r,indices:h})}return i}}K.version="6.4.6",K.createIndex=m,K.parseIndex=function(e,{getFn:t=p.getFn}={}){const{keys:n,records:s}=e,i=new f({getFn:t});return i.setKeys(n),i.setIndexRecords(s),i},K.config=p,K.parseQuery=P,function(...e){E.push(...e)}(O);const Q=K;window.LivewireUISpotlight=function(e){return{inputPlaceholder:e.placeholder,searchEngine:"commands",commands:e.commands,commandSearch:null,selectedCommand:null,dependencySearch:null,dependencyQueryResults:window.Livewire.find(e.componentId).entangle("dependencyQueryResults"),requiredDependencies:[],currentDependency:null,resolvedDependencies:{},init:function(){var t=this;this.commandSearch=new Q(this.commands,{threshold:.3,keys:["name","description","synonyms"]}),this.dependencySearch=new Q([],{threshold:.3,keys:["name","description"]}),this.$watch("dependencyQueryResults",(function(e){t.dependencySearch.setCollection(e)})),this.$watch("input",(function(e){0===e.length&&(t.selected=0),null!==t.selectedCommand&&null!==t.currentDependency&&"search"===t.currentDependency.type&&t.$wire.searchDependency(t.selectedCommand.id,t.currentDependency.id,e,t.resolvedDependencies)})),this.$watch("isOpen",(function(n){!1===n&&setTimeout((function(){t.input="",t.inputPlaceholder=e.placeholder,t.searchEngine="commands",t.resolvedDependencies={},t.selectedCommand=null,t.currentDependency=null,t.selectedCommand=null,t.requiredDependencies=[]}),300)}))},isOpen:!1,toggleOpen:function(){var e=this;this.isOpen?this.isOpen=!1:(this.input="",this.isOpen=!0,setTimeout((function(){e.$refs.input.focus()}),100))},input:"",filteredItems:function(){return"commands"===this.searchEngine?this.commandSearch.search(this.input).map((function(e,t){return[e,t]})):"search"===this.searchEngine?this.dependencySearch.search(this.input).map((function(e,t){return[e,t]})):[]},selectUp:function(){var e=this;this.selected=Math.max(0,this.selected-1),this.$nextTick((function(){e.$refs.results.children[e.selected+1].scrollIntoView({block:"nearest"})}))},selectDown:function(){var e=this;this.selected=Math.min(this.filteredItems().length-1,this.selected+1),this.$nextTick((function(){e.$refs.results.children[e.selected+1].scrollIntoView({block:"nearest"})}))},go:function(e){var t,n=this;(null===this.selectedCommand&&(this.selectedCommand=this.commands.find((function(t){return t.id===(e||n.filteredItems()[n.selected][0].item.id)})),this.requiredDependencies=JSON.parse(JSON.stringify(this.selectedCommand.dependencies))),null!==this.currentDependency)&&(t="search"===this.currentDependency.type?e||this.filteredItems()[this.selected][0].item.id:this.input,this.resolvedDependencies[this.currentDependency.id]=t);this.requiredDependencies.length>0?(this.input="",this.currentDependency=this.requiredDependencies.pop(),this.inputPlaceholder=this.currentDependency.placeholder,this.searchEngine="search"===this.currentDependency.type&&"search"):(this.isOpen=!1,this.$wire.execute(this.selectedCommand.id,this.resolvedDependencies))},selected:0}}},578:()=>{}},n={};function s(e){var i=n[e];if(void 0!==i)return i.exports;var r=n[e]={exports:{}};return t[e](r,r.exports,s),r.exports}s.m=t,e=[],s.O=(t,n,i,r)=>{if(!n){var c=1/0;for(a=0;a=r)&&Object.keys(s.O).every((e=>s.O[e](n[h])))?n.splice(h--,1):(o=!1,r0&&e[a-1][2]>r;a--)e[a]=e[a-1];e[a]=[n,i,r]},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={569:0,880:0};s.O.j=t=>0===e[t];var t=(t,n)=>{var i,r,[c,o,h]=n,a=0;for(i in o)s.o(o,i)&&(s.m[i]=o[i]);if(h)var l=h(s);for(t&&t(n);as(48)));var i=s.O(void 0,[880],(()=>s(578)));i=s.O(i)})(); \ No newline at end of file diff --git a/resources/js/spotlight.js b/resources/js/spotlight.js index 799ee5a..0fb1764 100644 --- a/resources/js/spotlight.js +++ b/resources/js/spotlight.js @@ -17,7 +17,7 @@ window.LivewireUISpotlight = (config) => { resolvedDependencies: {}, init() { - this.commandSearch = new Fuse(this.commands, {threshold: 0.3, keys: ['name', 'description']}); + this.commandSearch = new Fuse(this.commands, {threshold: 0.3, keys: ['name', 'description', 'synonyms']}); this.dependencySearch = new Fuse([], {threshold: 0.3, keys: ['name', 'description']}); this.$watch('dependencyQueryResults', value => { this.dependencySearch.setCollection(value) }); diff --git a/src/Spotlight.php b/src/Spotlight.php index 86f8e66..e5a4027 100644 --- a/src/Spotlight.php +++ b/src/Spotlight.php @@ -99,6 +99,7 @@ public function render(): View | Factory 'id' => $command->getId(), 'name' => $command->getName(), 'description' => $command->getDescription(), + 'synonyms' => $command->getSynonyms(), 'dependencies' => $command->dependencies()?->toArray() ?? [], ]; }), diff --git a/src/SpotlightCommand.php b/src/SpotlightCommand.php index bf0bd4e..05dc7f4 100644 --- a/src/SpotlightCommand.php +++ b/src/SpotlightCommand.php @@ -8,6 +8,8 @@ abstract class SpotlightCommand protected string $description = ''; + protected array $synonyms = []; + public function dependencies(): ?SpotlightCommandDependencies { return null; @@ -23,6 +25,11 @@ public function getName(): string return $this->name; } + public function getSynonyms(): array + { + return $this->synonyms; + } + public function getId(): string { return md5(static::class); diff --git a/src/stubs/spotlight.stub b/src/stubs/spotlight.stub index 5c21dfc..f0f9a18 100644 --- a/src/stubs/spotlight.stub +++ b/src/stubs/spotlight.stub @@ -20,6 +20,12 @@ class {{ class }} extends SpotlightCommand */ protected string $description = 'Redirect to user'; + /** + * You can define any number of additional search terms (also known as synonyms) + * to be used when searching for this command. + */ + protected array $synonyms = []; + /** * Defining dependencies is optional. If you don't have any dependencies you can remove this method. * Dependencies are asked from your user in the order you add the dependencies.