├── .circleci └── config.yml ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── .yarn └── releases │ └── yarn-3.2.0.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── README.md ├── docs └── screenshots │ └── frontend │ ├── dark-theme.png │ └── normal-theme.png ├── frontend ├── CONTRIBUTING.md ├── README.md ├── package.json ├── src │ ├── AlgoliasearchNetlify.ts │ ├── AutocompleteWrapper.ts │ ├── index.scss │ ├── index.ts │ ├── templates.tsx │ └── types │ │ ├── global.d.ts │ │ ├── index.ts │ │ ├── options.ts │ │ └── record.ts ├── tsconfig.json └── webpack.config.js ├── jest.config.js ├── logo.png ├── netlify.toml ├── package.json ├── plugin ├── .nodemon.json ├── CONTRIBUTING.md ├── README.md ├── manifest.yml ├── package.json ├── src │ ├── dev.ts │ ├── escapeRegExp.ts │ ├── index.ts │ ├── starMatch.test.ts │ ├── starMatch.ts │ └── types.ts └── tsconfig.json ├── public ├── 1.html ├── 2.html ├── README.md ├── favicon.ico └── index.html ├── renovate.json ├── scripts ├── dev_plugin.sh ├── generate_netlify_toml.sh └── release.sh ├── tsconfig.base.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: 3 | # Forward the current folder when using wokflows 4 | # persist-to-workspace & attach-workspace 5 | - &persist-work-dir 6 | root: . 7 | paths: . 8 | 9 | - &attach-work-dir 10 | at: ~/app/ 11 | 12 | # Cache Management 13 | - &restore_node_modules 14 | keys: 15 | - v2.8-node-modules-{{ arch }}-{{ checksum "yarn.lock" }} 16 | 17 | - &save_node_modules 18 | key: v2.8-node-modules-{{ arch }}-{{ checksum "yarn.lock" }} 19 | paths: 20 | - .yarn/cache 21 | - node_modules 22 | - src/plugin/node_modules 23 | - src/frontend/node_modules 24 | 25 | # Dependencies 26 | - &yarn 27 | name: Run Yarn 28 | command: | 29 | yarn install --immutable 30 | 31 | defaults: &defaults 32 | working_directory: ~/app 33 | docker: 34 | - image: cimg/node:16.14.0 35 | environment: 36 | NODE_ENV: 'test' 37 | 38 | version: 2 39 | 40 | jobs: 41 | checkout: 42 | <<: *defaults 43 | 44 | steps: 45 | - checkout 46 | 47 | - restore_cache: *restore_node_modules 48 | - run: *yarn 49 | 50 | - save_cache: *save_node_modules 51 | - persist-to-workspace: *persist-work-dir 52 | 53 | lint: 54 | <<: *defaults 55 | 56 | steps: 57 | - attach-workspace: *attach-work-dir 58 | 59 | - run: yarn lint 60 | 61 | test: 62 | <<: *defaults 63 | 64 | steps: 65 | - attach-workspace: *attach-work-dir 66 | 67 | - run: yarn test 68 | 69 | workflows: 70 | version: 2 71 | suite: 72 | jobs: 73 | - checkout 74 | - lint: 75 | requires: 76 | - checkout 77 | - test: 78 | requires: 79 | - checkout 80 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ALGOLIA_API_KEY= 2 | ALGOLIA_BASE_URL=https://crawler.algolia.com 3 | 4 | # Netlify spec 5 | SITE_NAME="algoliasearch-netlify" 6 | DEPLOY_PRIME_URL="https://master--algoliasearch-netlify.netlify.app" 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-commonjs 2 | module.exports = { 3 | env: { 4 | browser: true, // For frontend only 5 | es2020: true, 6 | jest: true, 7 | }, 8 | extends: ['algolia', 'algolia/jest', 'algolia/react', 'algolia/typescript'], 9 | parser: '@typescript-eslint/parser', 10 | parserOptions: { 11 | ecmaVersion: 11, 12 | sourceType: 'module', 13 | }, 14 | plugins: ['prettier', '@typescript-eslint', 'import', 'algolia'], 15 | rules: { 16 | 'algolia/func-style-toplevel': 'error', 17 | 18 | 'arrow-body-style': 'off', 19 | 'no-console': 'off', 20 | 'no-continue': 'off', 21 | 'no-loop-func': 'off', 22 | 'consistent-return': 'off', 23 | '@typescript-eslint/no-unused-vars': 'warn', 24 | 25 | '@typescript-eslint/explicit-member-accessibility': [ 26 | 'error', 27 | { accessibility: 'no-public' }, 28 | ], 29 | 'react/react-in-jsx-scope': 'off', 30 | 31 | // TMP 32 | 'jsdoc/check-examples': ['off'], 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | # Local Netlify folder 106 | .netlify 107 | 108 | # Built files 109 | dist/ 110 | 111 | # IDEs 112 | .vscode 113 | .idea 114 | 115 | # npm package lock 116 | package-lock.json 117 | 118 | # .env 119 | .env 120 | 121 | .DS_Store 122 | 123 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 124 | .yarn/* 125 | !.yarn/releases 126 | !.yarn/plugins 127 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.14.2 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | frontend/README.md 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | printWidth: 80, 7 | } 8 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.2.0.cjs 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.15](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.14...v1.0.15) (2022-04-25) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * ci with yarn3 ([f4475b6](https://github.com/algolia/algoliasearch-netlify/commit/f4475b643d8b89ea9faaa063432be028bb3e8a04)) 7 | * upgrade yarn ([db83c9c](https://github.com/algolia/algoliasearch-netlify/commit/db83c9cd3a48e0c83095933f2a37fc9eb9abee02)) 8 | 9 | 10 | ### Reverts 11 | 12 | * use TS ([967e96d](https://github.com/algolia/algoliasearch-netlify/commit/967e96d345ceb9e27959c28985d976b6b89acce7)) 13 | 14 | 15 | 16 | ## [1.0.14](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.13...v1.0.14) (2022-02-11) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * type error + unecessary build step ([f6c3a8b](https://github.com/algolia/algoliasearch-netlify/commit/f6c3a8bd371d5025a7bce1338ec311cacc8ec7c9)) 22 | * yarn build leftover ([e81979a](https://github.com/algolia/algoliasearch-netlify/commit/e81979a9db581cdc2e1d56939375b6546730960f)) 23 | 24 | 25 | ### Reverts 26 | 27 | * Revert "fix(deps): update dependency node-fetch to v3 (#448)" ([73f3704](https://github.com/algolia/algoliasearch-netlify/commit/73f3704ffea8a7c0487ffacce3863d27d08aa862)), closes [#448](https://github.com/algolia/algoliasearch-netlify/issues/448) 28 | 29 | 30 | 31 | ## [1.0.13](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.12...v1.0.13) (2022-01-26) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * **deps:** update dependency node-fetch to v3 ([#448](https://github.com/algolia/algoliasearch-netlify/issues/448)) ([ec8da2d](https://github.com/algolia/algoliasearch-netlify/commit/ec8da2dce1c86b56ee2aca46a3a5a0417bdde00b)) 37 | * netlify use TS directly ([bcaeb71](https://github.com/algolia/algoliasearch-netlify/commit/bcaeb713decd6ec29c723c5ca1aa5c1ddfe4c89e)) 38 | * **deps:** update dependency node-fetch to v2.6.6 ([#545](https://github.com/algolia/algoliasearch-netlify/issues/545)) ([1970c75](https://github.com/algolia/algoliasearch-netlify/commit/1970c75d15caa16ccee2a8b746c074dae2acd217)) 39 | * **deps:** update dependency node-fetch to v2.6.7 ([#672](https://github.com/algolia/algoliasearch-netlify/issues/672)) ([125a4d5](https://github.com/algolia/algoliasearch-netlify/commit/125a4d54496934a0d4ef7dbf2205e3bd2a2079c5)) 40 | 41 | 42 | 43 | ## [1.0.12](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.11...v1.0.12) (2021-11-03) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * upgrade autocomplete.js ([1e321a1](https://github.com/algolia/algoliasearch-netlify/commit/1e321a1eb6f5ae5d97f961928345be14eb43574e)) 49 | 50 | 51 | 52 | ## [1.0.11](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.10...v1.0.11) (2021-11-03) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * hierarchy to breadcrumbs wrong attribute displayed ([4d01afe](https://github.com/algolia/algoliasearch-netlify/commit/4d01afe976795e2094906a85f45ea93bf10cd750)) 58 | 59 | 60 | 61 | ## [1.0.10](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.9...v1.0.10) (2021-10-11) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * bump version ([c6ac356](https://github.com/algolia/algoliasearch-netlify/commit/c6ac35660e740363b9215059ac345adedf9ff94e)) 67 | * **deps:** update dependency node-fetch to v2.6.2 ([#451](https://github.com/algolia/algoliasearch-netlify/issues/451)) ([b170103](https://github.com/algolia/algoliasearch-netlify/commit/b1701031f3536baa635a3ffeb08b55127ac2cd6d)) 68 | * **deps:** update dependency node-fetch to v2.6.5 ([#482](https://github.com/algolia/algoliasearch-netlify/issues/482)) ([df17f0b](https://github.com/algolia/algoliasearch-netlify/commit/df17f0bb057df0ebdde80f35b88fc054635b6596)) 69 | 70 | 71 | 72 | ## [1.0.9](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.8...v1.0.9) (2021-08-30) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * update renovate ([83facfe](https://github.com/algolia/algoliasearch-netlify/commit/83facfeb94954fb71aa5b109f8cd9ef473f62b1a)) 78 | * upgrade webpack to V5 ([#436](https://github.com/algolia/algoliasearch-netlify/issues/436)) ([4ec3d1c](https://github.com/algolia/algoliasearch-netlify/commit/4ec3d1cddf1754cc2695e5313a548ea9c2bbc00e)) 79 | 80 | 81 | 82 | ## [1.0.8](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.7...v1.0.8) (2021-07-20) 83 | 84 | 85 | ### Bug Fixes 86 | 87 | * **autocomplete:** use the new Requester API ([#314](https://github.com/algolia/algoliasearch-netlify/issues/314)) ([67ca346](https://github.com/algolia/algoliasearch-netlify/commit/67ca3462357624d1f6f7bde6045726d7653afc1d)) 88 | 89 | 90 | ### Features 91 | 92 | * **doc:** move everything to algolia.com ([78f8a4e](https://github.com/algolia/algoliasearch-netlify/commit/78f8a4eb04591993ebd17737e93b6ea5b783d6f6)) 93 | 94 | 95 | 96 | ## [1.0.7](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.6...v1.0.7) (2021-04-22) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * move docs to algolia.com ([#277](https://github.com/algolia/algoliasearch-netlify/issues/277)) ([6495afc](https://github.com/algolia/algoliasearch-netlify/commit/6495afc79a03cad0e8b54f9683bea873ecf26ed3)) 102 | * undocument powered by ([#278](https://github.com/algolia/algoliasearch-netlify/issues/278)) ([ee0cfdd](https://github.com/algolia/algoliasearch-netlify/commit/ee0cfddd9f46982d93138ffd8a42bded5b854112)) 103 | 104 | 105 | 106 | ## [1.0.6](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.5...v1.0.6) (2021-03-22) 107 | 108 | 109 | 110 | ## [1.0.5](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.4...v1.0.5) (2021-03-04) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * autocomplete to alpha.44 ([#227](https://github.com/algolia/algoliasearch-netlify/issues/227)) ([9a467eb](https://github.com/algolia/algoliasearch-netlify/commit/9a467ebf6f6fba28baaad9844404493954c26d42)) 116 | 117 | 118 | 119 | ## [1.0.4](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.3...v1.0.4) (2021-02-09) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * Update autocomplete to alpha.41 ([#198](https://github.com/algolia/algoliasearch-netlify/issues/198)) ([19dc101](https://github.com/algolia/algoliasearch-netlify/commit/19dc1012801f7f24194e54c74a8d53ac3f00f43c)) 125 | 126 | 127 | 128 | ## [1.0.3](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.2...v1.0.3) (2020-12-16) 129 | 130 | 131 | ### Bug Fixes 132 | 133 | * **frontend:** suggestionSnippet correct logic ([#155](https://github.com/algolia/algoliasearch-netlify/issues/155)) ([0076ecb](https://github.com/algolia/algoliasearch-netlify/commit/0076ecb7dc87d3527b47befdbbc59d5737bb015b)) 134 | 135 | 136 | 137 | ## [1.0.2](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.1...v1.0.2) (2020-12-14) 138 | 139 | 140 | ### Bug Fixes 141 | 142 | * **frontend:** css edge cases ([5705615](https://github.com/algolia/algoliasearch-netlify/commit/5705615db67acc701f79cc5f161ea4faf40bb6bf)) 143 | * **frontend:** inherit border-radius ([a4c2fb8](https://github.com/algolia/algoliasearch-netlify/commit/a4c2fb8dc5560ba4d2c1af9cb4ec78075286ba39)) 144 | 145 | 146 | 147 | ## [1.0.1](https://github.com/algolia/algoliasearch-netlify/compare/v1.0.0...v1.0.1) (2020-12-14) 148 | 149 | 150 | ### Bug Fixes 151 | 152 | * **frontend:** upgrade autocomplete ([#146](https://github.com/algolia/algoliasearch-netlify/issues/146)) ([bd6753f](https://github.com/algolia/algoliasearch-netlify/commit/bd6753f129454cf8ffc09ef3ab91aa379a40e09b)) 153 | 154 | 155 | 156 | # [1.0.0](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.21...v1.0.0) (2020-12-09) 157 | 158 | 159 | ### Features 160 | 161 | * Extraction templates ([#142](https://github.com/algolia/algoliasearch-netlify/issues/142)) ([912386f](https://github.com/algolia/algoliasearch-netlify/commit/912386f1e9397cc0f85ba89ffe9de0429b768ad6)) 162 | * **UI:** Hierarchical records support ([#141](https://github.com/algolia/algoliasearch-netlify/issues/141)) ([1c84083](https://github.com/algolia/algoliasearch-netlify/commit/1c840832cf130070d80bf42e24f6e0579a3f3226)) 163 | * migrate to autocomplete.js v1 [BREAKING CHANGES] ([#135](https://github.com/algolia/algoliasearch-netlify/issues/135)) ([1f93199](https://github.com/algolia/algoliasearch-netlify/commit/1f9319924f27cd5ddce03b6578f63096b26c6600)) 164 | 165 | 166 | 167 | ## [0.0.21](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.20...v0.0.21) (2020-11-23) 168 | 169 | 170 | ### Bug Fixes 171 | 172 | * synchronise branch naming ([9381082](https://github.com/algolia/algoliasearch-netlify/commit/93810825a31dda990762f5ecfad0d9b694f8d146)) 173 | 174 | 175 | ### Features 176 | 177 | * add renderJavaScript option ([#134](https://github.com/algolia/algoliasearch-netlify/issues/134)) ([dd91d7e](https://github.com/algolia/algoliasearch-netlify/commit/dd91d7ebfe88917253fc335df76e12d54b37200c)) 178 | 179 | 180 | 181 | ## [0.0.20](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.19...v0.0.20) (2020-11-19) 182 | 183 | 184 | ### Features 185 | 186 | * new 'mainBranch' parameter ([#123](https://github.com/algolia/algoliasearch-netlify/issues/123)) ([0ca01b6](https://github.com/algolia/algoliasearch-netlify/commit/0ca01b642eb28c56c96b21be3664b02610d97047)) 187 | 188 | 189 | 190 | ## [0.0.19](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.18...v0.0.19) (2020-11-12) 191 | 192 | 193 | ### Features 194 | 195 | * new params pathPrefix, customDomain ([#122](https://github.com/algolia/algoliasearch-netlify/issues/122)) ([f06ae0d](https://github.com/algolia/algoliasearch-netlify/commit/f06ae0d0793b039ef7d281d232081e906c9d08a9)) 196 | 197 | 198 | 199 | ## [0.0.18](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.17...v0.0.18) (2020-10-29) 200 | 201 | 202 | ### Bug Fixes 203 | 204 | * **frontend:** add a default color for results title ([#103](https://github.com/algolia/algoliasearch-netlify/issues/103)) ([f22bf92](https://github.com/algolia/algoliasearch-netlify/commit/f22bf92ac236786e2c0c808de458c78c6e113bef)) 205 | 206 | 207 | 208 | ## [0.0.17](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.16...v0.0.17) (2020-10-27) 209 | 210 | 211 | ### Features 212 | 213 | * use algoliasearch-lite to avoid preflight requests ([#102](https://github.com/algolia/algoliasearch-netlify/issues/102)) ([2ec2ede](https://github.com/algolia/algoliasearch-netlify/commit/2ec2ede8ddb05ef71262f88b3f31a7ca66efbd8d)) 214 | 215 | 216 | 217 | ## [0.0.16](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.15...v0.0.16) (2020-10-26) 218 | 219 | 220 | ### Features 221 | 222 | * **frontend:** add warning when no search input is found ([#101](https://github.com/algolia/algoliasearch-netlify/issues/101)) ([07a9669](https://github.com/algolia/algoliasearch-netlify/commit/07a96698437555ccd2e5e88697f02eb1144de780)) 223 | 224 | 225 | 226 | ## [0.0.15](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.14...v0.0.15) (2020-10-20) 227 | 228 | 229 | ### Bug Fixes 230 | 231 | * **autocomplete:** handle null description or content ([#96](https://github.com/algolia/algoliasearch-netlify/issues/96)) ([32afff6](https://github.com/algolia/algoliasearch-netlify/commit/32afff6c60265fcdf5d51da5a7f96a637a0da613)) 232 | * **webpack:** fix dev server command ([#95](https://github.com/algolia/algoliasearch-netlify/issues/95)) ([aa353ea](https://github.com/algolia/algoliasearch-netlify/commit/aa353ea919b35320bcda591e4728e6717bf0f752)) 233 | * use debug mode env to log more info ([#84](https://github.com/algolia/algoliasearch-netlify/issues/84)) ([dc60780](https://github.com/algolia/algoliasearch-netlify/commit/dc60780a3163f39916e3ffc8af1b7eac5b3cf087)) 234 | 235 | 236 | 237 | ## [0.0.14](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.13...v0.0.14) (2020-10-07) 238 | 239 | 240 | ### Bug Fixes 241 | 242 | * missing baseUrl when no env var ([#71](https://github.com/algolia/algoliasearch-netlify/issues/71)) ([9c46e36](https://github.com/algolia/algoliasearch-netlify/commit/9c46e36faa24a4192cb21ea1ba1a28d5ba2a1c2e)) 243 | * typo in frontend readme ([475314e](https://github.com/algolia/algoliasearch-netlify/commit/475314e035176eb50334d03a09236a4e5d1112ea)) 244 | * use failPlugin instead of failBuild ([#74](https://github.com/algolia/algoliasearch-netlify/issues/74)) ([fc21018](https://github.com/algolia/algoliasearch-netlify/commit/fc210183a756845e365118429ac54b4a22c239d7)) 245 | 246 | 247 | 248 | ## [0.0.13](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.12...v0.0.13) (2020-10-06) 249 | 250 | 251 | ### Bug Fixes 252 | 253 | * add more logs + default branches + readme ([#68](https://github.com/algolia/algoliasearch-netlify/issues/68)) ([8d0e0b5](https://github.com/algolia/algoliasearch-netlify/commit/8d0e0b5a1d69ca98afc6221ab7b765b99cef670f)) 254 | 255 | 256 | 257 | ## [0.0.12](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.11...v0.0.12) (2020-10-02) 258 | 259 | 260 | ### Features 261 | 262 | * **plugin:** add siteId and branch params support ([#60](https://github.com/algolia/algoliasearch-netlify/issues/60)) ([3a2c587](https://github.com/algolia/algoliasearch-netlify/commit/3a2c58781c7f99f60b4583d07a611bbe78c58eeb)) 263 | * **public:** add favicon ([#61](https://github.com/algolia/algoliasearch-netlify/issues/61)) ([432b67a](https://github.com/algolia/algoliasearch-netlify/commit/432b67a212bb5d0ea713cf8180770ad9d88c662d)) 264 | 265 | 266 | 267 | ## [0.0.11](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.10...v0.0.11) (2020-10-02) 268 | 269 | 270 | ### Bug Fixes 271 | 272 | * add missing input ([b7f0781](https://github.com/algolia/algoliasearch-netlify/commit/b7f078161c3ce61ca8ac1ed94238e7b13514c204)) 273 | 274 | 275 | ### Features 276 | 277 | * **branches:** support star patterns ([#59](https://github.com/algolia/algoliasearch-netlify/issues/59)) ([34f04d9](https://github.com/algolia/algoliasearch-netlify/commit/34f04d9411d760791cbddb81332a368f5e6284c2)) 278 | 279 | 280 | 281 | ## [0.0.10](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.9...v0.0.10) (2020-10-02) 282 | 283 | 284 | 285 | ## [0.0.9](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.8...v0.0.9) (2020-10-02) 286 | 287 | 288 | ### Bug Fixes 289 | 290 | * Update record schema ([#56](https://github.com/algolia/algoliasearch-netlify/issues/56)) ([f1a7ace](https://github.com/algolia/algoliasearch-netlify/commit/f1a7ace123766a72a173ac0f4880e598a51b6d1c)) 291 | * **dev:** fix fork-ts-checker config options ([#55](https://github.com/algolia/algoliasearch-netlify/issues/55)) ([7abc0d0](https://github.com/algolia/algoliasearch-netlify/commit/7abc0d06127fcf796b8e9e3f108521661e584b40)) 292 | 293 | 294 | ### Features 295 | 296 | * **plugin:** add "branches" input ([#58](https://github.com/algolia/algoliasearch-netlify/issues/58)) ([cf397e2](https://github.com/algolia/algoliasearch-netlify/commit/cf397e2725878e0aeed60d972f3fcc9a091e9454)) 297 | 298 | 299 | 300 | ## [0.0.8](https://github.com/algolia/algoliasearch-netlify/compare/v0.0.7...v0.0.8) (2020-10-01) 301 | 302 | 303 | ### Bug Fixes 304 | 305 | * try to get correct branch name ([#53](https://github.com/algolia/algoliasearch-netlify/issues/53)) ([dbcda60](https://github.com/algolia/algoliasearch-netlify/commit/dbcda60ceda4eeaaaeb202ddba411b8047e2b596)) 306 | * **README:** fix npm version badge ([#47](https://github.com/algolia/algoliasearch-netlify/issues/47)) ([717e6e1](https://github.com/algolia/algoliasearch-netlify/commit/717e6e1c16e2fb75d16e4e5ec4e632d7eb301e1b)) 307 | * tutorial ([#43](https://github.com/algolia/algoliasearch-netlify/issues/43)) ([fce5c2e](https://github.com/algolia/algoliasearch-netlify/commit/fce5c2e3f2c15a90ac55a4e4162b046283c104ea)) 308 | * **doc:** correct library links ([#44](https://github.com/algolia/algoliasearch-netlify/issues/44)) ([1f8533e](https://github.com/algolia/algoliasearch-netlify/commit/1f8533e0558c48c953f3ce0ecb87e723cca6c598)) 309 | 310 | 311 | ### Features 312 | 313 | * **dev:** improve dev env ([#54](https://github.com/algolia/algoliasearch-netlify/issues/54)) ([ef53726](https://github.com/algolia/algoliasearch-netlify/commit/ef5372677f9e34cafeeedce0da7d0bf4223e9512)) 314 | * **frontend:** handle content snippeting ([#51](https://github.com/algolia/algoliasearch-netlify/issues/51)) ([61a0d4e](https://github.com/algolia/algoliasearch-netlify/commit/61a0d4e35c1ac33081ef3ecbef1a60f02e2074a4)) 315 | * add env var to disable the plugin ([#45](https://github.com/algolia/algoliasearch-netlify/issues/45)) ([60c7c44](https://github.com/algolia/algoliasearch-netlify/commit/60c7c44a0c843665795d3591aa29cab3c6b60433)) 316 | * **plugin:** send version in crawl call ([#42](https://github.com/algolia/algoliasearch-netlify/issues/42)) ([f52622b](https://github.com/algolia/algoliasearch-netlify/commit/f52622b1f684ff083e6e59e1d2c9e17d4597cd7c)) 317 | 318 | 319 | 320 | ## [0.0.7](https://github.com/algolia/algoliasearch-netlify/compare/8493dd5559af4ec9bd75a9be1c6392d611029ea4...v0.0.7) (2020-09-28) 321 | 322 | 323 | ### Bug Fixes 324 | 325 | * **release:** add missing --new-version flag ([ee56219](https://github.com/algolia/algoliasearch-netlify/commit/ee56219fe980cf746245f2d846df0f7933936f19)) 326 | * disable in prod ([#21](https://github.com/algolia/algoliasearch-netlify/issues/21)) ([78fd020](https://github.com/algolia/algoliasearch-netlify/commit/78fd02055f986825a4a3fb2a0ffc3905e56493d1)) 327 | * review from Netlify ([#34](https://github.com/algolia/algoliasearch-netlify/issues/34)) ([c559cb0](https://github.com/algolia/algoliasearch-netlify/commit/c559cb0fef844355b86540e61a7367b87d9a03c8)) 328 | * title ([f3d3a74](https://github.com/algolia/algoliasearch-netlify/commit/f3d3a744278b7bcc0e8771d4229373b74f4ccc93)) 329 | * title ([5429fc5](https://github.com/algolia/algoliasearch-netlify/commit/5429fc52ca671ddcbd436ac51b7f2630a0a81c5e)) 330 | * title ([ba6aafc](https://github.com/algolia/algoliasearch-netlify/commit/ba6aafcb1a8121d0bcb80b67eacdf6350168d412)) 331 | * title ([8493dd5](https://github.com/algolia/algoliasearch-netlify/commit/8493dd5559af4ec9bd75a9be1c6392d611029ea4)) 332 | * update dependencies ([#22](https://github.com/algolia/algoliasearch-netlify/issues/22)) ([d545cc0](https://github.com/algolia/algoliasearch-netlify/commit/d545cc01244036007063abfe9f4efb7943f97af7)) 333 | 334 | 335 | ### Features 336 | 337 | * add circleci ([#20](https://github.com/algolia/algoliasearch-netlify/issues/20)) ([5feafa9](https://github.com/algolia/algoliasearch-netlify/commit/5feafa9533ab9a096dde4d54a34c7ac5d734b7e2)) 338 | * add front-end bundle ([#35](https://github.com/algolia/algoliasearch-netlify/issues/35)) ([9e42155](https://github.com/algolia/algoliasearch-netlify/commit/9e42155f0b9e6a86b13e459d5d2ecdbed28bdb5a)) 339 | * add siteName & deployPrimeUrl ([8de476a](https://github.com/algolia/algoliasearch-netlify/commit/8de476a21c0653f0c70aa1b2092682b60add55f5)) 340 | * new version ([86a1378](https://github.com/algolia/algoliasearch-netlify/commit/86a1378e1e87c6c20a6819eccf9124581ec2ce6e)) 341 | * prepare rename to algoliasearch-netlify ([#19](https://github.com/algolia/algoliasearch-netlify/issues/19)) ([d3ccb96](https://github.com/algolia/algoliasearch-netlify/commit/d3ccb9687e8274398ac044e2f5b9c114121a2576)) 342 | * release script ([#40](https://github.com/algolia/algoliasearch-netlify/issues/40)) ([e670563](https://github.com/algolia/algoliasearch-netlify/commit/e67056337da0a1393f202ded32efa04f5261eccb)) 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Development 4 | 5 | ```sh 6 | yarn dev 7 | ``` 8 | 9 | Launches all 3 dev tools: 10 | 11 | - [plugin](./plugin) 12 | - [frontend library](./frontend) 13 | - and [test website](./public) 14 | 15 | See each tool's respective README. 16 | 17 | ## Releasing 18 | 19 | ```sh 20 | yarn release 21 | ``` 22 | 23 | This releases both `plugin` & `frontend` to be sure our versions are aligned even if there were changes in only one of both projects. 24 | Push to your website 25 | 26 | ## Architecture 27 | 28 | - [`frontend/`](./frontend/): Front-end library 29 | - [`plugin/`](./plugin/): Netlify plugin sources 30 | - [`public/`](./public/): Test website 31 | 32 | This repository is itself a netlify site, which allows us to test the whole setup. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Algolia + Netlify logo 4 | 5 |

6 |

7 | 8 | Build Status 9 | 10 | 11 | Netlify build status 12 | 13 | 14 | Version 15 | 16 |

17 | 18 |

Algolia Netlify plugin

19 | 20 | Automatically index your website to Algolia when deploying your project to Netlify with the Algolia Crawler. 21 | 22 | - [What is Algolia?](https://www.algolia.com/doc/guides/getting-started/what-is-algolia/) 23 | - [What is Algolia's Crawler?](https://www.algolia.com/doc/tools/crawler/getting-started/overview/) 24 | 25 |

26 | 27 | Sign in to Algolia with Netlify 28 | 29 |

30 | 31 | ## Documentation 32 | 33 | - [Getting Started](https://www.algolia.com/doc/tools/crawler/netlify-plugin/quick-start/) 34 | - [Plugin configuration](https://www.algolia.com/doc/tools/crawler/netlify-plugin/plugin/) 35 | - [Front-end configuration](https://www.algolia.com/doc/tools/crawler/netlify-plugin/front-end/) 36 | - [FAQ](https://www.algolia.com/doc/tools/crawler/netlify-plugin/netlify-faq/) 37 | - [Data Extraction](https://www.algolia.com/doc/tools/crawler/netlify-plugin/extraction-strategy/) 38 | - [Uninstall](https://www.algolia.com/doc/tools/crawler/netlify-plugin/uninstalling/) 39 | 40 | ## Troubleshooting 41 | 42 | - Need help? We have you covered in our [Discourse forum](https://discourse.algolia.com/c/netlify/28) 43 | - Found a bug in the plugin? Please read our [contributing guide](/CONTRIBUTING.md) and either open an [issue](https://github.com/algolia/algoliasearch-netlify/issues) or a [pull request](https://github.com/algolia/algoliasearch-netlify/pulls) 44 | - Can't find the answer to your issue? Please reach out to the [Algolia Support team](https://support.algolia.com/hc/en-us/requests/new) 45 | 46 | ## Development & Release 47 | 48 | See [CONTRIBUTING.md](./CONTRIBUTING.md). 49 | -------------------------------------------------------------------------------- /docs/screenshots/frontend/dark-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/algoliasearch-netlify/97ec146098930e60b51ea973ebdff06c32dfef8c/docs/screenshots/frontend/dark-theme.png -------------------------------------------------------------------------------- /docs/screenshots/frontend/normal-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/algoliasearch-netlify/97ec146098930e60b51ea973ebdff06c32dfef8c/docs/screenshots/frontend/normal-theme.png -------------------------------------------------------------------------------- /frontend/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Scripts 4 | 5 | - `yarn dev`: run dev environment 6 | - `yarn release`: build & publish the library 7 | 8 | ## Development 9 | 10 | From this folder: 11 | 12 | ```sh 13 | yarn dev 14 | ``` 15 | 16 | Or from the root of the repository: 17 | 18 | ```sh 19 | yarn dev:frontend 20 | ``` 21 | 22 | This runs a `webpack-dev-server` on port 9100. 23 | Meant to be used in conjunction with the [test website](../public/). 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # algoliasearch-netlify-frontend 2 | 3 | `algoliasearch-netlify-frontend` is the front-end bundle we recommend to use with our Netlify plugin. 4 | 5 | - [Install the plugin](https://www.algolia.com/doc/tools/crawler/netlify-plugin/quick-start/) 6 | - [Plugin configuration](https://www.algolia.com/doc/tools/crawler/netlify-plugin/plugin/) 7 | - [Front-end configuration](https://www.algolia.com/doc/tools/crawler/netlify-plugin/front-end/) 8 | 9 | ## Development & Release 10 | 11 | See [CONTRIBUTING.md](./CONTRIBUTING.md). 12 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@algolia/algoliasearch-netlify-frontend", 3 | "version": "1.0.15", 4 | "author": "Algolia Team ", 5 | "license": "MIT", 6 | "repository": "https://github.com/algolia/algoliasearch-netlify.git", 7 | "bugs": { 8 | "url": "https://github.com/algolia/algoliasearch-netlify/issues" 9 | }, 10 | "files": [ 11 | "README.md", 12 | "dist/" 13 | ], 14 | "scripts": { 15 | "build": "webpack --mode production", 16 | "dev": "PORT=9100 webpack serve --mode development", 17 | "postinstall": "[ -d dist/ ] || npm run build" 18 | }, 19 | "devDependencies": { 20 | "@algolia/autocomplete-js": "1.5.6", 21 | "@algolia/autocomplete-preset-algolia": "1.5.6", 22 | "@algolia/autocomplete-theme-classic": "1.5.6", 23 | "@algolia/transporter": "4.13.0", 24 | "@babel/core": "7.17.9", 25 | "@babel/preset-env": "7.16.11", 26 | "@types/react": "17.0.44", 27 | "algoliasearch": "4.13.0", 28 | "babel-loader": "8.2.4", 29 | "clean-webpack-plugin": "4.0.0", 30 | "core-js": "3.21.1", 31 | "css-loader": "6.7.1", 32 | "fork-ts-checker-webpack-plugin": "6.5.1", 33 | "mini-css-extract-plugin": "2.6.0", 34 | "mustache": "4.2.0", 35 | "node-sass": "7.0.1", 36 | "postcss": "8.4.12", 37 | "postcss-loader": "6.2.1", 38 | "postcss-preset-env": "7.4.3", 39 | "preact": "10.7.1", 40 | "sass-loader": "12.6.0", 41 | "terser-webpack-plugin": "4.2.3", 42 | "ts-loader": "9.2.8", 43 | "webpack": "5.72.0", 44 | "webpack-cli": "4.9.2", 45 | "webpack-dev-server": "4.8.1" 46 | }, 47 | "keywords": [ 48 | "algolia", 49 | "algoliasearch", 50 | "crawl", 51 | "crawler", 52 | "indexing", 53 | "jamstack", 54 | "netlify-plugin", 55 | "netlify-search", 56 | "netlify", 57 | "plugin", 58 | "robots", 59 | "search", 60 | "ui" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /frontend/src/AlgoliasearchNetlify.ts: -------------------------------------------------------------------------------- 1 | import { AutocompleteWrapper } from './AutocompleteWrapper'; 2 | import type { Options } from './types'; 3 | 4 | const defaultOptions: Omit< 5 | Options, 6 | 'apiKey' | 'appId' | 'branch' | 'selector' | 'siteId' 7 | > = { 8 | analytics: true, 9 | hitsPerPage: 5, 10 | debug: false, 11 | poweredBy: true, 12 | placeholder: 'Search...', 13 | openOnFocus: false, 14 | }; 15 | 16 | const mandatory: Array = [ 17 | 'appId', 18 | 'apiKey', 19 | 'selector', 20 | 'siteId', 21 | 'branch', 22 | ]; 23 | 24 | const instances: AutocompleteWrapper[] = []; 25 | 26 | function algoliasearchNetlify(_options: Options): void { 27 | const options = { 28 | ...defaultOptions, 29 | ..._options, 30 | }; 31 | for (const key of mandatory) { 32 | if (options[key]) continue; 33 | 34 | throw new Error(`[algoliasearch-netlify] Missing mandatory key: ${key}`); 35 | } 36 | 37 | const autocomplete = new AutocompleteWrapper(options); 38 | instances.push(autocomplete); 39 | 40 | // Wait for DOM initialization, then render 41 | const render = (): void => { 42 | autocomplete.render(); 43 | }; 44 | if (['complete', 'interactive'].includes(document.readyState)) { 45 | render(); 46 | } else { 47 | document.addEventListener('DOMContentLoaded', render); 48 | } 49 | } 50 | 51 | export { algoliasearchNetlify }; 52 | -------------------------------------------------------------------------------- /frontend/src/AutocompleteWrapper.ts: -------------------------------------------------------------------------------- 1 | import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; 2 | import type { 3 | AutocompleteApi, 4 | AutocompleteSource, 5 | SourceTemplates, 6 | } from '@algolia/autocomplete-js'; 7 | import type { HighlightedHit } from '@algolia/autocomplete-preset-algolia'; 8 | import algoliasearch from 'algoliasearch/lite'; 9 | import type { SearchClient } from 'algoliasearch/lite'; 10 | 11 | // @ts-expect-error 12 | import { version } from '../package.json'; 13 | 14 | import { templates } from './templates'; 15 | import type { Options, AlgoliaRecord } from './types'; 16 | 17 | class AutocompleteWrapper { 18 | private options; 19 | private indexName; 20 | private client; 21 | private $themeNode: HTMLStyleElement | null = null; 22 | private autocomplete: AutocompleteApi | undefined; 23 | 24 | constructor(options: Options) { 25 | this.options = options; 26 | this.client = this.createClient(); 27 | this.indexName = this.computeIndexName(); 28 | } 29 | 30 | render(): void { 31 | const $input = document.querySelector(this.options.selector) as HTMLElement; 32 | if (!$input) { 33 | console.error( 34 | `[algoliasearch-netlify] no element ${this.options.selector} found` 35 | ); 36 | return; 37 | } 38 | 39 | let detachedMediaQuery = undefined; 40 | if (this.options.detached !== undefined) { 41 | if (this.options.detached === true) { 42 | detachedMediaQuery = ''; 43 | } else if (this.options.detached === false) { 44 | detachedMediaQuery = 'none'; 45 | } else { 46 | detachedMediaQuery = this.options.detached.mediaQuery; 47 | } 48 | } 49 | const instance = autocomplete({ 50 | container: $input, 51 | autoFocus: false, 52 | placeholder: this.options.placeholder, 53 | debug: this.options.debug, 54 | openOnFocus: this.options.openOnFocus, 55 | panelPlacement: 'input-wrapper-width', 56 | detachedMediaQuery, 57 | getSources: () => { 58 | return [this.getSources()]; 59 | }, 60 | }); 61 | this.applyTheme($input.firstElementChild as HTMLElement); 62 | 63 | this.autocomplete = instance; 64 | } 65 | 66 | private computeIndexName(): string { 67 | const { siteId, branch } = this.options; 68 | 69 | // Keep in sync with crawler code in /netlify/crawl 70 | const cleanBranch = branch 71 | .trim() 72 | .replace(/[^\p{L}\p{N}_.-]+/gu, '-') 73 | .replace(/-{2,}/g, '-') 74 | .toLocaleLowerCase(); 75 | return `netlify_${siteId}_${cleanBranch}_all`; 76 | } 77 | 78 | private createClient(): SearchClient { 79 | const client = algoliasearch(this.options.appId, this.options.apiKey); 80 | client.addAlgoliaAgent(`Netlify integration ${version}`); 81 | return client; 82 | } 83 | 84 | private getSources(): AutocompleteSource> { 85 | const poweredBy = this.options.poweredBy; 86 | const tpls: SourceTemplates> = { 87 | header() { 88 | return ''; 89 | }, 90 | item({ item, components }) { 91 | return templates.item(item, components); 92 | }, 93 | footer() { 94 | if (poweredBy) { 95 | return templates.poweredBy({ 96 | hostname: window.location.host, 97 | }); 98 | } 99 | return ''; 100 | }, 101 | }; 102 | const res: AutocompleteSource> = { 103 | sourceId: 'algoliaHits', 104 | getItems: ({ query }) => { 105 | return getAlgoliaResults({ 106 | searchClient: this.client, 107 | queries: [ 108 | { 109 | indexName: this.indexName, 110 | query, 111 | params: { 112 | analytics: this.options.analytics, 113 | hitsPerPage: this.options.hitsPerPage, 114 | }, 115 | }, 116 | ], 117 | }); 118 | }, 119 | getItemUrl({ item }) { 120 | return item.url; 121 | }, 122 | templates: tpls, 123 | }; 124 | return res; 125 | } 126 | 127 | private applyTheme(el: HTMLElement | null): void { 128 | if (!el || !this.options.theme) { 129 | return; 130 | } 131 | 132 | const theme = this.options.theme; 133 | this.$themeNode = addCss( 134 | `.aa-Autocomplete, .aa-Panel, .aa-DetachedContainer { 135 | ${theme.mark && `--color-mark: ${theme.mark};`} 136 | ${theme.mark && `--color-background: ${theme.background};`} 137 | ${theme.mark && `--color-selected: ${theme.selected};`} 138 | ${theme.mark && `--color-text: ${theme.text};`} 139 | ${theme.mark && `--color-source-icon: ${theme.colorSourceIcon};`} 140 | }`, 141 | this.$themeNode 142 | ); 143 | } 144 | } 145 | 146 | function addCss( 147 | css: string, 148 | $mainStyle: HTMLElement | null = null 149 | ): HTMLStyleElement { 150 | const $usedSibling = 151 | $mainStyle ?? 152 | document.querySelector( 153 | 'link[rel=stylesheet][href*="algoliasearchNetlify"]' 154 | ) ?? 155 | document.getElementsByTagName('head')[0].lastChild!; 156 | const $styleTag = document.createElement('style'); 157 | $styleTag.setAttribute('type', 'text/css'); 158 | $styleTag.appendChild(document.createTextNode(css)); 159 | return $usedSibling.parentNode!.insertBefore( 160 | $styleTag, 161 | $usedSibling.nextSibling 162 | ); 163 | } 164 | 165 | export { AutocompleteWrapper }; 166 | -------------------------------------------------------------------------------- /frontend/src/index.scss: -------------------------------------------------------------------------------- 1 | @import '~@algolia/autocomplete-theme-classic'; 2 | 3 | $color-bg: #fff; 4 | $color-muted: #969faf; 5 | $color-light: #797979; 6 | $color-text: #23263b; 7 | $color-mark: rgb(84, 104, 255); 8 | $color-bg-selected: #f5f5fa; 9 | $color-input-icon: #777; 10 | $color-source-icon: rgba(80, 80, 80, 0.32); 11 | 12 | $font-size-xs: 12px; 13 | $font-size-s: 14px; 14 | $font-size-m: 16px; 15 | 16 | $size-xs: 2px; 17 | $size-s: 4px; 18 | $size-m: 8px; 19 | $size-l: 16px; 20 | $size-xl: 32px; 21 | 22 | $height: $size-xl; 23 | $height-icon: $size-l; 24 | $font-size: $font-size-m; 25 | 26 | .aa-Autocomplete, .aa-Panel, .aa-DetachedContainer { 27 | --color-mark: #{$color-mark}; 28 | --color-background: #{$color-bg}; 29 | --color-selected: #{$color-bg-selected}; 30 | --color-text: #{$color-text}; 31 | --color-input-icon: #{$color-input-icon}; 32 | --color-source-icon: #{$color-source-icon}; 33 | --height: #{$height}; 34 | --height-icon: #{$height-icon}; 35 | --font-size: #{$font-size}; 36 | } 37 | 38 | //// ---- Overridden definitions of classic theme 39 | 40 | .aa-Panel { 41 | min-width: 350px; 42 | z-index: 1100; 43 | margin-top: $size-xs; 44 | 45 | .aa-PanelLayout { 46 | padding-top: 0; 47 | padding-bottom: 0; 48 | background-color: var(--color-background); 49 | 50 | .aa-PanelLayoutPreview { 51 | border-left: solid 1px var(--color-selected); 52 | } 53 | } 54 | 55 | .aa-GradientBottom { 56 | background-image: none; 57 | } 58 | } 59 | 60 | .aa-Autocomplete, .aa-DetachedFormContainer { 61 | .aa-Form { 62 | height: var(--height); 63 | font-size: var(--font-size); 64 | padding: 0; 65 | background-color: var(--color-background); 66 | 67 | &:focus-within { 68 | box-shadow: none; 69 | } 70 | .aa-InputWrapperPrefix { 71 | padding: 0; 72 | 73 | .aa-Label { 74 | padding: 0; 75 | 76 | svg { 77 | left: 0; 78 | vertical-align: middle; 79 | color: var(--color-input-icon); 80 | width: var(--height-icon); 81 | } 82 | } 83 | } 84 | .aa-InputWrapper { 85 | .aa-Input { 86 | height: var(--height); 87 | color: var(--color-text); 88 | } 89 | } 90 | .aa-InputWrapperSuffix { 91 | height: var(--height); 92 | 93 | .aa-ClearButton { 94 | padding: 0; 95 | 96 | &:hover, 97 | &:focus { 98 | color: var(--color-text); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | .aa-Item { 106 | color: var(--color-text); 107 | padding: $size-xs 0; 108 | 109 | a { 110 | color: inherit; 111 | text-decoration: none; 112 | } 113 | 114 | &[aria-selected='true'] { 115 | background-color: var(--color-selected); 116 | } 117 | .aa-ItemContent { 118 | display: flex; 119 | color: var(--color-text); 120 | 121 | mark { 122 | color: var(--color-mark); 123 | background-color: transparent; 124 | } 125 | } 126 | .aa-ItemIcon { 127 | align-items: baseline; 128 | color: var(--color-source-icon); 129 | 130 | background: none; 131 | box-shadow: none; 132 | margin: 0 var(--aa-spacing-half) 0 2px; 133 | } 134 | .aa-ItemTitle { 135 | font-size: $font-size-s; 136 | font-weight: bold; 137 | line-height: 18px; 138 | } 139 | .aa-ItemHierarchy { 140 | font-size: $font-size-xs; 141 | font-style: italic; 142 | line-height: 18px; 143 | opacity: 0.8; 144 | padding: 1px 0; 145 | } 146 | .aa-ItemDescription { 147 | font-size: $font-size-xs; 148 | line-height: 16px; 149 | color: $color-light; 150 | } 151 | } 152 | 153 | .aa-DetachedContainer { 154 | background: var(--color-background); 155 | 156 | .aa-DetachedFormContainer { 157 | .aa-DetachedCancelButton { 158 | color: var(--color-text); 159 | } 160 | } 161 | } 162 | 163 | .aa-DetachedOverlay { 164 | z-index: 10; 165 | } 166 | 167 | .aa-DetachedSearchButton { 168 | background-color: var(--color-background); 169 | 170 | .aa-DetachedSearchButtonIcon { 171 | color: var(--color-input-icon); 172 | } 173 | } 174 | 175 | 176 | /* Search by */ 177 | .aa-powered-by-link { 178 | display: inline-block; 179 | width: 64px; 180 | height: 18px; 181 | margin-left: $size-s; 182 | text-indent: 101%; 183 | overflow: hidden; 184 | white-space: nowrap; 185 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODUgMTIwIj48ZyBmaWxsPSJub25lIj48cGF0aCBmaWxsPSIjNTQ2OEZGIiBkPSJNMTYuOC0xLjAwMWg4OC40YzguNyAwIDE1LjggNy4wNjUgMTUuOCAxNS44djg4LjQwNWMwIDguNy03LjA2NSAxNS43OTUtMTUuOCAxNS43OTVIMTYuOGMtOC43IDAtMTUuOC03LjA2LTE1LjgtMTUuNzk1VjE0Ljc1OWMwLTguNjk1IDcuMDYtMTUuNzYgMTUuOC0xNS43NiIvPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03My41MDUgMjUuNzg4di00LjExNWE1LjIwOSA1LjIwOSAwIDAwLTUuMjEtNS4yMDVINTYuMTVhNS4yMDkgNS4yMDkgMCAwMC01LjIxIDUuMjA1djQuMjI1YzAgLjQ3LjQzNS44LjkxLjY5YTM3Ljk2NiAzNy45NjYgMCAwMTEwLjU3LTEuNDljMy40NjUgMCA2Ljg5NS40NyAxMC4yMSAxLjM4LjQ0LjExLjg3NS0uMjE1Ljg3NS0uNjlNNDAuMjIgMzEuMTczbC0yLjA3NS0yLjA3NWE1LjIwNiA1LjIwNiAwIDAwLTcuMzY1IDBsLTIuNDggMi40NzVhNS4xODUgNS4xODUgMCAwMDAgNy4zNTVsMi4wNCAyLjA0Yy4zMy4zMjUuODA1LjI1IDEuMDk1LS4wNzVhMzkuODc2IDM5Ljg3NiAwIDAxMy45NzUtNC42NiAzNy42OCAzNy42OCAwIDAxNC43LTRjLjM2NC0uMjIuNC0uNzMuMTEtMS4wNm0yMi4xNjQgMTMuMDY1djE3LjhjMCAuNTEuNTUuODc1IDEuMDIuNjJsMTUuODI1LTguMTljLjM2LS4xOC40Ny0uNjIuMjktLjk4LTMuMjgtNS43NTUtOS4zNy05LjY4NS0xNi40MDUtOS45NC0uMzY1IDAtLjczLjI5LS43My42OW0wIDQyLjg4Yy0xMy4xOTUgMC0yMy45MTUtMTAuNzA1LTIzLjkxNS0yMy44OCAwLTEzLjE3NSAxMC43Mi0yMy44NzUgMjMuOTE1LTIzLjg3NSAxMy4yIDAgMjMuOTE2IDEwLjcgMjMuOTE2IDIzLjg3NXMtMTAuNjggMjMuODgtMjMuOTE2IDIzLjg4bTAtNTcuOGMtMTguNzQgMC0zMy45NCAxNS4xOC0zMy45NCAzMy45MiAwIDE4Ljc0NSAxNS4yIDMzLjg5IDMzLjk0IDMzLjg5czMzLjk0LTE1LjE4IDMzLjk0LTMzLjkyNWMwLTE4Ljc0NS0xNS4xNjUtMzMuODg1LTMzLjk0LTMzLjg4NSIvPjxwYXRoIGZpbGw9IiM1NDY4RkYiIGQ9Ik0yNDAuMjE0IDk0LjE3N2MtMjMuMzY1LjExLTIzLjM2NS0xOC44NTUtMjMuMzY1LTIxLjg3NWwtLjA0LTcxLjA0NSAxNC4yNTQtMi4yNnY3MC42MWMwIDEuNzE1IDAgMTIuNTYgOS4xNSAxMi41OTV2MTEuOTc1aC4wMDF6bS01Ny43OC0xMS42MWM0LjM3NCAwIDcuNjItLjI1NSA5Ljg4LS42OVY2Ny4zOTJhMjkuMTk2IDI5LjE5NiAwIDAwLTMuNDMtLjY5NSAzMy43NDIgMzMuNzQyIDAgMDAtNC45NTYtLjM2NWMtMS41NyAwLTMuMTc1LjExLTQuNzc1LjM2NS0xLjYwNS4yMi0zLjA2NS42NTUtNC4zNCAxLjI3NS0xLjI3NS42Mi0yLjMzNSAxLjQ5NS0zLjEgMi42Mi0uOCAxLjEzLTEuMTY1IDEuNzg1LTEuMTY1IDMuNDk1IDAgMy4zNDUgMS4xNjUgNS4yOCAzLjI4IDYuNTUgMi4xMTUgMS4yNzUgNC45OTUgMS45MyA4LjYwNiAxLjkzem0tMS4yNC01MS42ODVjNC43IDAgOC42NzQuNTg1IDExLjg4NCAxLjc1IDMuMjA2IDEuMTY1IDUuNzk2IDIuOCA3LjY5IDQuODc1IDEuOTM1IDIuMTEgMy4yNDUgNC45MTUgNC4wNDYgNy45Ljg0IDIuOTg1IDEuMjQgNi4yNiAxLjI0IDkuODZ2MzYuNjJjLTIuMTg1LjQ3LTUuNTA2IDEuMDE1LTkuOTUgMS42Ny00LjQ0Ni42NTUtOS40NC45ODUtMTQuOTg2Ljk4NS0zLjY4IDAtNy4wNy0uMzY1LTEwLjA5NS0xLjA1NS0zLjA2NS0uNjktNS42NS0xLjgyLTcuODQtMy4zODUtMi4xNS0xLjU2NS0zLjgyNS0zLjU3LTUuMDY1LTYuMDQtMS4yMDUtMi40OC0xLjgyNS01Ljk3LTEuODI1LTkuNjEgMC0zLjQ5NS42OS01LjcxNSAyLjA0NS04LjEyIDEuMzgtMi40IDMuMjQtNC4zNjUgNS41NzUtNS44OTUgMi4zNy0xLjUzIDUuMDY1LTIuNjIgOC4xNjUtMy4yNzUgMy4xLS42NTUgNi4zNDUtLjk4NSA5LjY5NS0uOTg1IDEuNTcgMCAzLjIxLjExIDQuOTYuMjkgMS43MTUuMTg1IDMuNTc1LjUxNSA1LjU0NS45ODV2LTIuMzNjMC0xLjYzNS0uMTg1LTMuMi0uNTg1LTQuNjU1YTEwLjAxMiAxMC4wMTIgMCAwMC0yLjA0NS0zLjg5NWMtLjk4NS0xLjEzLTIuMjU1LTIuMDA1LTMuODYtMi42Mi0xLjYwNS0uNjItMy42NS0xLjA5NS02LjA5LTEuMDk1LTMuMjggMC02LjI3LjQtOS4wMDUuODc1LTIuNzM1LjQ3LTQuOTk1IDEuMDItNi43MSAxLjYzNWwtMS43MS0xMS42OGMxLjc4NS0uNjIgNC40NDUtMS4yNCA3Ljg3NS0xLjg1NSAzLjQyNS0uNjYgNy4xMS0uOTUgMTEuMDQ1LS45NWguMDAxem0yODEuNTEgNTEuMjg1YzQuMzc1IDAgNy42MTUtLjI1NSA5Ljg3NS0uNjk1di0xNC40OGMtLjgtLjIyLTEuOTMtLjQ3NS0zLjQyNS0uNjk1YTMzLjgxMyAzMy44MTMgMCAwMC00Ljk2LS4zNjVjLTEuNTY1IDAtMy4xNy4xMS00Ljc3NS4zNjUtMS42LjIyLTMuMDYuNjU1LTQuMzM1IDEuMjc1LTEuMjguNjItMi4zMzUgMS40OTUtMy4xIDIuNjItLjgwNSAxLjEzLTEuMTY1IDEuNzg1LTEuMTY1IDMuNDk1IDAgMy4zNDUgMS4xNjUgNS4yOCAzLjI4IDYuNTUgMi4xNSAxLjMxIDQuOTk1IDEuOTMgOC42MDUgMS45M3ptLTEuMjA1LTUxLjY0NWM0LjcgMCA4LjY3NC41OCAxMS44ODQgMS43NDUgMy4yMDUgMS4xNjUgNS43OTUgMi44IDcuNjkgNC44NzUgMS44OTUgMi4wNzUgMy4yNDUgNC45MTUgNC4wNDUgNy45Ljg0IDIuOTg1IDEuMjQgNi4yNiAxLjI0IDkuODY1djM2LjYxNWMtMi4xODUuNDctNS41MDUgMS4wMTUtOS45NSAxLjY3NS00LjQ0NS42NTUtOS40NC45OC0xNC45ODUuOTgtMy42OCAwLTcuMDctLjM2NS0xMC4wOTQtMS4wNTUtMy4wNjUtLjY5LTUuNjUtMS44Mi03Ljg0LTMuMzg1LTIuMTUtMS41NjUtMy44MjUtMy41Ny01LjA2NS02LjA0LTEuMjA1LTIuNDc1LTEuODI1LTUuOTctMS44MjUtOS42MSAwLTMuNDk1LjY5NS01LjcxNSAyLjA0NS04LjEyIDEuMzgtMi40IDMuMjQtNC4zNjUgNS41NzUtNS44OTUgMi4zNy0xLjUyNSA1LjA2NS0yLjYyIDguMTY1LTMuMjc1IDMuMS0uNjU1IDYuMzQ1LS45OCA5LjctLjk4IDEuNTY1IDAgMy4yMDUuMTEgNC45NTUuMjlzMy41NzUuNTEgNS41NC45ODV2LTIuMzNjMC0xLjY0LS4xOC0zLjIwNS0uNTgtNC42NmE5Ljk3NyA5Ljk3NyAwIDAwLTIuMDQ1LTMuODk1Yy0uOTg1LTEuMTMtMi4yNTUtMi4wMDUtMy44Ni0yLjYyLTEuNjA2LS42Mi0zLjY1LTEuMDktNi4wOS0xLjA5LTMuMjggMC02LjI3LjQtOS4wMDUuODctMi43MzUuNDc1LTQuOTk1IDEuMDItNi43MSAxLjY0bC0xLjcxLTExLjY4NWMxLjc4NS0uNjIgNC40NDUtMS4yMzUgNy44NzUtMS44NTUgMy40MjUtLjYyIDcuMTA1LS45NDUgMTEuMDQ1LS45NDV6bS00Mi44LTYuNzdjNC43NzQgMCA4LjY4LTMuODYgOC42OC04LjYzIDAtNC43NjUtMy44NjYtOC42MjUtOC42OC04LjYyNS00LjgxIDAtOC42NzUgMy44Ni04LjY3NSA4LjYyNSAwIDQuNzcgMy45IDguNjMgOC42NzUgOC42M3ptNy4xOCA3MC40MjVoLTE0LjMyNnYtNjEuNDRsMTQuMzI1LTIuMjU1djYzLjY5NWguMDAxem0tMjUuMTE2IDBjLTIzLjM2NS4xMS0yMy4zNjUtMTguODU1LTIzLjM2NS0yMS44NzVsLS4wNC03MS4wNDUgMTQuMjU1LTIuMjZ2NzAuNjFjMCAxLjcxNSAwIDEyLjU2IDkuMTUgMTIuNTk1djExLjk3NXptLTQ2LjMzNS0zMS40NDVjMC02LjE1NS0xLjM1LTExLjI4NS0zLjk3NC0xNC44NS0yLjYyNS0zLjYwNS02LjMwNS01LjM4NS0xMS4wMS01LjM4NS00LjcgMC04LjM4NiAxLjc4LTExLjAwNiA1LjM4NS0yLjYyNSAzLjYtMy45MDQgOC42OTUtMy45MDQgMTQuODUgMCA2LjIyNSAxLjMxNSAxMC40MDUgMy45NCAxNC4wMSAyLjYyNSAzLjY0IDYuMzA1IDUuNDI1IDExLjAxIDUuNDI1IDQuNyAwIDguMzg1LTEuODIgMTEuMDEtNS40MjUgMi42MjQtMy42NCAzLjkzNC03Ljc4NSAzLjkzNC0xNC4wMXptMTQuNTgtLjAzNWMwIDQuODA1LS42OSA4LjQ0LTIuMTE0IDEyLjQxLTEuNDIgMy45NjUtMy40MjUgNy4zNS02LjAxIDEwLjE1NS0yLjU5IDIuOC01LjY5IDQuOTg1LTkuMzM2IDYuNTE1LTMuNjQ0IDEuNTI1LTkuMjYgMi40LTEyLjA2NSAyLjQtMi44MS0uMDM1LTguMzg1LS44MzUtMTEuOTk1LTIuNC0zLjYxLTEuNTY1LTYuNzEtMy43MTUtOS4yOTUtNi41MTUtMi41OS0yLjgwNS00LjU5NC02LjE5LTYuMDU0LTEwLjE1NS0xLjQ1Ni0zLjk3LTIuMTg1LTcuNjA1LTIuMTg1LTEyLjQxcy42NTQtOS40MyAyLjExNC0xMy4zNmMxLjQ2LTMuOTMgMy41LTcuMjggNi4xMjUtMTAuMDggMi42MjUtMi44MDUgNS43Ni00Ljk1NSA5LjMzLTYuNDggMy42MS0xLjUzIDcuNTg1LTIuMjU1IDExLjg4NS0yLjI1NSA0LjMwNSAwIDguMjc1Ljc2IDExLjkyIDIuMjU1IDMuNjUgMS41MjUgNi43ODYgMy42NzUgOS4zMzYgNi40OCAyLjU4NCAyLjggNC41OSA2LjE1IDYuMDUgMTAuMDggMS41MyAzLjkzIDIuMjk1IDguNTU1IDIuMjk1IDEzLjM2aC0uMDAxem0tMTA3LjI4NCAwYzAgNS45NjUgMS4zMSAxMi41OSAzLjkzNSAxNS4zNTUgMi42MjUgMi43NyA2LjAxNCA0LjE1IDEwLjE3NSA0LjE1IDIuMjYgMCA0LjQxLS4zMjUgNi40MTQtLjk0NSAyLjAwNS0uNjIgMy42MDYtMS4zNSA0Ljg4Ni0yLjIydi0zNS4zNGMtMS4wMi0uMjItNS4yODYtMS4wOTUtOS40MS0xLjItNS4xNzUtLjE1LTkuMTEgMS45NjUtMTEuODggNS4zNDUtMi43MzYgMy4zOS00LjEyIDkuMzItNC4xMiAxNC44NTV6bTM5LjYyNSAyOC4wOTVjMCA5LjcyLTIuNDggMTYuODE1LTcuNDc2IDIxLjMzLTQuOTkgNC41MS0xMi42MSA2Ljc3LTIyLjg5IDYuNzctMy43NTUgMC0xMS41NTUtLjczLTE3Ljc5LTIuMTFsMi4yOTUtMTEuMjg1YzUuMjE1IDEuMDkgMTIuMTA1IDEuMzg1IDE1LjcxNSAxLjM4NSA1LjcyIDAgOS44MDUtMS4xNjUgMTIuMjQ1LTMuNDk1IDIuNDQ1LTIuMzMgMy42NDUtNS43ODUgMy42NDUtMTAuMzc1di0yLjMzYy0xLjQyLjY5LTMuMjggMS4zODUtNS41NzUgMi4xMTUtMi4yOTUuNjktNC45NTUgMS4wNTUtNy45NSAxLjA1NS0zLjkzNSAwLTcuNTEtLjYyLTEwLjc1LTEuODYtMy4yNDUtMS4yMzUtNi4wNTUtMy4wNTUtOC4zNS01LjQ2LTIuMjk1LTIuNC00LjEyLTUuNDItNS4zOTUtOS4wMjUtMS4yNzUtMy42MDUtMS45MzUtMTAuMDQ1LTEuOTM1LTE0Ljc3NSAwLTQuNDQuNjk1LTEwLjAxIDIuMDQ2LTEzLjcyNSAxLjM4NC0zLjcxIDMuMzUtNi45MTUgNi4wMTQtOS41NyAyLjYyNi0yLjY1NSA1LjgzNS00LjY5NSA5LjU5LTYuMTkgMy43NTUtMS40OSA4LjE2LTIuNDM1IDEyLjkzNS0yLjQzNSA0LjYzNSAwIDguOS41OCAxMy4wNTUgMS4yNzUgNC4xNTUuNjkgNy42OSAxLjQxNSAxMC41NyAyLjIxNXY1Ni40OWguMDAxeiIvPjwvZz48L3N2Zz4=); 186 | background-repeat: no-repeat; 187 | background-size: contain; 188 | vertical-align: middle; 189 | } 190 | .aa-powered-by { 191 | text-align: right; 192 | font-size: $font-size-xs; 193 | color: $color-muted; 194 | padding: $size-m $size-m $size-s 0; 195 | font-weight: normal; 196 | } 197 | -------------------------------------------------------------------------------- /frontend/src/index.ts: -------------------------------------------------------------------------------- 1 | import { algoliasearchNetlify } from './AlgoliasearchNetlify'; 2 | 3 | // eslint-disable-next-line import/no-commonjs 4 | module.exports = algoliasearchNetlify; 5 | -------------------------------------------------------------------------------- /frontend/src/templates.tsx: -------------------------------------------------------------------------------- 1 | import type { AutocompleteComponents, VNode } from '@algolia/autocomplete-js'; 2 | import type { Hit } from '@algolia/client-search'; 3 | 4 | import type { AlgoliaRecord } from './types'; 5 | 6 | export const templates = { 7 | poweredBy: ({ hostname }: { hostname: string }): VNode => { 8 | const escapedHostname = encodeURIComponent(hostname); 9 | return ( 10 |
11 | Search by 12 | 16 | Algolia 17 | 18 |
19 | ); 20 | }, 21 | 22 | item: ( 23 | hit: AlgoliaRecord, 24 | components: AutocompleteComponents 25 | ): JSX.Element => { 26 | return ( 27 | 28 |
29 |
30 | 31 | 38 | 39 |
40 |
41 |
42 | {hit.hierarchy?.lvl0 ?? ( 43 | 44 | )} 45 |
46 | {hit.hierarchy && ( 47 |
48 | {hierarchyToBreadcrumbs(hit, components)} 49 |
50 | )} 51 |
52 | {getSuggestionSnippet(hit, components)} 53 |
54 |
55 |
56 |
57 | ); 58 | }, 59 | }; 60 | 61 | /** 62 | * Transform a highlighted hierarchy object into an array of Highlighted elements. 63 | * 3 levels max are returned. 64 | * 65 | * @param hit - A record with a hierarchy field ( { lvl0: string, lvl1: string, lvl2: string, ... } ). 66 | * @param components - Autocomplete components. 67 | * @returns An array of JSX.Elements | string, representing of the highlighted hierarchy starting from lvl1. 68 | * Between each element, we insert a ' > ' character to render them as breadcrumbs eventually. 69 | */ 70 | function hierarchyToBreadcrumbs( 71 | hit: Hit, 72 | components: AutocompleteComponents 73 | ): Array { 74 | const breadcrumbArray: Array = []; 75 | let addedLevels = 0; 76 | if (!hit.hierarchy) { 77 | return breadcrumbArray; 78 | } 79 | for (let i = 1; i < 7 && addedLevels < 3; ++i) { 80 | const lvl = `lvl${i}`; 81 | if (hit.hierarchy[lvl] && hit.hierarchy[lvl].length > 0) { 82 | if (addedLevels > 0) { 83 | breadcrumbArray.push(' > '); 84 | } 85 | breadcrumbArray.push( 86 | 87 | ); 88 | ++addedLevels; 89 | } 90 | } 91 | return breadcrumbArray; 92 | } 93 | 94 | function getSuggestionSnippet( 95 | hit: Hit, 96 | components: AutocompleteComponents 97 | ): JSX.Element | string { 98 | // If they are defined as `searchableAttributes`, 'description' and 'content' are always 99 | // present in the `_snippetResult`, even if they don't match. 100 | // So we need to have 1 check on the presence and 1 check on the match 101 | const description = hit._snippetResult?.description; 102 | const content = hit._snippetResult?.content; 103 | 104 | // Take in priority props that matches the search 105 | if (description && description.matchLevel === 'full') { 106 | return ; 107 | } 108 | if (content && content.matchLevel === 'full') { 109 | return ; 110 | } 111 | 112 | // Otherwise take the prop that was at least correctly returned 113 | if (description && !content) { 114 | return ; 115 | } 116 | if (content) { 117 | return ; 118 | } 119 | 120 | // Otherwise raw value or empty 121 | return hit.description || hit.content || ''; 122 | } 123 | -------------------------------------------------------------------------------- /frontend/src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'autocomplete.js' { 2 | declare function autocomplete(..._args: any[]): any; 3 | export default autocomplete; 4 | } 5 | declare module 'autocomplete.js/src/common/utils' { 6 | declare function isMsie(): boolean; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './options'; 2 | export * from './record'; 3 | -------------------------------------------------------------------------------- /frontend/src/types/options.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | // Mandatory 3 | appId: string; 4 | apiKey: string; 5 | selector: string; 6 | siteId: string; 7 | branch: string; 8 | 9 | // Optional 10 | analytics?: boolean; 11 | hitsPerPage?: number; 12 | theme?: { 13 | mark?: string; 14 | background?: string; 15 | selected?: string; 16 | text?: string; 17 | colorSourceIcon?: string; 18 | }; 19 | debug?: boolean; 20 | detached?: boolean | { mediaQuery: string }; 21 | placeholder?: string; 22 | openOnFocus?: boolean; 23 | poweredBy?: boolean; 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/types/record.ts: -------------------------------------------------------------------------------- 1 | export type AlgoliaRecord = { 2 | objectID: string; 3 | 4 | url: string; 5 | origin: string; 6 | title: string; 7 | content: string; 8 | 9 | lang?: string; 10 | description?: string; 11 | keywords?: string[]; 12 | image?: string; 13 | authors?: string[]; 14 | datePublished?: number; 15 | dateModified?: number; 16 | category?: string; 17 | 18 | hierarchy?: Hierarchy; 19 | hierarchicalCategories?: Hierarchy; 20 | 21 | urlDepth?: number; 22 | position?: number; 23 | }; 24 | 25 | export type Hierarchy = { [lvl: string]: string }; 26 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "lib": ["es2020", "DOM"], 5 | "rootDir": "src", 6 | /* Use a fake outDir */ 7 | "outDir": "dist-webpack-never-used", 8 | "jsx": "react-jsx", 9 | "jsxImportSource": "preact" 10 | }, 11 | "include": ["src/**/*"] 12 | } 13 | -------------------------------------------------------------------------------- /frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /* eslint import/no-commonjs: 'off' */ 3 | const path = require('path'); 4 | 5 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 6 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 8 | const TerserPlugin = require('terser-webpack-plugin'); 9 | const { HotModuleReplacementPlugin } = require('webpack'); 10 | 11 | function plugins({ production }) { 12 | if (production === undefined) { 13 | throw new Error('plugins: Missing parameter'); 14 | } 15 | 16 | const defaultPlugins = [ 17 | new CleanWebpackPlugin(), 18 | new MiniCssExtractPlugin({ 19 | filename: '[name].css', 20 | chunkFilename: '[id].css', 21 | }), 22 | ]; 23 | 24 | let additionalPlugins = []; 25 | if (production) { 26 | console.log('No additionalPlugin yet'); 27 | } else { 28 | additionalPlugins = [ 29 | new HotModuleReplacementPlugin(), 30 | new ForkTsCheckerWebpackPlugin({ 31 | typescript: { 32 | configFile: 'tsconfig.json', 33 | }, 34 | eslint: { 35 | files: 'src/**/*.{js,ts}', 36 | }, 37 | }), 38 | ]; 39 | } 40 | 41 | return [...defaultPlugins, ...additionalPlugins]; 42 | } 43 | 44 | function styleLoaders({ production, sourceMap }) { 45 | if (production === undefined || sourceMap === undefined) { 46 | throw new Error('styleLoaders: Missing parameter'); 47 | } 48 | 49 | const loaders = []; 50 | 51 | loaders.push({ 52 | loader: MiniCssExtractPlugin.loader, 53 | }); 54 | 55 | loaders.push({ 56 | loader: 'css-loader', 57 | options: { 58 | sourceMap, 59 | }, 60 | }); 61 | 62 | loaders.push({ 63 | loader: 'postcss-loader', 64 | options: { 65 | postcssOptions: { 66 | plugins: [ 67 | [ 68 | 'postcss-preset-env', 69 | { 70 | stage: 4, // Only stable polyfills 71 | autoprefixer: {}, 72 | }, 73 | ], 74 | ], 75 | sourceMap, 76 | }, 77 | }, 78 | }); 79 | 80 | loaders.push({ 81 | loader: 'sass-loader', 82 | options: { 83 | sourceMap, 84 | sassOptions: { 85 | includePaths: [path.resolve(__dirname, 'src')], 86 | }, 87 | }, 88 | }); 89 | 90 | return loaders; 91 | } 92 | 93 | module.exports = function (env, options) { 94 | const mode = options.mode || 'development'; 95 | const production = mode === 'production'; 96 | const sourceMap = true; 97 | 98 | console.log(`Webpack running in "${mode}" mode`); 99 | 100 | const resolvedExtensions = ['.ts', '.tsx', '.js']; 101 | 102 | const buildFolder = 'dist'; 103 | 104 | // See what changes what here: https://webpack.js.org/configuration/devtool/#devtool 105 | let devtool = 'eval-cheap-module-source-map'; 106 | if (production) { 107 | devtool = 'cheap-source-map'; 108 | } else if (process.env.CI) { 109 | devtool = undefined; 110 | } 111 | 112 | let devServer; 113 | if (!production) { 114 | devServer = { 115 | static: path.resolve(__dirname, buildFolder), 116 | port: process.env.PORT || '1234', 117 | historyApiFallback: true, 118 | hot: true, 119 | client: { 120 | overlay: { 121 | warnings: false, 122 | errors: true, 123 | }, 124 | }, 125 | headers: { 126 | 'Access-Control-Allow-Origin': 'http://localhost:9000', 127 | 'Access-Control-Allow-Methods': 128 | 'HEAD, GET, POST, PUT, DELETE, PATCH, OPTIONS', 129 | }, 130 | }; 131 | } 132 | 133 | return { 134 | target: 'web', 135 | mode, 136 | devServer, 137 | entry: { 138 | algoliasearchNetlify: [ 139 | path.resolve(__dirname, 'src', 'index.scss'), 140 | path.resolve(__dirname, 'src', 'index.ts'), 141 | ], 142 | }, 143 | devtool, 144 | module: { 145 | rules: [ 146 | { 147 | test: /\.tsx?$/, 148 | exclude: /node_modules/, 149 | use: [ 150 | { 151 | loader: 'babel-loader', 152 | options: { 153 | compact: production, 154 | presets: [ 155 | ['@babel/preset-env', { useBuiltIns: 'entry', corejs: '3' }], 156 | ], 157 | }, 158 | }, 159 | { 160 | loader: 'ts-loader', 161 | options: { 162 | configFile: 'tsconfig.json', 163 | transpileOnly: !production, 164 | experimentalWatchApi: !production, 165 | }, 166 | }, 167 | ], 168 | }, 169 | { 170 | test: /\.scss$/, 171 | exclude: /node_modules/, 172 | use: styleLoaders({ production, sourceMap }), 173 | }, 174 | ], 175 | }, 176 | resolve: { 177 | extensions: resolvedExtensions, 178 | }, 179 | optimization: { 180 | minimize: production, 181 | minimizer: [ 182 | new TerserPlugin({ 183 | extractComments: false, 184 | parallel: true, 185 | terserOptions: { 186 | parse: {}, 187 | compress: {}, 188 | mangle: true, 189 | output: null, 190 | toplevel: true, 191 | ie8: false, 192 | }, 193 | }), 194 | ], 195 | usedExports: true, 196 | sideEffects: true, 197 | }, 198 | plugins: plugins({ production }), 199 | output: { 200 | path: path.resolve(__dirname, buildFolder), 201 | filename: '[name].js', 202 | libraryTarget: 'umd', 203 | library: '[name]', 204 | publicPath: '/', 205 | chunkLoadTimeout: 30000, 206 | }, 207 | stats: { 208 | assets: true, 209 | assetsSort: 'size', 210 | builtAt: false, 211 | cached: false, 212 | cachedAssets: false, 213 | children: false, 214 | chunks: false, 215 | colors: true, 216 | entrypoints: false, 217 | hash: false, 218 | loggingTrace: true, 219 | modules: false, 220 | version: false, 221 | }, 222 | }; 223 | }; 224 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-commonjs 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testPathIgnorePatterns: ['/node_modules/', '/dist/'], 6 | }; 7 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/algoliasearch-netlify/97ec146098930e60b51ea973ebdff06c32dfef8c/logo.png -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | 2 | # This file is generated by scripts/generate_netlify_toml.sh 3 | # DO NOT MODIFY, MODIFY THE GENERATING SCRIPT 4 | 5 | 6 | [[plugins]] 7 | package = "@algolia/netlify-plugin-crawler" 8 | [plugins.inputs] 9 | branches = ["*"] 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@algolia/netlify", 3 | "version": "1.0.15", 4 | "private": true, 5 | "author": "Algolia Team ", 6 | "license": "MIT", 7 | "repository": "https://github.com/algolia/algoliasearch-netlify.git", 8 | "workspaces": [ 9 | "frontend", 10 | "plugin" 11 | ], 12 | "scripts": { 13 | "build": "echo 'Website already built in public'", 14 | "build:plugin": "cd plugin && yarn build", 15 | "dev": "yarn && concurrently --success first --kill-others --names 'plugin,frontend,website' --prefix-colors 'magenta,cyan,yellow' 'yarn dev:plugin' 'yarn dev:frontend' 'yarn dev:website'", 16 | "dev:frontend": "cd frontend && yarn dev", 17 | "dev:plugin": "cd plugin && yarn dev", 18 | "dev:website": "http-server --port 9000 -c -1 public", 19 | "changelog:generate": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", 20 | "lint": "eslint --ext=jsx,ts,tsx,js .", 21 | "release": "./scripts/release.sh", 22 | "test": "jest" 23 | }, 24 | "devDependencies": { 25 | "@algolia/algoliasearch-netlify-frontend": "*", 26 | "@algolia/netlify-plugin-crawler": "*", 27 | "@netlify/build": "26.5.2", 28 | "@types/jest": "27.4.1", 29 | "@types/node": "16.11.26", 30 | "@typescript-eslint/eslint-plugin": "5.18.0", 31 | "@typescript-eslint/parser": "5.18.0", 32 | "concurrently": "7.1.0", 33 | "conventional-changelog": "3.1.25", 34 | "conventional-changelog-cli": "2.2.2", 35 | "eslint": "8.12.0", 36 | "eslint-config-algolia": "20.0.0", 37 | "eslint-config-prettier": "8.5.0", 38 | "eslint-config-standard": "16.0.3", 39 | "eslint-plugin-algolia": "2.0.0", 40 | "eslint-plugin-eslint-comments": "3.2.0", 41 | "eslint-plugin-import": "2.26.0", 42 | "eslint-plugin-jest": "25.7.0", 43 | "eslint-plugin-jsdoc": "37.9.7", 44 | "eslint-plugin-jsx-a11y": "6.6.0", 45 | "eslint-plugin-node": "11.1.0", 46 | "eslint-plugin-prettier": "4.0.0", 47 | "eslint-plugin-promise": "6.0.0", 48 | "eslint-plugin-react": "7.29.4", 49 | "eslint-plugin-react-hooks": "4.4.0", 50 | "eslint-plugin-standard": "4.1.0", 51 | "http-server": "14.1.0", 52 | "jest": "27.5.1", 53 | "json": "11.0.0", 54 | "netlify-cli": "8.19.3", 55 | "nodemon": "2.0.15", 56 | "prettier": "2.7.1", 57 | "renovate-config-algolia": "2.1.9", 58 | "ts-jest": "27.1.4", 59 | "typescript": "4.6.3" 60 | }, 61 | "engines": { 62 | "yarn": "1.*", 63 | "node": "16.*" 64 | }, 65 | "packageManager": "yarn@3.2.0" 66 | } 67 | -------------------------------------------------------------------------------- /plugin/.nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": "js,json,ts", 3 | "watch": [ 4 | "../.env", 5 | "src" 6 | ], 7 | "delay": "500", 8 | "verbose": false, 9 | "ignore": [] 10 | } 11 | -------------------------------------------------------------------------------- /plugin/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Scripts 4 | 5 | - `yarn dev`: run dev environment 6 | 7 | ## Development 8 | 9 | ### Pre-requisites 10 | 11 | **Only accessible to Algolia employees.** 12 | 13 | 1. Access to the Algolia team on Netlify (only granted to Algolia employees). 14 | 2. Access to the test website in this org: 15 | 3. Clone the repo and link it to the test website on Netlify: 16 | 17 | ```sh 18 | git clone git@github.com:algolia/algoliasearch-netlify.git 19 | cd algoliasearch-netlify 20 | yarn 21 | yarn netlify link 22 | # Accept linking it with the current git remote, it'll detect the correct site automatically 23 | ``` 24 | 25 | 4. Setup `.env` by copying the example file: 26 | 27 | ```sh 28 | cp .env.example .env 29 | ``` 30 | 31 | Make sure the values in this file are good. 32 | 33 | ### Running the dev env 34 | 35 | From this folder: 36 | 37 | ```sh 38 | yarn dev 39 | ``` 40 | 41 | Or from the root of the repository: 42 | 43 | ```sh 44 | yarn dev:plugin 45 | ``` 46 | 47 | It builds the site locally, running the local version of the plugin. 48 | 49 | To change the crawler target from the prod one to a local instance, simply change in your `.env` `ALGOLIA_BASE_URL`. 50 | -------------------------------------------------------------------------------- /plugin/README.md: -------------------------------------------------------------------------------- 1 | # netlify-plugin-crawler 2 | 3 | This plugin links your Netlify site with Algolia's Crawler. 4 | It will trigger a crawl on each successful build. 5 | 6 | - [Install the plugin](https://www.algolia.com/doc/tools/crawler/netlify-plugin/quick-start/) 7 | - [Plugin configuration](https://www.algolia.com/doc/tools/crawler/netlify-plugin/plugin/) 8 | - [Front-end configuration](https://www.algolia.com/doc/tools/crawler/netlify-plugin/front-end/) 9 | 10 | ## Development & Release 11 | 12 | See [CONTRIBUTING.md](./CONTRIBUTING.md). 13 | -------------------------------------------------------------------------------- /plugin/manifest.yml: -------------------------------------------------------------------------------- 1 | name: '@algolia/netlify-plugin-crawler' 2 | inputs: 3 | - name: disabled 4 | description: Disable the plugin 5 | default: false 6 | - name: branches 7 | description: Branches to index. Each branch in this array will have its content crawled in a dedicated index. 8 | default: ['master', 'main'] 9 | - name: mainBranch 10 | description: Main working branch. Useful when you tweak Algolia search settings. If set, Algolia indices created for other branches will inherit the settings of the mainBranch's index. 11 | - name: pathPrefix 12 | description: The prefix of your website if it's not at the root level, e.g /blog 13 | - name: customDomain 14 | description: The custom domain that you use, if it's not possible to define it on your Netlify's settings. 15 | - name: renderJavaScript 16 | description: If true, we will use JavaScript to render your website. 17 | - name: template 18 | description: Change the way we extract records and their schema. 19 | -------------------------------------------------------------------------------- /plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@algolia/netlify-plugin-crawler", 3 | "version": "1.0.15", 4 | "author": "Algolia Team ", 5 | "license": "MIT", 6 | "repository": "https://github.com/algolia/algoliasearch-netlify.git", 7 | "bugs": { 8 | "url": "https://github.com/algolia/algoliasearch-netlify/issues" 9 | }, 10 | "main": "dist/index.js", 11 | "files": [ 12 | "README.md", 13 | "manifest.yml", 14 | "dist" 15 | ], 16 | "scripts": { 17 | "build": "npx tsc -b", 18 | "dev": "nodemon --config .nodemon.json --exec '../scripts/dev_plugin.sh'" 19 | }, 20 | "dependencies": { 21 | "node-fetch": "2.6.7" 22 | }, 23 | "devDependencies": { 24 | "@types/node-fetch": "2.6.1", 25 | "dotenv": "16.0.0" 26 | }, 27 | "keywords": [ 28 | "netlify", 29 | "netlify-plugin", 30 | "search", 31 | "crawl", 32 | "crawler", 33 | "robots", 34 | "indexing", 35 | "netlify-search", 36 | "algolia", 37 | "algoliasearch" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /plugin/src/dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | function forceEnvVar(env: Record, key: string): void { 4 | if (env[key] === undefined) { 5 | throw new Error(`Missing ${key} in .env`); 6 | } 7 | process.env[key] = env[key]; 8 | } 9 | 10 | // In dev env, yarn netlify build inherits of the env vars set in Netlify's UI. 11 | // We need to manually override them. 12 | export function loadDevEnvVariables(): void { 13 | // eslint-disable-next-line @typescript-eslint/no-var-requires 14 | const dotenv = require('dotenv'); 15 | 16 | const filePath = path.join(__dirname, '..', '..', '.env'); 17 | const env = dotenv.config({ path: filePath }).parsed; 18 | 19 | forceEnvVar(env, 'ALGOLIA_API_KEY'); 20 | forceEnvVar(env, 'ALGOLIA_BASE_URL'); 21 | } 22 | -------------------------------------------------------------------------------- /plugin/src/escapeRegExp.ts: -------------------------------------------------------------------------------- 1 | // Taken from https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 2 | export function escapeRegExp(string: string): string { 3 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string 4 | } 5 | -------------------------------------------------------------------------------- /plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { NetlifyPluginOptions, NetlifyPluginUtils } from '@netlify/build'; 2 | import type { Response } from 'node-fetch'; 3 | import fetch from 'node-fetch'; 4 | 5 | // @ts-expect-error 6 | import { version } from '../package.json'; 7 | 8 | import { loadDevEnvVariables } from './dev'; 9 | import { starMatch } from './starMatch'; 10 | import type { PluginInputs } from './types'; 11 | 12 | function createSummaryLogger( 13 | show: NetlifyPluginUtils['status']['show'] 14 | ): (message: string) => void { 15 | return (message): void => { 16 | show({ title: 'Algolia Crawler', summary: message }); 17 | console.log(message); 18 | }; 19 | } 20 | 21 | export async function onSuccess( 22 | params: NetlifyPluginOptions 23 | ): Promise { 24 | console.log('Algolia Netlify plugin started'); 25 | 26 | // Debug 27 | // console.log(JSON.stringify(params, null, 2)); 28 | // console.log(JSON.stringify(process.env, null, 2)); 29 | 30 | const { utils, inputs, constants } = params; 31 | 32 | const isDev = process.env.ALGOLIA_DEV_ENV === 'true'; 33 | const isDebugMode = process.env.NETLIFY_BUILD_DEBUG === 'true'; 34 | 35 | if (isDev) loadDevEnvVariables(); 36 | 37 | const summary = createSummaryLogger(utils.status.show); 38 | 39 | const siteId = constants.SITE_ID; 40 | const isLocal = constants.IS_LOCAL; 41 | 42 | // HEAD is incorrect locally if you try to run `yarn netlify build` 43 | // before having pushed your first commit on this branch, it says `master` 44 | const branch = process.env.HEAD!; 45 | const siteName = process.env.SITE_NAME; 46 | const deployPrimeUrl = process.env.DEPLOY_PRIME_URL; 47 | 48 | const isEnvDisabled = process.env.ALGOLIA_DISABLED === 'true'; 49 | const isInputDisabled = inputs.disabled; 50 | 51 | const algoliaBaseUrl = 52 | process.env.ALGOLIA_BASE_URL || 'https://crawler.algolia.com'; 53 | const algoliaApiKey = process.env.ALGOLIA_API_KEY; 54 | 55 | const branches = inputs.branches; 56 | const mainBranch = inputs.mainBranch; 57 | const pathPrefix = inputs.pathPrefix; 58 | const customDomain = inputs.customDomain; 59 | const renderJavaScript = inputs.renderJavaScript; 60 | const template = inputs.template; 61 | 62 | if (isEnvDisabled) { 63 | summary(`Disabled by the "ALGOLIA_DISABLED" environment variable`); 64 | return; 65 | } 66 | 67 | if (isInputDisabled) { 68 | summary(`Disabled by the "disabled" input in "netlify.toml"`); 69 | return; 70 | } 71 | 72 | if (isLocal && !isDev) { 73 | return utils.build.failPlugin( 74 | 'This plugin does not work locally, please deploy to a branch to test it.' 75 | ); 76 | } 77 | 78 | // Check internal constants 79 | if (!siteName) { 80 | return utils.build.failPlugin('Missing or invalid SITE_NAME'); 81 | } 82 | if (!deployPrimeUrl) { 83 | return utils.build.failPlugin('Missing DEPLOY_PRIME_URL'); 84 | } 85 | 86 | // Check required env vars 87 | const missingEnvMessage = (key: string): string => 88 | `Missing ${key}, please go to ${algoliaBaseUrl}/admin/netlify to complete your installation.`; 89 | if (!algoliaBaseUrl) { 90 | return utils.build.failPlugin(missingEnvMessage('ALGOLIA_BASE_URL')); 91 | } 92 | if (!isDev && !algoliaApiKey) { 93 | return utils.build.failPlugin(missingEnvMessage('ALGOLIA_API_KEY')); 94 | } 95 | 96 | // Check branch is whitelisted 97 | if (!branches.some((pattern) => starMatch(pattern, branch))) { 98 | summary(`"${branch}" is not part of configuration's "branches", skipping`); 99 | return; 100 | } 101 | 102 | const endpoint = `${algoliaBaseUrl}/api/1/netlify/crawl`; 103 | const apiKey = 104 | isDev && !algoliaApiKey ? 'not-necessary-in-dev' : algoliaApiKey; 105 | const creds = `${siteId}:${apiKey}`; 106 | 107 | let response: Response; 108 | try { 109 | const body = JSON.stringify({ 110 | branch, 111 | mainBranch, 112 | siteName, 113 | deployPrimeUrl, 114 | version, 115 | pathPrefix, 116 | customDomain, 117 | renderJavaScript, 118 | template, 119 | }); 120 | console.log('Sending request to crawl', endpoint); 121 | if (isDebugMode) { 122 | console.log(body); 123 | } 124 | 125 | response = await fetch(endpoint, { 126 | method: 'POST', 127 | headers: { 128 | Authorization: `Basic ${Buffer.from(creds).toString('base64')}`, 129 | 'Content-Type': 'application/json', 130 | }, 131 | body, 132 | }); 133 | 134 | if (!response.ok) { 135 | console.warn(response); 136 | throw new Error( 137 | `${response.statusText} ${JSON.stringify(response.json())}` 138 | ); 139 | } 140 | } catch (error) { 141 | console.error('Could not reach algolia', error); 142 | utils.build.failPlugin( 143 | `Could not reach Algolia's Crawler, got: ${(error as Error).message}` 144 | ); 145 | return; 146 | } 147 | const json = (await response.json()) as { crawlerId: string }; 148 | 149 | console.log(`API answered: ${response.status}`); 150 | 151 | const crawlerUrl = `${algoliaBaseUrl}/admin/user_configs/${json.crawlerId}`; 152 | summary(`Your crawler is running at: ${crawlerUrl}`); 153 | console.log('Done.'); 154 | } 155 | -------------------------------------------------------------------------------- /plugin/src/starMatch.test.ts: -------------------------------------------------------------------------------- 1 | import { starMatch } from './starMatch'; 2 | 3 | describe('starMatch', () => { 4 | it.each([ 5 | ['foo', 'foo'], 6 | ['fo*', 'foo'], 7 | ['*oo', 'foo'], 8 | ['*o*', 'foo'], 9 | ['foo*', 'foo'], 10 | ['*foo', 'foo'], 11 | ['*foo*', 'foo'], 12 | ['foo*bar', 'foobar'], 13 | ['fo*ar', 'foobar'], 14 | ])('"%s" should match "%s"', (pattern: string, value: string) => { 15 | expect(starMatch(pattern, value)).toBe(true); 16 | }); 17 | 18 | it.each([ 19 | ['foo', 'bar'], 20 | ['foo', 'foa'], 21 | ['*foo', 'foobar'], 22 | ['foo*', 'barfoo'], 23 | ])('"%s" should not match "%s"', (pattern: string, value: string) => { 24 | expect(starMatch(pattern, value)).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /plugin/src/starMatch.ts: -------------------------------------------------------------------------------- 1 | import { escapeRegExp } from './escapeRegExp'; 2 | 3 | export function starMatch(pattern: string, value: string): boolean { 4 | const regexp = pattern.split('*').map(escapeRegExp).join('.*'); 5 | return new RegExp(`^${regexp}$`).test(value); 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/types.ts: -------------------------------------------------------------------------------- 1 | export type PluginInputs = { 2 | disabled: boolean; 3 | branches: string[]; 4 | mainBranch?: string; 5 | pathPrefix?: string; 6 | customDomain?: string; 7 | renderJavaScript?: boolean; 8 | template?: string; 9 | }; 10 | -------------------------------------------------------------------------------- /plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "./dist" 6 | }, 7 | "include": [ 8 | "src" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | First test page 5 | 6 | 7 | 8 |

First test page

9 |

This is the contents of the first test page.

10 | 11 | 12 | -------------------------------------------------------------------------------- /public/2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Second test page 5 | 6 | 7 | 8 |

Second test page

9 |

This is the contents of the second test page.

10 | 11 | 12 | -------------------------------------------------------------------------------- /public/README.md: -------------------------------------------------------------------------------- 1 | # algoliasearch-netlify test website 2 | 3 | This website is a testing website. 4 | It holds some crawlable content and has a testing page for our frontend library. 5 | It is run by running `yarn dev` or more specifically `yarn dev:website` at the root of the project. 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/algoliasearch-netlify/97ec146098930e60b51ea973ebdff06c32dfef8c/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Algoliasearch Netlify Test Website 6 | 7 | 8 | 9 | 10 | 11 | 106 | 107 | 108 | 109 |

Algoliasearch Netlify Test Website

110 | 111 | 115 | 116 |

Script

117 |
118 |
119 | 120 |
121 |
122 |
Base URL
123 |
124 |
CSS
125 |
126 |
JS
127 |
128 |
Code
129 |
130 |
131 |
Test before
132 |
Error
133 |

134 |     
135 | 136 | 137 |
138 |
139 | 140 |

Test content

141 | 142 |

Some content to index.

143 |

Links to other pages: 144 |

152 |

153 | 154 | 156 | 357 | 358 | 359 | 360 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:js-lib", 4 | "algolia" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/dev_plugin.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "${BASH_SOURCE[0]}")" 6 | cd .. 7 | 8 | [ -f .env ] || (echo 'Missing .env' && exit 1) 9 | set -a 10 | source .env 11 | set +a 12 | 13 | restore_netlify_toml() { 14 | ./scripts/generate_netlify_toml.sh 15 | } 16 | 17 | ALGOLIA_DEV_ENV=true ./scripts/generate_netlify_toml.sh 18 | trap restore_netlify_toml EXIT 19 | 20 | ALGOLIA_DEV_ENV=true yarn netlify build 21 | -------------------------------------------------------------------------------- /scripts/generate_netlify_toml.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | cd "$(dirname "${BASH_SOURCE[0]}")" 5 | 6 | target="../netlify.toml" 7 | 8 | # Common content 9 | common=' 10 | # This file is generated by scripts/generate_netlify_toml.sh 11 | # DO NOT MODIFY, MODIFY THE GENERATING SCRIPT 12 | ' 13 | 14 | # Dev only 15 | dev_only=' 16 | [[plugins]] 17 | package = "./plugin/dist/index.js" 18 | [plugins.inputs] 19 | branches = ["*"] 20 | mainBranch = "master" 21 | 22 | [[plugins]] 23 | package = "@algolia/netlify-plugin-crawler" 24 | [plugins.inputs] 25 | disabled = true 26 | ' 27 | 28 | # Prod only 29 | prod_only=' 30 | [[plugins]] 31 | package = "@algolia/netlify-plugin-crawler" 32 | [plugins.inputs] 33 | branches = ["*"] 34 | ' 35 | 36 | echo "$common" >"$target" 37 | if [ "$ALGOLIA_DEV_ENV" = "true" ]; then 38 | echo "$dev_only" >>"$target" 39 | else 40 | echo "$prod_only" >>"$target" 41 | fi 42 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "${BASH_SOURCE[0]}")" 6 | cd .. 7 | 8 | # Check if the git directory is clean 9 | if [[ $(git diff --shortstat 2>/dev/null | tail -n1) != "" ]]; then 10 | echo "Your git directory is unclean" 11 | exit 1 12 | fi 13 | 14 | current=$(npx json -f package.json version) 15 | read -p "New version number (current is ${current}): " version 16 | export ALGOLIASEARCH_NETLIFY_VERSION=$version 17 | 18 | # Ask for confirmation 19 | echo 20 | read -p "[All] We'll release \"v$ALGOLIASEARCH_NETLIFY_VERSION\". Continue (yn)? " -n 1 -r 21 | echo 22 | [[ $REPLY =~ ^[Yy]$ ]] || exit -1 23 | 24 | # Preparing 25 | 26 | ## Building plugin 27 | echo 28 | echo 'Preparing plugin...' 29 | cd plugin/ 30 | npm version -s --no-git-tag-version $ALGOLIASEARCH_NETLIFY_VERSION 31 | yarn build 32 | cd .. 33 | 34 | ## Building front-end 35 | echo 36 | echo 'Preparing frontend...' 37 | cd frontend/ 38 | npm version -s --no-git-tag-version $ALGOLIASEARCH_NETLIFY_VERSION 39 | yarn build 40 | cd .. 41 | 42 | ## Git commit & tag 43 | echo 44 | echo 'Preparing changelog, creating commit & tag...' 45 | ### Initial (fake) commit & version tag 46 | npm version -s --no-git-tag-version $ALGOLIASEARCH_NETLIFY_VERSION 47 | git add package.json plugin/package.json frontend/package.json 48 | git commit -m "chore(release): $ALGOLIASEARCH_NETLIFY_VERSION" 49 | git tag -a "v$ALGOLIASEARCH_NETLIFY_VERSION" -m "$ALGOLIASEARCH_NETLIFY_VERSION" 50 | ### Changelog generation (we need the tag to exist) 51 | yarn changelog:generate 52 | git add CHANGELOG.md 53 | git commit --amend -m "chore(release): $ALGOLIASEARCH_NETLIFY_VERSION" 54 | git tag -a "v$ALGOLIASEARCH_NETLIFY_VERSION" -m "$ALGOLIASEARCH_NETLIFY_VERSION" -f 55 | 56 | # Releasing 57 | 58 | echo 59 | echo 'Everything built, ready for release.' 60 | 61 | ## 2FA is mandatory on npm for all Algolia employees 62 | echo 63 | echo "[npm] One time password: " 64 | read OTP 65 | [[ $OTP =~ [0-9]{6} ]] || exit -1 66 | 67 | ## Release plugin 68 | echo 69 | echo "Publishing plugin on npm..." 70 | cd plugin/ 71 | npm publish --otp $OTP 72 | cd .. 73 | 74 | echo 75 | echo "Publishing frontend on npm..." 76 | ## Release frontend 77 | cd frontend/ 78 | npm publish --otp $OTP 79 | cd .. 80 | 81 | ## Release git 82 | echo 83 | echo "Pushing on git remote..." 84 | git push 85 | git push --tags 86 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | /* Basic Options */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | "target": "es2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 7 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 8 | "lib": [ 9 | "es2020" 10 | ] /* Specify library files to be included in the compilation. */, 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | // "outDir": "./dist", /* Redirect output structure to the directory. */ 19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | /* Additional Checks */ 37 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 38 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 39 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 40 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | /* Experimental Options */ 58 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 59 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 60 | /* Advanced Options */ 61 | "skipLibCheck": true /* Skip type checking of declaration files. */, 62 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 63 | }, 64 | "include": [] 65 | } 66 | --------------------------------------------------------------------------------