├── .gitignore ├── GlobalSearchModule.gif ├── src ├── shims.d.ts ├── index.ts ├── config.js ├── CollectionResults.vue ├── adjustFieldsForDisplays.ts └── module.vue ├── package.json ├── tsconfig.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.local 5 | .env 6 | .env.development 7 | 8 | .idea -------------------------------------------------------------------------------- /GlobalSearchModule.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/u12206050/directus-extension-global-search/HEAD/GlobalSearchModule.gif -------------------------------------------------------------------------------- /src/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue'; 3 | const component: DefineComponent<{}, {}, any>; 4 | export default component; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { defineModule } from '@directus/extensions-sdk'; 2 | import ModuleComponent from './module.vue'; 3 | 4 | export default defineModule({ 5 | id: 'global-search', 6 | name: 'Global Search', 7 | icon: 'search', 8 | routes: [ 9 | { 10 | path: '', 11 | component: ModuleComponent, 12 | }, 13 | ], 14 | }); 15 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | things: { 3 | collection: 'things', 4 | limit: 10, 5 | display: 'The {{title}} in {{topic.name}}', 6 | fields: ['title', 'topic.name'], 7 | filter: { 8 | _and: [ 9 | { topic: { _exists: true } }, 10 | ] 11 | }, 12 | sort: 'name', 13 | }, 14 | topics: { 15 | collection: 'topics', 16 | limit: 5, 17 | display: '{{name}}', 18 | fields: ['name'], 19 | }, 20 | happenings: { 21 | collection: 'happenings', 22 | limit: 5, 23 | display: '{{label}} [{{status}}]', 24 | fields: ['topic.name', 'label'], 25 | }, 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "directus-extension-global-search", 3 | "version": "2.0.0", 4 | "keywords": [ 5 | "directus", 6 | "directus-extension", 7 | "directus-custom-module" 8 | ], 9 | "directus:extension": { 10 | "type": "module", 11 | "path": "dist/index.js", 12 | "source": "src/index.ts", 13 | "host": "^10.0.0" 14 | }, 15 | "scripts": { 16 | "dev": "directus-extension build -w --no-minify", 17 | "build": "directus-extension build" 18 | }, 19 | "devDependencies": { 20 | "@directus/extensions-sdk": "^11.0.2", 21 | "typescript": "^5.4.4", 22 | "vue": "^3.4.21" 23 | }, 24 | "dependencies": { 25 | "@directus/utils": "^11.0.7" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "lib": ["ES2019", "DOM"], 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "noFallthroughCasesInSwitch": true, 8 | "esModuleInterop": true, 9 | "noImplicitAny": true, 10 | "noImplicitThis": true, 11 | "noImplicitReturns": true, 12 | "noUnusedLocals": true, 13 | "noUncheckedIndexedAccess": true, 14 | "noUnusedParameters": true, 15 | "alwaysStrict": true, 16 | "strictNullChecks": true, 17 | "strictFunctionTypes": true, 18 | "strictBindCallApply": true, 19 | "strictPropertyInitialization": true, 20 | "resolveJsonModule": false, 21 | "skipLibCheck": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "allowSyntheticDefaultImports": true, 24 | "isolatedModules": true, 25 | "rootDir": "./src" 26 | }, 27 | "include": ["./src/**/*.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /src/CollectionResults.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gerard Lamusse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Global Search Module 2 | 3 |  4 | 5 | A module for searching across multiple collections at once and returning results to allow you to navigate directly to the item page. 6 | 7 | ## Install 8 | 9 | - Copy the `global-search` folder into `your_directus_project/extensions/modules/` 10 | - Enable the module under Project Settings in the Directus Admin Panel. 11 | 12 | ## Setup (in admin panel) 13 | 14 | - Navigate to the Global Search module and in the side-bar select a collection you wish to search. 15 | - Fill in the display template: This is how search results will be displayed back to you. 16 | - Choose the fields that you want to search. The fewer the better. 17 | 18 | 19 | ### Development 20 | 21 | Update the `"directus:extension"."path"` value in `package.json` with the relative path to your Directus Project if you don't want to copy it across everytime. 22 | 23 | - `npm install` 24 | - `npm run dev` 25 | 26 | ### Build 27 | 28 | - `npm run build` 29 | 30 | 31 | ## Author and Support 32 | 33 | Created by Gerard Lamusse (u12206050) 34 | 35 | If this is helps you in anyway please support me with what you think it is worth via [PayPal.me](https://paypal.me/GerardLamusse) 36 | -------------------------------------------------------------------------------- /src/adjustFieldsForDisplays.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Field } from '@directus/shared/types'; 3 | import { computed } from 'vue'; 4 | 5 | export function adjustFieldsForDisplays(fields: string[], parentCollection: string, { 6 | fieldsStore, 7 | useExtension 8 | }: any): string[] { 9 | const adjustedFields: string[] = fields 10 | .map((fieldKey) => { 11 | const field: Field | null = fieldsStore.getField(parentCollection, fieldKey); 12 | 13 | if (!field) return fieldKey; 14 | if (field.meta?.display === null) return fieldKey; 15 | 16 | const display = useExtension( 17 | 'display', 18 | computed(() => field.meta?.display ?? null) 19 | ); 20 | 21 | if (!display) return fieldKey; 22 | if (!display.value?.fields) return fieldKey; 23 | 24 | let fieldKeys: string[] | null = null; 25 | 26 | if (Array.isArray(display.value.fields)) { 27 | fieldKeys = display.value.fields.map((relatedFieldKey: string) => `${fieldKey}.${relatedFieldKey}`); 28 | } 29 | 30 | if (typeof display.value.fields === 'function') { 31 | fieldKeys = display.value 32 | .fields(field.meta?.display_options, { 33 | collection: field.collection, 34 | field: field.field, 35 | type: field.type, 36 | }) 37 | .map((relatedFieldKey: string) => `${fieldKey}.${relatedFieldKey}`); 38 | } 39 | 40 | if (fieldKeys) { 41 | return fieldKeys.map((fieldKey) => { 42 | /** 43 | * This is for the special case where you want to show a thumbnail in a relation to 44 | * directus_files. The thumbnail itself isn't a real field, but shows the thumbnail based 45 | * on the other available fields (like ID, title, and type). 46 | */ 47 | if (fieldKey.includes('$thumbnail') && field.collection === 'directus_files') { 48 | return fieldKey 49 | .split('.') 50 | .filter((part) => part !== '$thumbnail') 51 | .join('.'); 52 | } 53 | 54 | return fieldKey; 55 | }); 56 | } 57 | 58 | return fieldKey; 59 | }) 60 | .flat(); 61 | 62 | return adjustedFields; 63 | } 64 | -------------------------------------------------------------------------------- /src/module.vue: -------------------------------------------------------------------------------- 1 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | Select a Collection to configure 199 | 200 | 201 | 202 | 203 | Display template of search results 204 | 205 | 206 | 207 | 208 | No. of search results {{ currentIndex.limit }} 209 | 210 | 211 | 212 | 213 | 214 | Search fields 215 | [{{ currentIndex.fields.length }}] 216 | 217 | 222 | Deselect all fields to remove this collection from being searched 223 | 224 | 225 | 226 | Additional filter to apply during query 227 | 232 | 233 | 234 | 235 | {{ persistedIndexes }} 236 | Paste this json into the top of the `index.js` file of the global-search module 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | {{ status.hits }} results returned in {{ status.time }}ms 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 269 | 270 | 271 | {{ t('no_results') }} 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 321 | --------------------------------------------------------------------------------
Deselect all fields to remove this collection from being searched
{{ persistedIndexes }}
Paste this json into the top of the `index.js` file of the global-search module
253 | {{ status.hits }} results returned in {{ status.time }}ms 254 |
271 | {{ t('no_results') }} 272 |