1
0
Fork 0

chore(deps): dependencies 2024-11-04 (#2335)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
Pierre Millot 2024-11-04 18:15:51 +01:00 committed by GitHub
parent 6579023a2f
commit aa666deccc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 25314 additions and 20402 deletions

View file

@ -1,12 +1,6 @@
version: 2.1
aliases:
- &install_yarn_version
name: Install specific Yarn version
command: |
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.5
echo 'export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"' >> $BASH_ENV
- &restore_yarn_cache
name: Restore Yarn cache
keys:
@ -16,11 +10,12 @@ aliases:
name: Save Yarn cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- .yarn/cache
- .yarn/unplugged
- &run_yarn_install
name: Install dependencies
command: yarn install --frozen-lockfile
command: yarn install --immutable
- &restore_dist_folders
name: Restore dist folders
@ -59,7 +54,6 @@ jobs:
<<: *defaults
steps:
- checkout
- run: *install_yarn_version
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
@ -88,7 +82,6 @@ jobs:
steps:
- checkout
- *attach_workspace
- run: *install_yarn_version
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
@ -101,7 +94,6 @@ jobs:
steps:
- checkout
- *attach_workspace
- run: *install_yarn_version
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
@ -114,7 +106,6 @@ jobs:
steps:
- checkout
- *attach_workspace
- run: *install_yarn_version
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
@ -127,20 +118,18 @@ jobs:
steps:
- checkout
- *attach_workspace
- run: *install_yarn_version
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *restore_dist_folders
- run:
name: Unit tests
command: yarn run test --maxWorkers=4
command: yarn run test
release:
<<: *defaults
steps:
- checkout
- *attach_workspace
- run: *install_yarn_version
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache

View file

@ -1,5 +0,0 @@
node_modules
dist
/examples
build
.cache

View file

@ -1,94 +0,0 @@
/* eslint-disable import/no-commonjs */
module.exports = {
extends: ['algolia', 'algolia/jest', 'algolia/react', 'algolia/typescript'],
globals: {
__DEV__: false,
},
settings: {
react: {
pragma: 'React',
version: 'detect',
},
'import/resolver': {
node: {
extensions: ['.js', '.ts', '.tsx'],
},
},
},
rules: {
'no-param-reassign': 0,
'valid-jsdoc': 0,
'no-shadow': 0,
'prefer-template': 0,
'jest/no-disabled-tests': 0,
'react/prop-types': 0,
'react/no-unescaped-entities': 0,
'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
'import/extensions': 0,
'no-unused-expressions': 0,
complexity: 0,
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
'newlines-between': 'always',
groups: ['builtin', 'external', 'parent', 'sibling', 'index'],
pathGroups: [
{
pattern: '@/**/*',
group: 'parent',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['builtin'],
},
],
// TMP
'@typescript-eslint/explicit-function-return-type': ['off'],
'react/function-component-definition': ['off'],
'react/jsx-filename-extension': ['off'],
'jsdoc/check-examples': ['off'],
},
overrides: [
{
files: ['**/rollup.config.js', 'stories/**/*', '**/__tests__/**'],
rules: {
'import/no-extraneous-dependencies': 0,
},
},
{
files: ['cypress/**/*'],
plugins: ['cypress'],
env: {
'cypress/globals': true,
},
rules: {
'jest/expect-expect': 0,
'spaced-comment': 0,
'@typescript-eslint/triple-slash-reference': 0,
},
},
{
files: [
'scripts/**/*',
'*.config.js',
'packages/website/plugins/**/*',
'packages/website/sidebars.js',
],
rules: {
'import/no-commonjs': 0,
},
},
{
files: ['packages/website/**/*'],
rules: {
'import/no-unresolved': 0,
'import/no-extraneous-dependencies': 0,
},
},
],
};

5
.gitignore vendored
View file

@ -22,3 +22,8 @@ dist/
# Cypress Video and Screenshots output
cypress/screenshots/
cypress/videos/
**/.yarn/*
!**/.yarn/releases
!**/.yarn/plugins
!**/.yarn/patches

View file

@ -7,7 +7,6 @@
"stylelint-config-standard",
"stylelint-config-sass-guidelines",
"stylelint-order",
"stylelint-a11y/recommended",
"stylelint-prettier/recommended"
],
"rules": {
@ -33,4 +32,4 @@
}
]
}
}
}

934
.yarn/releases/yarn-4.5.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

5
.yarnrc.yml Normal file
View file

@ -0,0 +1,5 @@
nodeLinker: node-modules
enableGlobalCache: false
yarnPath: .yarn/releases/yarn-4.5.1.cjs

View file

@ -1,40 +0,0 @@
module.exports = (api) => {
const isTest = api.env('test');
const modules = isTest ? 'commonjs' : false;
const targets = {};
if (isTest) {
targets.node = true;
} else {
targets.browsers = ['last 2 versions', 'ie >= 9'];
}
return {
presets: [
'@babel/preset-typescript',
[
'@babel/preset-env',
{
modules,
targets,
},
],
],
plugins: clean([
'@babel/plugin-transform-react-jsx',
!isTest && [
'inline-replace-variables',
{
__DEV__: {
type: 'node',
replacement: "process.env.NODE_ENV === 'development'",
},
},
],
]),
};
};
function clean(config) {
return config.filter(Boolean);
}

View file

@ -10,7 +10,7 @@
},
{
"path": "packages/docsearch-js/dist/umd/index.js",
"maxSize": "35 kB"
"maxSize": "35.05 kB"
}
]
}

View file

@ -32,7 +32,7 @@ describe('Start', () => {
});
it('Open modal with forward slash key shortcut', () => {
cy.get('body').type('/');
cy.get('body').wait(1000).type('/');
cy.modalIsVisibleAndFocused();
});
});
@ -97,7 +97,7 @@ describe('Search', () => {
const currentURL = cy.url();
cy.typeQueryMatching();
cy.get('.DocSearch-Hits #docsearch-item-1 > a').click({ force: true });
cy.get('.DocSearch-Hits #docsearch-hits0-item-1 > a').click({ force: true });
cy.on('url:changed', (newUrl) => {
expect(newUrl).not.equal(currentURL);
});
@ -114,38 +114,30 @@ describe('Recent and Favorites', () => {
cy.visit(Cypress.config().baseUrl!);
cy.openModal();
cy.typeQueryMatching();
cy.get('#docsearch-item-0 > a').click({ force: true }).wait(1000);
cy.get('#docsearch-hits0-item-0 > a').click({ force: true }).wait(1000);
cy.openModal();
cy.contains('Recent').should('be.visible');
});
it('Recent search is displayed after visiting a result', () => {
cy.get('#docsearch-item-0').should('be.visible');
cy.get('#docsearch-recentSearches-item-0').should('be.visible');
});
it('Recent search can be deleted', () => {
cy.get('#docsearch-item-0')
.find('[title="Remove this search from history"]')
.trigger('click');
cy.get('#docsearch-recentSearches-item-0').find('[title="Remove this search from history"]').trigger('click');
cy.contains('No recent searches').should('be.visible');
});
it('Recent search can be favorited', () => {
cy.get('#docsearch-item-0')
.find('[title="Save this search"]')
.trigger('click');
cy.get('#docsearch-recentSearches-item-0').find('[title="Save this search"]').trigger('click');
cy.contains('Favorite').should('be.visible');
cy.get('#docsearch-item-0').should('be.visible');
cy.get('#docsearch-favoriteSearches-item-0').should('be.visible');
});
it('Favorite can be deleted', () => {
cy.get('#docsearch-item-0')
.find('[title="Save this search"]')
.trigger('click');
cy.get('#docsearch-recentSearches-item-0').find('[title="Save this search"]').trigger('click');
cy.contains('Favorite').should('be.visible');
cy.get('#docsearch-item-0')
.find('[title="Remove this search from favorites"]')
.trigger('click');
cy.get('#docsearch-favoriteSearches-item-0').find('[title="Remove this search from favorites"]').trigger('click');
cy.contains('No recent searches').should('be.visible');
});
});

97
eslint.config.mjs Normal file
View file

@ -0,0 +1,97 @@
import algolia from 'eslint-config-algolia/flat/base.js';
import algoliaReact from 'eslint-config-algolia/flat/react.js';
import algoliaTypescript from 'eslint-config-algolia/flat/typescript.js';
import cypresss from 'eslint-plugin-cypress/flat'; // eslint-disable-line import/no-unresolved
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint'; // eslint-disable-line import/no-unresolved
export default [
...algolia,
...algoliaReact,
...algoliaTypescript,
{
ignores: ['**/node_modules/', '**/dist/', '**/build/', '.yarn/', '**/.docusaurus'],
},
{
plugins: {
'react-refresh': reactRefresh,
},
languageOptions: {
parserOptions: {
projectService: true,
},
},
settings: {
react: {
pragma: 'React',
version: 'detect',
},
'import/resolver': {
node: {
extensions: ['.js', '.ts', '.tsx'],
},
},
},
rules: {
'no-param-reassign': 0,
'valid-jsdoc': 0,
'no-shadow': 0,
'prefer-template': 0,
'react/prop-types': 0,
'react/no-unescaped-entities': 0,
'import/extensions': 0,
'no-unused-expressions': 0,
complexity: 0,
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
'newlines-between': 'always',
groups: ['builtin', 'external', 'parent', 'sibling', 'index'],
pathGroups: [
{
pattern: '@/**/*',
group: 'parent',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['builtin'],
},
],
// TMP
'react/function-component-definition': ['off'],
'react/jsx-filename-extension': ['off'],
'jsdoc/check-examples': ['off'],
},
},
{
files: ['**/*.js'],
},
tseslint.configs.disableTypeChecked,
{
files: ['cypress/**/*'],
plugins: {
cypresss,
},
rules: {
'@typescript-eslint/triple-slash-reference': 0,
},
},
{
files: ['packages/website/**/*'],
rules: {
'import/no-unresolved': 0,
'import/no-extraneous-dependencies': 0,
},
},
{
files: ['examples/demo/**/*'],
rules: {
'react/react-in-jsx-scope': 0,
},
},
];

View file

@ -12,7 +12,9 @@
},
"dependencies": {
"@docsearch/css": "3.6.3",
"@docsearch/react": "3.6.3"
"@docsearch/react": "3.6.3",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.3",

View file

@ -3,16 +3,11 @@ import { DocSearch } from '@docsearch/react';
import './App.css';
import '@docsearch/css/dist/style.css';
function App() {
function App(): JSX.Element {
return (
<div>
<h1>DocSearch v3 - React</h1>
<DocSearch
indexName="vuejs"
appId="ML0LEBN7FQ"
apiKey="21cf9df0734770a2448a9da64a700c22"
insights={true}
/>
<DocSearch indexName="vuejs" appId="ML0LEBN7FQ" apiKey="21cf9df0734770a2448a9da64a700c22" insights={true} />
</div>
);
}

View file

@ -1,10 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.js';
import App from './App.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
</StrictMode>,
);

View file

@ -3,10 +3,13 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
@ -14,7 +17,6 @@
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
@ -22,5 +24,8 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
"include": [
"src",
"vite.config.ts"
],
}

View file

@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
});

View file

@ -3,16 +3,18 @@
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
@ -20,5 +22,8 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
"include": [
"src",
"vite.config.ts"
]
}

View file

@ -1,16 +0,0 @@
module.exports = {
preset: 'ts-jest',
testMatch: ['<rootDir>/packages/docsearch-*/src/__tests__/*.test.(ts|tsx)'],
transformIgnorePatterns: [
'<rootDir>/node_modules/',
'<rootDir>/packages/docsearch-*/node_modules/',
],
transform: {
'^.+\\.(tsx|ts|js)$': 'babel-jest',
},
setupFilesAfterEnv: ['<rootDir>/scripts/jest/setupTests.ts'],
testEnvironment: 'jsdom',
globals: {
__DEV__: true,
},
};

View file

@ -1,30 +1,23 @@
{
"name": "@docsearch/monorepo",
"private": true,
"workspaces": {
"packages": [
"packages/*",
"examples/*"
],
"nohoist": [
"**/html-minifier-terser"
]
},
"workspaces": [
"packages/*",
"examples/*"
],
"scripts": {
"build:clean": "lerna run build:clean --scope @docsearch/*",
"build:esm": "lerna run build:esm --scope @docsearch/*",
"build:types": "lerna run build:types --scope @docsearch/*",
"build:umd": "lerna run build:umd --scope @docsearch/*",
"build": "lerna run build --scope @docsearch/*",
"build:clean": "lerna run build:clean",
"build:types": "lerna run build:types",
"build": "lerna run build",
"cy:clean": "rm -rf cypress/screenshots",
"cy:info": "cypress info",
"cy:run:chrome": "yarn run cy:run --browser chrome",
"cy:run:edge": "yarn run cy:run --browser edge",
"cy:run:firefox": "yarn run cy:run --browser firefox",
"cy:run": "start-server-and-test 'yarn website:test' http://localhost:3000 'cypress run --spec 'cypress/e2e/**/*' --headless'",
"cy:run": "start-server-and-test 'yarn website:test' http://localhost:3000 'cypress run --headless'",
"cy:verify": "cypress verify",
"lint:css": "stylelint **/src/**/*.css",
"lint": "eslint --ext .js,.ts,.tsx .",
"lint": "eslint .",
"playground:build": "yarn workspace @docsearch/react-example build",
"playground:start": "yarn workspace @docsearch/react-example dev",
"playground-js:start": "yarn workspace @docsearch/js-example dev",
@ -32,67 +25,64 @@
"start": "yarn run watch",
"test:size": "bundlesize",
"test:types": "tsc --noEmit",
"test": "jest",
"watch": "lerna run watch --parallel --scope @docsearch/*",
"test": "vitest",
"watch": "lerna run watch --parallel",
"website:build": "yarn workspace @docsearch/website build",
"website:start": "yarn workspace @docsearch/website start --host 0.0.0.0",
"website:test": "yarn workspace @docsearch/website start --no-open"
},
"devDependencies": {
"@babel/plugin-transform-react-jsx": "7.23.4",
"@babel/preset-env": "7.23.5",
"@babel/preset-typescript": "7.23.3",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-json": "4.1.0",
"@rollup/plugin-node-resolve": "11.2.1",
"@rollup/plugin-replace": "2.3.3",
"@testing-library/dom": "9.3.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "14.1.2",
"@types/jest": "^29.0.0",
"@babel/plugin-transform-react-jsx": "7.25.9",
"@babel/preset-env": "7.26.0",
"@babel/preset-typescript": "7.26.0",
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-node-resolve": "15.3.0",
"@rollup/plugin-replace": "6.0.1",
"@rollup/plugin-terser": "0.4.4",
"@stylistic/eslint-plugin": "2.10.1",
"@testing-library/dom": "10.4.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@typescript-eslint/eslint-plugin": "5.41.0",
"@typescript-eslint/parser": "5.41.0",
"babel-plugin-inline-replace-variables": "1.3.1",
"babel-plugin-module-resolver": "4.0.0",
"@typescript-eslint/eslint-plugin": "8.12.2",
"@typescript-eslint/parser": "8.12.2",
"babel-plugin-module-resolver": "5.0.2",
"bundlesize2": "0.0.31",
"cssnano": "4.1.10",
"cssnano": "7.0.6",
"cypress": "13.15.1",
"eslint": "8.55.0",
"eslint-config-algolia": "20.0.0",
"eslint-config-prettier": "8.10.0",
"eslint": "8.57.1",
"eslint-config-algolia": "23.2.1",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-cypress": "4.1.0",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-jest": "27.6.0",
"eslint-plugin-jsdoc": "39.4.0",
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.31.10",
"eslint-plugin-react-hooks": "4.6.0",
"jest": "29.6.1",
"jest-environment-jsdom": "29.6.1",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsdoc": "50.4.3",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "7.37.2",
"eslint-plugin-react-hooks": "5.0.0",
"eslint-plugin-react-refresh": "0.4.14",
"jsdom": "25.0.1",
"lerna": "8.1.9",
"postcss": "8.4.25",
"prettier": "2.7.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"rollup": "1.32.1",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-filesize": "9.1.2",
"rollup-plugin-terser": "7.0.2",
"postcss": "8.4.47",
"prettier": "3.3.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"rollup": "4.24.4",
"rollup-plugin-dts": "6.1.1",
"rollup-plugin-filesize": "10.0.0",
"shipjs": "0.26.4",
"start-server-and-test": "1.15.5",
"stylelint": "14.16.1",
"stylelint-a11y": "1.2.3",
"stylelint-config-sass-guidelines": "9.0.1",
"stylelint-config-standard": "28.0.0",
"stylelint-no-unsupported-browser-features": "5.0.4",
"stylelint-order": "5.0.0",
"stylelint-prettier": "3.0.0",
"ts-jest": "28.0.8",
"start-server-and-test": "2.0.8",
"stylelint": "16.10.0",
"stylelint-config-sass-guidelines": "12.1.0",
"stylelint-config-standard": "36.0.1",
"stylelint-no-unsupported-browser-features": "8.0.2",
"stylelint-order": "6.0.4",
"stylelint-prettier": "5.0.2",
"typescript": "5.6.3",
"typescript-eslint": "8.12.2",
"vitest": "2.1.4",
"watch": "1.0.2"
}
},
"packageManager": "yarn@4.5.1"
}

View file

@ -13,12 +13,8 @@ const pkg = require('./package.json');
const readFile = util.promisify(fs.readFile);
function getBundleBanner(_pkg) {
const lastCommitHash = execSync('git rev-parse --short HEAD')
.toString()
.trim();
const version = process.env.SHIPJS
? _pkg.version
: `${_pkg.version} (UNRELEASED ${lastCommitHash})`;
const lastCommitHash = execSync('git rev-parse --short HEAD').toString().trim();
const version = process.env.SHIPJS ? _pkg.version : `${_pkg.version} (UNRELEASED ${lastCommitHash})`;
const authors = '© Algolia, Inc. and contributors';
return `/*! ${_pkg.name} ${version} | MIT License | ${authors} | ${_pkg.homepage} */`;
@ -71,20 +67,14 @@ async function buildStyle() {
fs.writeFile(
'dist/style.css',
[
getBundleBanner(pkg),
[variablesOutput.css, buttonOutput.css, modalOutput.css].join(''),
].join('\n'),
() => true
[getBundleBanner(pkg), [variablesOutput.css, buttonOutput.css, modalOutput.css].join('')].join('\n'),
() => true,
);
fs.writeFile(
'dist/style.scss',
[
getBundleBanner(pkg),
[variablesOutput.css, buttonOutput.css, modalOutput.css].join(''),
].join('\n'),
() => true
[getBundleBanner(pkg), [variablesOutput.css, buttonOutput.css, modalOutput.css].join('')].join('\n'),
() => true,
);
}

View file

@ -1,6 +1,5 @@
module.exports = (api) => {
export default (api) => {
const isTest = api.env('test');
const modules = isTest ? 'commonjs' : false;
const targets = {};
if (isTest) {
@ -15,7 +14,6 @@ module.exports = (api) => {
[
'@babel/preset-env',
{
modules,
targets,
},
],

View file

@ -24,12 +24,15 @@
"build:clean": "rm -rf ./dist",
"build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/esm/types",
"build:clean-types": "rm -rf ./dist/esm/types",
"build": "yarn build:clean && yarn build:types && rollup --config && yarn build:clean-types",
"build": "yarn build:clean && yarn build:types && rollup --config --bundleConfigAsCjs && yarn build:clean-types",
"on:change": "yarn build",
"watch": "watch \"yarn on:change\" --ignoreDirectoryPattern \"/dist/\""
},
"dependencies": {
"@docsearch/react": "3.6.3",
"preact": "^10.0.0"
},
"devDependencies": {
"@rollup/plugin-replace": "6.0.1"
}
}

View file

@ -27,6 +27,7 @@ export default [
plugins: [
...plugins,
replace({
preventAssignment: true,
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],

View file

@ -2,10 +2,7 @@ import type { DocSearchProps as DocSearchComponentProps } from '@docsearch/react
import { DocSearch, version } from '@docsearch/react';
import React, { render } from 'preact/compat';
function getHTMLElement(
value: HTMLElement | string,
environment: DocSearchProps['environment'] = window
): HTMLElement {
function getHTMLElement(value: HTMLElement | string, environment: DocSearchProps['environment'] = window): HTMLElement {
if (typeof value === 'string') {
return environment.document.querySelector<HTMLElement>(value)!;
}
@ -18,18 +15,16 @@ interface DocSearchProps extends DocSearchComponentProps {
environment?: typeof window;
}
export function docsearch(props: DocSearchProps) {
export function docsearch(props: DocSearchProps): void {
render(
<DocSearch
{...props}
transformSearchClient={(searchClient) => {
searchClient.addAlgoliaAgent('docsearch.js', version);
return props.transformSearchClient
? props.transformSearchClient(searchClient)
: searchClient;
return props.transformSearchClient ? props.transformSearchClient(searchClient) : searchClient;
}}
/>,
getHTMLElement(props.container, props.environment)
getHTMLElement(props.container, props.environment),
);
}

View file

@ -1,6 +1,5 @@
module.exports = (api) => {
export default (api) => {
const isTest = api.env('test');
const modules = isTest ? 'commonjs' : 'auto';
const targets = {};
if (isTest) {
@ -15,7 +14,6 @@ module.exports = (api) => {
[
'@babel/preset-env',
{
modules,
targets,
},
],

View file

@ -27,15 +27,21 @@
"build:clean": "rm -rf ./dist",
"build:clean-types": "rm -rf ./dist/esm/types",
"build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/esm/types",
"build": "yarn build:clean && yarn build:types && rollup --config && yarn build:clean-types",
"build": "yarn build:clean && yarn build:types && rollup --config --bundleConfigAsCjs && yarn build:clean-types",
"on:change": "yarn build",
"watch": "watch \"yarn on:change\" --ignoreDirectoryPattern \"/dist/\""
},
"dependencies": {
"@algolia/autocomplete-core": "1.9.3",
"@algolia/autocomplete-core": "1.17.6",
"@algolia/autocomplete-preset-algolia": "1.17.6",
"@docsearch/css": "3.6.3",
"algoliasearch": "^5.11.0"
"algoliasearch": "^5.12.0"
},
"devDependencies": {
"@rollup/plugin-replace": "6.0.1",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.0.1",
"vitest": "2.1.4"
},
"peerDependencies": {
"@types/react": ">= 16.8.0 < 19.0.0",

View file

@ -26,6 +26,7 @@ export default [
plugins: [
...plugins,
replace({
preventAssignment: true,
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],

View file

@ -8,7 +8,7 @@ type AlgoliaLogoProps = {
translations?: AlgoliaLogoTranslations;
};
export function AlgoliaLogo({ translations = {} }: AlgoliaLogoProps) {
export function AlgoliaLogo({ translations = {} }: AlgoliaLogoProps): JSX.Element {
const { searchByText = 'Search by' } = translations;
return (
@ -28,21 +28,13 @@ export function AlgoliaLogo({ translations = {} }: AlgoliaLogoProps) {
viewBox="0 0 2196.2 500"
>
<defs>
<style>{`.cls-1,.cls-2{fill:#003dff;}.cls-2{fill-rule:evenodd;}`}</style>
<style>{'.cls-1,.cls-2{fill:#003dff;}.cls-2{fill-rule:evenodd;}'}</style>
</defs>
<path
className="cls-2"
d="M1070.38,275.3V5.91c0-3.63-3.24-6.39-6.82-5.83l-50.46,7.94c-2.87,.45-4.99,2.93-4.99,5.84l.17,273.22c0,12.92,0,92.7,95.97,95.49,3.33,.1,6.09-2.58,6.09-5.91v-40.78c0-2.96-2.19-5.51-5.12-5.84-34.85-4.01-34.85-47.57-34.85-54.72Z"
/>
<rect
className="cls-1"
x="1845.88"
y="104.73"
width="62.58"
height="277.9"
rx="5.9"
ry="5.9"
/>
<rect className="cls-1" x="1845.88" y="104.73" width="62.58" height="277.9" rx="5.9" ry="5.9" />
<path
className="cls-2"
d="M1851.78,71.38h50.77c3.26,0,5.9-2.64,5.9-5.9V5.9c0-3.62-3.24-6.39-6.82-5.83l-50.77,7.95c-2.87,.45-4.99,2.92-4.99,5.83v51.62c0,3.26,2.64,5.9,5.9,5.9Z"

View file

@ -1,7 +1,4 @@
import type {
AutocompleteState,
AutocompleteOptions,
} from '@algolia/autocomplete-core';
import type { AutocompleteState, AutocompleteOptions } from '@algolia/autocomplete-core';
import type { SearchClient } from 'algoliasearch';
import type { SearchQuery, LiteClient } from 'algoliasearch/lite';
import React from 'react';
@ -9,11 +6,7 @@ import { createPortal } from 'react-dom';
import { DocSearchButton } from './DocSearchButton';
import { DocSearchModal } from './DocSearchModal';
import type {
DocSearchHit,
InternalDocSearchHit,
StoredDocSearchHit,
} from './types';
import type { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit } from './types';
import { useDocSearchKeyboardEvents } from './useDocSearchKeyboardEvents';
import type { ButtonTranslations, ModalTranslations } from '.';
@ -31,16 +24,9 @@ export interface DocSearchProps {
searchParameters?: SearchQuery;
maxResultsPerGroup?: number;
transformItems?: (items: DocSearchHit[]) => DocSearchHit[];
hitComponent?: (props: {
hit: InternalDocSearchHit | StoredDocSearchHit;
children: React.ReactNode;
}) => JSX.Element;
resultsFooterComponent?: (props: {
state: AutocompleteState<InternalDocSearchHit>;
}) => JSX.Element | null;
transformSearchClient?: <T extends LiteClient | SearchClient>(
searchClient: T
) => T;
hitComponent?: (props: { hit: InternalDocSearchHit | StoredDocSearchHit; children: React.ReactNode }) => JSX.Element;
resultsFooterComponent?: (props: { state: AutocompleteState<InternalDocSearchHit> }) => JSX.Element | null;
transformSearchClient?: <T extends LiteClient | SearchClient>(searchClient: T) => T;
disableUserPersonalization?: boolean;
initialQuery?: string;
navigator?: AutocompleteOptions<InternalDocSearchHit>['navigator'];
@ -49,12 +35,10 @@ export interface DocSearchProps {
insights?: AutocompleteOptions<InternalDocSearchHit>['insights'];
}
export function DocSearch(props: DocSearchProps) {
export function DocSearch(props: DocSearchProps): JSX.Element {
const searchButtonRef = React.useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = React.useState(false);
const [initialQuery, setInitialQuery] = React.useState<string | undefined>(
props?.initialQuery || undefined
);
const [initialQuery, setInitialQuery] = React.useState<string | undefined>(props?.initialQuery || undefined);
const onOpen = React.useCallback(() => {
setIsOpen(true);
@ -70,7 +54,7 @@ export function DocSearch(props: DocSearchProps) {
setIsOpen(true);
setInitialQuery(event.key);
},
[setIsOpen, setInitialQuery]
[setIsOpen, setInitialQuery],
);
useDocSearchKeyboardEvents({
@ -83,11 +67,7 @@ export function DocSearch(props: DocSearchProps) {
return (
<>
<DocSearchButton
ref={searchButtonRef}
translations={props?.translations?.button}
onClick={onOpen}
/>
<DocSearchButton ref={searchButtonRef} translations={props?.translations?.button} onClick={onOpen} />
{isOpen &&
createPortal(
@ -98,7 +78,7 @@ export function DocSearch(props: DocSearchProps) {
translations={props?.translations?.modal}
onClose={onClose}
/>,
document.body
document.body,
)}
</>
);

View file

@ -15,65 +15,49 @@ export type DocSearchButtonProps = React.ComponentProps<'button'> & {
const ACTION_KEY_DEFAULT = 'Ctrl' as const;
const ACTION_KEY_APPLE = '⌘' as const;
function isAppleDevice() {
function isAppleDevice(): boolean {
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
}
export const DocSearchButton = React.forwardRef<
HTMLButtonElement,
DocSearchButtonProps
>(({ translations = {}, ...props }, ref) => {
const { buttonText = 'Search', buttonAriaLabel = 'Search' } = translations;
export const DocSearchButton = React.forwardRef<HTMLButtonElement, DocSearchButtonProps>(
({ translations = {}, ...props }, ref) => {
const { buttonText = 'Search', buttonAriaLabel = 'Search' } = translations;
const [key, setKey] = useState<
typeof ACTION_KEY_APPLE | typeof ACTION_KEY_DEFAULT | null
>(null);
const [key, setKey] = useState<typeof ACTION_KEY_APPLE | typeof ACTION_KEY_DEFAULT | null>(null);
useEffect(() => {
if (typeof navigator !== 'undefined') {
isAppleDevice() ? setKey(ACTION_KEY_APPLE) : setKey(ACTION_KEY_DEFAULT);
}
}, []);
useEffect(() => {
if (typeof navigator !== 'undefined') {
isAppleDevice() ? setKey(ACTION_KEY_APPLE) : setKey(ACTION_KEY_DEFAULT);
}
}, []);
return (
<button
type="button"
className="DocSearch DocSearch-Button"
aria-label={buttonAriaLabel}
{...props}
ref={ref}
>
<span className="DocSearch-Button-Container">
<SearchIcon />
<span className="DocSearch-Button-Placeholder">{buttonText}</span>
</span>
return (
<button type="button" className="DocSearch DocSearch-Button" aria-label={buttonAriaLabel} {...props} ref={ref}>
<span className="DocSearch-Button-Container">
<SearchIcon />
<span className="DocSearch-Button-Placeholder">{buttonText}</span>
</span>
<span className="DocSearch-Button-Keys">
{key !== null && (
<>
<DocSearchButtonKey
reactsToKey={
key === ACTION_KEY_DEFAULT ? ACTION_KEY_DEFAULT : 'Meta'
}
>
{key === ACTION_KEY_DEFAULT ? <ControlKeyIcon /> : key}
</DocSearchButtonKey>
<DocSearchButtonKey reactsToKey="k">K</DocSearchButtonKey>
</>
)}
</span>
</button>
);
});
<span className="DocSearch-Button-Keys">
{key !== null && (
<>
<DocSearchButtonKey reactsToKey={key === ACTION_KEY_DEFAULT ? ACTION_KEY_DEFAULT : 'Meta'}>
{key === ACTION_KEY_DEFAULT ? <ControlKeyIcon /> : key}
</DocSearchButtonKey>
<DocSearchButtonKey reactsToKey="k">K</DocSearchButtonKey>
</>
)}
</span>
</button>
);
},
);
type DocSearchButtonKeyProps = {
reactsToKey?: string;
};
function DocSearchButtonKey({
reactsToKey,
children,
}: React.PropsWithChildren<DocSearchButtonKeyProps>) {
function DocSearchButtonKey({ reactsToKey, children }: React.PropsWithChildren<DocSearchButtonKeyProps>): JSX.Element {
const [isKeyDown, setIsKeyDown] = useState(false);
useEffect(() => {
@ -81,13 +65,13 @@ function DocSearchButtonKey({
return undefined;
}
function handleKeyDown(e: KeyboardEvent) {
function handleKeyDown(e: KeyboardEvent): void {
if (e.key === reactsToKey) {
setIsKeyDown(true);
}
}
function handleKeyUp(e: KeyboardEvent) {
function handleKeyUp(e: KeyboardEvent): void {
if (
e.key === reactsToKey ||
// keyup doesn't fire when Command is held down,
@ -102,20 +86,14 @@ function DocSearchButtonKey({
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);
return () => {
return (): void => {
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keyup', handleKeyUp);
};
}, [reactsToKey]);
return (
<kbd
className={
isKeyDown
? 'DocSearch-Button-Key DocSearch-Button-Key--pressed'
: 'DocSearch-Button-Key'
}
>
<kbd className={isKeyDown ? 'DocSearch-Button-Key DocSearch-Button-Key--pressed' : 'DocSearch-Button-Key'}>
{children}
</kbd>
);

View file

@ -1,7 +1,4 @@
import {
type AlgoliaInsightsHit,
createAutocomplete,
} from '@algolia/autocomplete-core';
import { type AlgoliaInsightsHit, createAutocomplete } from '@algolia/autocomplete-core';
import type { SearchResponse } from 'algoliasearch/lite';
import React from 'react';
@ -15,22 +12,11 @@ import { ScreenState } from './ScreenState';
import type { SearchBoxTranslations } from './SearchBox';
import { SearchBox } from './SearchBox';
import { createStoredSearches } from './stored-searches';
import type {
DocSearchHit,
DocSearchState,
InternalDocSearchHit,
StoredDocSearchHit,
} from './types';
import type { DocSearchHit, DocSearchState, InternalDocSearchHit, StoredDocSearchHit } from './types';
import { useSearchClient } from './useSearchClient';
import { useTouchEvents } from './useTouchEvents';
import { useTrapFocus } from './useTrapFocus';
import {
groupBy,
identity,
noop,
removeHighlightTags,
isModifierEvent,
} from './utils';
import { groupBy, identity, noop, removeHighlightTags, isModifierEvent } from './utils';
export type ModalTranslations = Partial<{
searchBox: SearchBoxTranslations;
@ -54,7 +40,7 @@ export function DocSearchModal({
onClose = noop,
transformItems = identity,
hitComponent = Hit,
resultsFooterComponent = () => null,
resultsFooterComponent = (): JSX.Element | null => null,
navigator,
initialScrollY = 0,
transformSearchClient = identity,
@ -63,15 +49,9 @@ export function DocSearchModal({
translations = {},
getMissingResultsUrl,
insights = false,
}: DocSearchModalProps) {
const {
footer: footerTranslations,
searchBox: searchBoxTranslations,
...screenStateTranslations
} = translations;
const [state, setState] = React.useState<
DocSearchState<InternalDocSearchHit>
>({
}: DocSearchModalProps): JSX.Element {
const { footer: footerTranslations, searchBox: searchBoxTranslations, ...screenStateTranslations } = translations;
const [state, setState] = React.useState<DocSearchState<InternalDocSearchHit>>({
query: '',
collections: [],
completion: null,
@ -88,20 +68,16 @@ export function DocSearchModal({
const inputRef = React.useRef<HTMLInputElement | null>(null);
const snippetLength = React.useRef<number>(10);
const initialQueryFromSelection = React.useRef(
typeof window !== 'undefined'
? window.getSelection()!.toString().slice(0, MAX_QUERY_SIZE)
: ''
).current;
const initialQuery = React.useRef(
initialQueryFromProp || initialQueryFromSelection
typeof window !== 'undefined' ? window.getSelection()!.toString().slice(0, MAX_QUERY_SIZE) : '',
).current;
const initialQuery = React.useRef(initialQueryFromProp || initialQueryFromSelection).current;
const searchClient = useSearchClient(appId, apiKey, transformSearchClient);
const favoriteSearches = React.useRef(
createStoredSearches<StoredDocSearchHit>({
key: `__DOCSEARCH_FAVORITE_SEARCHES__${indexName}`,
limit: 10,
})
}),
).current;
const recentSearches = React.useRef(
createStoredSearches<StoredDocSearchHit>({
@ -109,7 +85,7 @@ export function DocSearchModal({
// We display 7 recent searches and there's no favorites, but only
// 4 when there are favorites.
limit: favoriteSearches.getAll().length === 0 ? 7 : 4,
})
}),
).current;
const saveRecentSearch = React.useCallback(
@ -122,22 +98,16 @@ export function DocSearchModal({
const search = item.type === 'content' ? item.__docsearch_parent : item;
// We save the recent search only if it's not favorited.
if (
search &&
favoriteSearches
.getAll()
.findIndex((x) => x.objectID === search.objectID) === -1
) {
if (search && favoriteSearches.getAll().findIndex((x) => x.objectID === search.objectID) === -1) {
recentSearches.add(search);
}
},
[favoriteSearches, recentSearches, disableUserPersonalization]
[favoriteSearches, recentSearches, disableUserPersonalization],
);
const sendItemClickEvent = React.useCallback(
(item: InternalDocSearchHit) => {
if (!state.context.algoliaInsightsPlugin || !item.__autocomplete_id)
return;
if (!state.context.algoliaInsightsPlugin || !item.__autocomplete_id) return;
const insightsItem = item as AlgoliaInsightsHit;
@ -149,184 +119,164 @@ export function DocSearchModal({
queryID: insightsItem.__autocomplete_queryID,
};
state.context.algoliaInsightsPlugin.insights.clickedObjectIDsAfterSearch(
insightsClickParams
);
state.context.algoliaInsightsPlugin.insights.clickedObjectIDsAfterSearch(insightsClickParams);
},
[state.context.algoliaInsightsPlugin]
[state.context.algoliaInsightsPlugin],
);
const autocomplete = React.useMemo(
() =>
createAutocomplete<
InternalDocSearchHit,
React.FormEvent<HTMLFormElement>,
React.MouseEvent,
React.KeyboardEvent
>({
id: 'docsearch',
defaultActiveItemId: 0,
placeholder,
openOnFocus: true,
initialState: {
query: initialQuery,
context: {
searchSuggestions: [],
createAutocomplete<InternalDocSearchHit, React.FormEvent<HTMLFormElement>, React.MouseEvent, React.KeyboardEvent>(
{
id: 'docsearch',
defaultActiveItemId: 0,
placeholder,
openOnFocus: true,
initialState: {
query: initialQuery,
context: {
searchSuggestions: [],
},
},
},
insights,
navigator,
onStateChange(props) {
setState(props.state);
},
getSources({ query, state: sourcesState, setContext, setStatus }) {
if (!query) {
if (disableUserPersonalization) {
return [];
insights,
navigator,
onStateChange(props) {
setState(props.state);
},
getSources({ query, state: sourcesState, setContext, setStatus }) {
if (!query) {
if (disableUserPersonalization) {
return [];
}
return [
{
sourceId: 'recentSearches',
onSelect({ item, event }): void {
saveRecentSearch(item);
if (!isModifierEvent(event)) {
onClose();
}
},
getItemUrl({ item }): string {
return item.url;
},
getItems(): InternalDocSearchHit[] {
return recentSearches.getAll() as InternalDocSearchHit[];
},
},
{
sourceId: 'favoriteSearches',
onSelect({ item, event }): void {
saveRecentSearch(item);
if (!isModifierEvent(event)) {
onClose();
}
},
getItemUrl({ item }): string {
return item.url;
},
getItems(): InternalDocSearchHit[] {
return favoriteSearches.getAll() as InternalDocSearchHit[];
},
},
];
}
return [
{
sourceId: 'recentSearches',
onSelect({ item, event }) {
saveRecentSearch(item);
const insightsActive = Boolean(insights);
if (!isModifierEvent(event)) {
onClose();
}
},
getItemUrl({ item }) {
return item.url;
},
getItems() {
return recentSearches.getAll() as InternalDocSearchHit[];
},
},
{
sourceId: 'favoriteSearches',
onSelect({ item, event }) {
saveRecentSearch(item);
if (!isModifierEvent(event)) {
onClose();
}
},
getItemUrl({ item }) {
return item.url;
},
getItems() {
return favoriteSearches.getAll() as InternalDocSearchHit[];
},
},
];
}
const insightsActive = Boolean(insights);
return searchClient
.search<DocSearchHit>({
requests: [
{
query,
indexName,
attributesToRetrieve: [
'hierarchy.lvl0',
'hierarchy.lvl1',
'hierarchy.lvl2',
'hierarchy.lvl3',
'hierarchy.lvl4',
'hierarchy.lvl5',
'hierarchy.lvl6',
'content',
'type',
'url',
],
attributesToSnippet: [
`hierarchy.lvl1:${snippetLength.current}`,
`hierarchy.lvl2:${snippetLength.current}`,
`hierarchy.lvl3:${snippetLength.current}`,
`hierarchy.lvl4:${snippetLength.current}`,
`hierarchy.lvl5:${snippetLength.current}`,
`hierarchy.lvl6:${snippetLength.current}`,
`content:${snippetLength.current}`,
],
snippetEllipsisText: '…',
highlightPreTag: '<mark>',
highlightPostTag: '</mark>',
hitsPerPage: 20,
clickAnalytics: insightsActive,
...searchParameters,
},
],
})
.catch((error) => {
// The Algolia `RetryError` happens when all the servers have
// failed, meaning that there's no chance the response comes
// back. This is the right time to display an error.
// See https://github.com/algolia/algoliasearch-client-javascript/blob/2ffddf59bc765cd1b664ee0346b28f00229d6e12/packages/transporter/src/errors/createRetryError.ts#L5
if (error.name === 'RetryError') {
setStatus('error');
}
throw error;
})
.then(({ results }) => {
const firstResult = results[0] as SearchResponse<DocSearchHit>;
const { hits, nbHits } = firstResult;
const sources = groupBy<DocSearchHit>(
hits,
(hit) => removeHighlightTags(hit),
maxResultsPerGroup
);
// We store the `lvl0`s to display them as search suggestions
// in the "no results" screen.
if (
(sourcesState.context.searchSuggestions as any[]).length <
Object.keys(sources).length
) {
setContext({
searchSuggestions: Object.keys(sources),
});
}
setContext({ nbHits });
let insightsParams = {};
if (insightsActive) {
insightsParams = {
__autocomplete_indexName: indexName,
__autocomplete_queryID: firstResult.queryID,
__autocomplete_algoliaCredentials: {
appId,
apiKey,
return searchClient
.search<DocSearchHit>({
requests: [
{
query,
indexName,
attributesToRetrieve: [
'hierarchy.lvl0',
'hierarchy.lvl1',
'hierarchy.lvl2',
'hierarchy.lvl3',
'hierarchy.lvl4',
'hierarchy.lvl5',
'hierarchy.lvl6',
'content',
'type',
'url',
],
attributesToSnippet: [
`hierarchy.lvl1:${snippetLength.current}`,
`hierarchy.lvl2:${snippetLength.current}`,
`hierarchy.lvl3:${snippetLength.current}`,
`hierarchy.lvl4:${snippetLength.current}`,
`hierarchy.lvl5:${snippetLength.current}`,
`hierarchy.lvl6:${snippetLength.current}`,
`content:${snippetLength.current}`,
],
snippetEllipsisText: '…',
highlightPreTag: '<mark>',
highlightPostTag: '</mark>',
hitsPerPage: 20,
clickAnalytics: insightsActive,
...searchParameters,
},
};
}
],
})
.catch((error) => {
// The Algolia `RetryError` happens when all the servers have
// failed, meaning that there's no chance the response comes
// back. This is the right time to display an error.
// See https://github.com/algolia/algoliasearch-client-javascript/blob/2ffddf59bc765cd1b664ee0346b28f00229d6e12/packages/transporter/src/errors/createRetryError.ts#L5
if (error.name === 'RetryError') {
setStatus('error');
}
return Object.values<DocSearchHit[]>(sources).map(
(items, index) => {
throw error;
})
.then(({ results }) => {
const firstResult = results[0] as SearchResponse<DocSearchHit>;
const { hits, nbHits } = firstResult;
const sources = groupBy<DocSearchHit>(hits, (hit) => removeHighlightTags(hit), maxResultsPerGroup);
// We store the `lvl0`s to display them as search suggestions
// in the "no results" screen.
if ((sourcesState.context.searchSuggestions as any[]).length < Object.keys(sources).length) {
setContext({
searchSuggestions: Object.keys(sources),
});
}
setContext({ nbHits });
let insightsParams = {};
if (insightsActive) {
insightsParams = {
__autocomplete_indexName: indexName,
__autocomplete_queryID: firstResult.queryID,
__autocomplete_algoliaCredentials: {
appId,
apiKey,
},
};
}
return Object.values<DocSearchHit[]>(sources).map((items, index) => {
return {
sourceId: `hits${index}`,
onSelect({ item, event }) {
onSelect({ item, event }): void {
saveRecentSearch(item);
if (!isModifierEvent(event)) {
onClose();
}
},
getItemUrl({ item }) {
getItemUrl({ item }): string {
return item.url;
},
getItems() {
return Object.values(
groupBy(
items,
(item) => item.hierarchy.lvl1,
maxResultsPerGroup
)
)
getItems(): InternalDocSearchHit[] {
return Object.values(groupBy(items, (item) => item.hierarchy.lvl1, maxResultsPerGroup))
.map(transformItems)
.map((groupedHits) =>
groupedHits.map((item) => {
@ -334,9 +284,7 @@ export function DocSearchModal({
const potentialParent = groupedHits.find(
(siblingItem) =>
siblingItem.type === 'lvl1' &&
siblingItem.hierarchy.lvl1 ===
item.hierarchy.lvl1
siblingItem.type === 'lvl1' && siblingItem.hierarchy.lvl1 === item.hierarchy.lvl1,
) as InternalDocSearchHit | undefined;
if (item.type !== 'lvl1' && potentialParent) {
@ -348,16 +296,16 @@ export function DocSearchModal({
__docsearch_parent: parent,
...insightsParams,
};
})
}),
)
.flat();
},
};
}
);
});
});
});
},
},
}),
),
[
indexName,
searchParameters,
@ -375,7 +323,7 @@ export function DocSearchModal({
insights,
appId,
apiKey,
]
],
);
const { getEnvironmentProps, getRootProps, refresh } = autocomplete;
@ -391,7 +339,7 @@ export function DocSearchModal({
React.useEffect(() => {
document.body.classList.add('DocSearch--active');
return () => {
return (): void => {
document.body.classList.remove('DocSearch--active');
// IE11 doesn't support `scrollTo` so we check that the method exists
@ -435,7 +383,7 @@ export function DocSearchModal({
// because all mobile browsers don't compute their height the same way.
// See https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
React.useEffect(() => {
function setFullViewportHeight() {
function setFullViewportHeight(): void {
if (modalRef.current) {
const vh = window.innerHeight * 0.01;
modalRef.current.style.setProperty('--docsearch-vh', `${vh}px`);
@ -446,7 +394,7 @@ export function DocSearchModal({
window.addEventListener('resize', setFullViewportHeight);
return () => {
return (): void => {
window.removeEventListener('resize', setFullViewportHeight);
};
}, []);
@ -480,10 +428,7 @@ export function DocSearchModal({
state={state}
autoFocus={initialQuery.length === 0}
inputRef={inputRef}
isFromSelection={
Boolean(initialQuery) &&
initialQuery === initialQueryFromSelection
}
isFromSelection={Boolean(initialQuery) && initialQuery === initialQueryFromSelection}
translations={searchBoxTranslations}
onClose={onClose}
/>

View file

@ -11,11 +11,9 @@ type ErrorScreenProps = {
translations?: ErrorScreenTranslations;
};
export function ErrorScreen({ translations = {} }: ErrorScreenProps) {
const {
titleText = 'Unable to fetch results',
helpText = 'You might want to check your network connection.',
} = translations;
export function ErrorScreen({ translations = {} }: ErrorScreenProps): JSX.Element {
const { titleText = 'Unable to fetch results', helpText = 'You might want to check your network connection.' } =
translations;
return (
<div className="DocSearch-ErrorScreen">
<div className="DocSearch-Screen-Icon">

View file

@ -22,23 +22,17 @@ interface CommandIconProps {
ariaLabel: string;
}
function CommandIcon(props: CommandIconProps) {
function CommandIcon(props: CommandIconProps): JSX.Element {
return (
<svg width="15" height="15" aria-label={props.ariaLabel} role="img">
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.2"
>
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.2">
{props.children}
</g>
</svg>
);
}
export function Footer({ translations = {} }: FooterProps) {
export function Footer({ translations = {} }: FooterProps): JSX.Element {
const {
selectText = 'to select',
selectKeyAriaLabel = 'Enter key',

View file

@ -7,6 +7,6 @@ interface HitProps {
children: React.ReactNode;
}
export function Hit({ hit, children }: HitProps) {
export function Hit({ hit, children }: HitProps): JSX.Element {
return <a href={hit.url}>{children}</a>;
}

View file

@ -11,25 +11,18 @@ export type NoResultsScreenTranslations = Partial<{
reportMissingResultsLinkText: string;
}>;
type NoResultsScreenProps = Omit<
ScreenStateProps<InternalDocSearchHit>,
'translations'
> & {
type NoResultsScreenProps = Omit<ScreenStateProps<InternalDocSearchHit>, 'translations'> & {
translations?: NoResultsScreenTranslations;
};
export function NoResultsScreen({
translations = {},
...props
}: NoResultsScreenProps) {
export function NoResultsScreen({ translations = {}, ...props }: NoResultsScreenProps): JSX.Element {
const {
noResultsText = 'No results for',
suggestedQueryText = 'Try searching for',
reportMissingResultsText = 'Believe this query should return results?',
reportMissingResultsLinkText = 'Let us know.',
} = translations;
const searchSuggestions: string[] | undefined = props.state.context
.searchSuggestions as string[];
const searchSuggestions: string[] | undefined = props.state.context.searchSuggestions as string[];
return (
<div className="DocSearch-NoResults">
@ -62,7 +55,7 @@ export function NoResultsScreen({
</button>
</li>,
],
[]
[],
)}
</ul>
</div>
@ -71,11 +64,7 @@ export function NoResultsScreen({
{props.getMissingResultsUrl && (
<p className="DocSearch-Help">
{`${reportMissingResultsText} `}
<a
href={props.getMissingResultsUrl({ query: props.state.query })}
target="_blank"
rel="noopener noreferrer"
>
<a href={props.getMissingResultsUrl({ query: props.state.query })} target="_blank" rel="noopener noreferrer">
{reportMissingResultsLinkText}
</a>
</p>

View file

@ -1,8 +1,4 @@
import type {
AutocompleteApi,
AutocompleteState,
BaseItem,
} from '@algolia/autocomplete-core';
import type { AutocompleteApi, AutocompleteState, BaseItem } from '@algolia/autocomplete-core';
import React from 'react';
import type { DocSearchProps } from './DocSearch';
@ -10,12 +6,7 @@ import { Snippet } from './Snippet';
import type { InternalDocSearchHit, StoredDocSearchHit } from './types';
interface ResultsProps<TItem extends BaseItem>
extends AutocompleteApi<
TItem,
React.FormEvent,
React.MouseEvent,
React.KeyboardEvent
> {
extends AutocompleteApi<TItem, React.FormEvent, React.MouseEvent, React.KeyboardEvent> {
title: string;
collection: AutocompleteState<TItem>['collections'][0];
renderIcon: (props: { item: TItem; index: number }) => React.ReactNode;
@ -28,9 +19,7 @@ interface ResultsProps<TItem extends BaseItem>
hitComponent: DocSearchProps['hitComponent'];
}
export function Results<TItem extends StoredDocSearchHit>(
props: ResultsProps<TItem>
) {
export function Results<TItem extends StoredDocSearchHit>(props: ResultsProps<TItem>): JSX.Element | null {
if (!props.collection || props.collection.items.length === 0) {
return null;
}
@ -41,14 +30,7 @@ export function Results<TItem extends StoredDocSearchHit>(
<ul {...props.getListProps()}>
{props.collection.items.map((item, index) => {
return (
<Result
key={[props.title, item.objectID].join(':')}
item={item}
index={index}
{...props}
/>
);
return <Result key={[props.title, item.objectID].join(':')} item={item} index={index} {...props} />;
})}
</ul>
</section>
@ -69,18 +51,18 @@ function Result<TItem extends StoredDocSearchHit>({
onItemClick,
collection,
hitComponent,
}: ResultProps<TItem>) {
}: ResultProps<TItem>): JSX.Element {
const [isDeleting, setIsDeleting] = React.useState(false);
const [isFavoriting, setIsFavoriting] = React.useState(false);
const action = React.useRef<(() => void) | null>(null);
const Hit = hitComponent!;
function runDeleteTransition(cb: () => void) {
function runDeleteTransition(cb: () => void): void {
setIsDeleting(true);
action.current = cb;
}
function runFavoriteTransition(cb: () => void) {
function runFavoriteTransition(cb: () => void): void {
setIsFavoriting(true);
action.current = cb;
}
@ -89,8 +71,7 @@ function Result<TItem extends StoredDocSearchHit>({
<li
className={[
'DocSearch-Hit',
(item as unknown as InternalDocSearchHit).__docsearch_parent &&
'DocSearch-Hit--Child',
(item as unknown as InternalDocSearchHit).__docsearch_parent && 'DocSearch-Hit--Child',
isDeleting && 'DocSearch-Hit--deleting',
isFavoriting && 'DocSearch-Hit--favoriting',
]
@ -115,18 +96,8 @@ function Result<TItem extends StoredDocSearchHit>({
{item.hierarchy[item.type] && item.type === 'lvl1' && (
<div className="DocSearch-Hit-content-wrapper">
<Snippet
className="DocSearch-Hit-title"
hit={item}
attribute="hierarchy.lvl1"
/>
{item.content && (
<Snippet
className="DocSearch-Hit-path"
hit={item}
attribute="content"
/>
)}
<Snippet className="DocSearch-Hit-title" hit={item} attribute="hierarchy.lvl1" />
{item.content && <Snippet className="DocSearch-Hit-path" hit={item} attribute="content" />}
</div>
)}
@ -137,31 +108,15 @@ function Result<TItem extends StoredDocSearchHit>({
item.type === 'lvl5' ||
item.type === 'lvl6') && (
<div className="DocSearch-Hit-content-wrapper">
<Snippet
className="DocSearch-Hit-title"
hit={item}
attribute={`hierarchy.${item.type}`}
/>
<Snippet
className="DocSearch-Hit-path"
hit={item}
attribute="hierarchy.lvl1"
/>
<Snippet className="DocSearch-Hit-title" hit={item} attribute={`hierarchy.${item.type}`} />
<Snippet className="DocSearch-Hit-path" hit={item} attribute="hierarchy.lvl1" />
</div>
)}
{item.type === 'content' && (
<div className="DocSearch-Hit-content-wrapper">
<Snippet
className="DocSearch-Hit-title"
hit={item}
attribute="content"
/>
<Snippet
className="DocSearch-Hit-path"
hit={item}
attribute="hierarchy.lvl1"
/>
<Snippet className="DocSearch-Hit-title" hit={item} attribute="content" />
<Snippet className="DocSearch-Hit-path" hit={item} attribute="hierarchy.lvl1" />
</div>
)}

View file

@ -6,12 +6,9 @@ import type { ScreenStateProps } from './ScreenState';
import type { InternalDocSearchHit } from './types';
import { removeHighlightTags } from './utils';
type ResultsScreenProps = Omit<
ScreenStateProps<InternalDocSearchHit>,
'translations'
>;
type ResultsScreenProps = Omit<ScreenStateProps<InternalDocSearchHit>, 'translations'>;
export function ResultsScreen(props: ResultsScreenProps) {
export function ResultsScreen(props: ResultsScreenProps): JSX.Element {
return (
<div className="DocSearch-Dropdown-Container">
{props.state.collections.map((collection) => {
@ -38,8 +35,7 @@ export function ResultsScreen(props: ResultsScreenProps) {
strokeLinecap="round"
strokeLinejoin="round"
>
{item.__docsearch_parent !==
collection.items[index + 1]?.__docsearch_parent ? (
{item.__docsearch_parent !== collection.items[index + 1]?.__docsearch_parent ? (
<path d="M8 6v21M20 27H8.3" />
) : (
<path d="M8 6v42M20 27H8.3" />

View file

@ -1,8 +1,4 @@
import type {
AutocompleteApi,
AutocompleteState,
BaseItem,
} from '@algolia/autocomplete-core';
import type { AutocompleteApi, AutocompleteState, BaseItem } from '@algolia/autocomplete-core';
import React from 'react';
import type { DocSearchProps } from './DocSearch';
@ -23,19 +19,11 @@ export type ScreenStateTranslations = Partial<{
}>;
export interface ScreenStateProps<TItem extends BaseItem>
extends AutocompleteApi<
TItem,
React.FormEvent,
React.MouseEvent,
React.KeyboardEvent
> {
extends AutocompleteApi<TItem, React.FormEvent, React.MouseEvent, React.KeyboardEvent> {
state: AutocompleteState<TItem>;
recentSearches: StoredSearchPlugin<StoredDocSearchHit>;
favoriteSearches: StoredSearchPlugin<StoredDocSearchHit>;
onItemClick: (
item: InternalDocSearchHit,
event: KeyboardEvent | MouseEvent
) => void;
onItemClick: (item: InternalDocSearchHit, event: KeyboardEvent | MouseEvent) => void;
inputRef: React.MutableRefObject<HTMLInputElement | null>;
hitComponent: DocSearchProps['hitComponent'];
indexName: DocSearchProps['indexName'];
@ -51,27 +39,14 @@ export const ScreenState = React.memo(
return <ErrorScreen translations={translations?.errorScreen} />;
}
const hasCollections = props.state.collections.some(
(collection) => collection.items.length > 0
);
const hasCollections = props.state.collections.some((collection) => collection.items.length > 0);
if (!props.state.query) {
return (
<StartScreen
{...props}
hasCollections={hasCollections}
translations={translations?.startScreen}
/>
);
return <StartScreen {...props} hasCollections={hasCollections} translations={translations?.startScreen} />;
}
if (hasCollections === false) {
return (
<NoResultsScreen
{...props}
translations={translations?.noResultsScreen}
/>
);
return <NoResultsScreen {...props} translations={translations?.noResultsScreen} />;
}
return <ResultsScreen {...props} />;
@ -81,9 +56,6 @@ export const ScreenState = React.memo(
// avoid UI flashes:
// - Empty screen → Results screen
// - NoResults screen → NoResults screen with another query
return (
nextProps.state.status === 'loading' ||
nextProps.state.status === 'stalled'
);
}
return nextProps.state.status === 'loading' || nextProps.state.status === 'stalled';
},
);

View file

@ -1,7 +1,4 @@
import type {
AutocompleteApi,
AutocompleteState,
} from '@algolia/autocomplete-core';
import type { AutocompleteApi, AutocompleteState } from '@algolia/autocomplete-core';
import type { MutableRefObject } from 'react';
import React from 'react';
@ -20,12 +17,7 @@ export type SearchBoxTranslations = Partial<{
}>;
interface SearchBoxProps
extends AutocompleteApi<
InternalDocSearchHit,
React.FormEvent,
React.MouseEvent,
React.KeyboardEvent
> {
extends AutocompleteApi<InternalDocSearchHit, React.FormEvent, React.MouseEvent, React.KeyboardEvent> {
state: AutocompleteState<InternalDocSearchHit>;
autoFocus: boolean;
inputRef: MutableRefObject<HTMLInputElement | null>;
@ -34,7 +26,7 @@ interface SearchBoxProps
translations?: SearchBoxTranslations;
}
export function SearchBox({ translations = {}, ...props }: SearchBoxProps) {
export function SearchBox({ translations = {}, ...props }: SearchBoxProps): JSX.Element {
const {
resetButtonTitle = 'Clear the query',
resetButtonAriaLabel = 'Clear the query',
@ -69,9 +61,7 @@ export function SearchBox({ translations = {}, ...props }: SearchBoxProps) {
>
<label className="DocSearch-MagnifierLabel" {...props.getLabelProps()}>
<SearchIcon />
<span className="DocSearch-VisuallyHiddenForAccessibility">
{searchInputLabel}
</span>
<span className="DocSearch-VisuallyHiddenForAccessibility">{searchInputLabel}</span>
</label>
<div className="DocSearch-LoadingIndicator">
@ -99,12 +89,7 @@ export function SearchBox({ translations = {}, ...props }: SearchBoxProps) {
</button>
</form>
<button
className="DocSearch-Cancel"
type="reset"
aria-label={cancelButtonAriaLabel}
onClick={props.onClose}
>
<button className="DocSearch-Cancel" type="reset" aria-label={cancelButtonAriaLabel} onClick={props.onClose}>
{cancelButtonText}
</button>
</>

View file

@ -23,13 +23,11 @@ export function Snippet<TItem extends StoredDocSearchHit>({
attribute,
tagName = 'span',
...rest
}: SnippetProps<TItem>) {
}: SnippetProps<TItem>): JSX.Element {
return createElement(tagName, {
...rest,
dangerouslySetInnerHTML: {
__html:
getPropertyByPath(hit, `_snippetResult.${attribute}.value`) ||
getPropertyByPath(hit, attribute),
__html: getPropertyByPath(hit, `_snippetResult.${attribute}.value`) || getPropertyByPath(hit, attribute),
},
});
}

View file

@ -14,15 +14,12 @@ export type StartScreenTranslations = Partial<{
removeFavoriteSearchButtonTitle: string;
}>;
type StartScreenProps = Omit<
ScreenStateProps<InternalDocSearchHit>,
'translations'
> & {
type StartScreenProps = Omit<ScreenStateProps<InternalDocSearchHit>, 'translations'> & {
hasCollections: boolean;
translations?: StartScreenTranslations;
};
export function StartScreen({ translations = {}, ...props }: StartScreenProps) {
export function StartScreen({ translations = {}, ...props }: StartScreenProps): JSX.Element | null {
const {
recentSearchesTitle = 'Recent',
noRecentSearchesText = 'No recent searches',
@ -58,11 +55,7 @@ export function StartScreen({ translations = {}, ...props }: StartScreenProps) {
<RecentIcon />
</div>
)}
renderAction={({
item,
runFavoriteTransition,
runDeleteTransition,
}) => (
renderAction={({ item, runFavoriteTransition, runDeleteTransition }) => (
<>
<div className="DocSearch-Hit-action">
<button

View file

@ -1,15 +1,14 @@
import { render, act, fireEvent, screen } from '@testing-library/react';
import { render, act, fireEvent, screen, cleanup } from '@testing-library/react';
import React from 'react';
import { describe, it, expect, afterEach } from 'vitest';
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/vitest';
import { DocSearch as DocSearchComponent } from '../DocSearch';
import type { DocSearchProps } from '../DocSearch';
function DocSearch(props: Partial<DocSearchProps>) {
return (
<DocSearchComponent appId="woo" apiKey="foo" indexName="bar" {...props} />
);
function DocSearch(props: Partial<DocSearchProps>): JSX.Element {
return <DocSearchComponent appId="woo" apiKey="foo" indexName="bar" {...props} />;
}
// mock empty response
@ -36,6 +35,10 @@ function noResultSearch(_queries: any, _requestOptions?: any): Promise<any> {
describe('api', () => {
const docSearchSelector = '.DocSearch';
afterEach(() => {
cleanup();
});
it('renders with minimal parameters', () => {
render(<DocSearch />);
@ -52,15 +55,11 @@ describe('api', () => {
buttonAriaLabel: 'Recherche',
},
}}
/>
/>,
);
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
expect(
document.querySelector('.DocSearch-Button-Placeholder')?.innerHTML
).toBe('Recherche');
expect(
document.querySelector('.DocSearch-Button')?.getAttribute('aria-label')
).toBe('Recherche');
expect(document.querySelector('.DocSearch-Button-Placeholder')?.innerHTML).toBe('Recherche');
expect(document.querySelector('.DocSearch-Button')?.getAttribute('aria-label')).toBe('Recherche');
});
it('overrides the default DocSearchModal startScreen text', async () => {
@ -73,7 +72,7 @@ describe('api', () => {
},
},
}}
/>
/>,
);
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
@ -99,14 +98,13 @@ describe('api', () => {
modal: {
noResultsScreen: {
noResultsText: 'Pas de résultats pour',
reportMissingResultsText:
'Ouvrez une issue sur docsearch-configs',
reportMissingResultsText: 'Ouvrez une issue sur docsearch-configs',
reportMissingResultsLinkText: 'Lien du repo',
},
},
}}
getMissingResultsUrl={() => 'algolia.com'}
/>
/>,
);
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
@ -122,13 +120,11 @@ describe('api', () => {
});
expect(screen.getByText(/Pas de résultats pour/)).toBeInTheDocument();
expect(
screen.getByText(/Ouvrez une issue sur docsearch-configs/)
).toBeInTheDocument();
expect(screen.getByText(/Ouvrez une issue sur docsearch-configs/)).toBeInTheDocument();
expect(
screen.getByRole('link', {
name: 'Lien du repo',
})
}),
).toBeInTheDocument();
});
@ -146,31 +142,21 @@ describe('api', () => {
},
},
}}
/>
/>,
);
await act(async () => {
fireEvent.click(await screen.findByText('Search'));
});
const searchInputLabel = document.querySelector(
'.DocSearch-MagnifierLabel'
);
const searchInputLabel = document.querySelector('.DocSearch-MagnifierLabel');
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
expect(document.querySelector('.DocSearch-Cancel')?.innerHTML).toBe(
'Annuler'
);
expect(
document.querySelector('.DocSearch-Cancel')?.getAttribute('aria-label')
).toBe('Annuler');
expect(
document.querySelector('.DocSearch-Reset')?.getAttribute('title')
).toBe('Effacer');
expect(
document.querySelector('.DocSearch-Reset')?.getAttribute('aria-label')
).toBe('Effacer');
expect(document.querySelector('.DocSearch-Cancel')?.innerHTML).toBe('Annuler');
expect(document.querySelector('.DocSearch-Cancel')?.getAttribute('aria-label')).toBe('Annuler');
expect(document.querySelector('.DocSearch-Reset')?.getAttribute('title')).toBe('Effacer');
expect(document.querySelector('.DocSearch-Reset')?.getAttribute('aria-label')).toBe('Effacer');
expect(searchInputLabel?.textContent).toBe('Recherche');
});
@ -191,7 +177,7 @@ describe('api', () => {
},
},
}}
/>
/>,
);
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
@ -205,24 +191,14 @@ describe('api', () => {
expect(screen.getByText('Pour naviguer')).toBeInTheDocument();
expect(screen.getByText('Pour selectionner')).toBeInTheDocument();
expect(
document.querySelector(
'.DocSearch-Commands-Key > svg[aria-label="Touche d\'échappement"]'
)
document.querySelector('.DocSearch-Commands-Key > svg[aria-label="Touche d\'échappement"]'),
).toBeInTheDocument();
expect(
document.querySelector(
'.DocSearch-Commands-Key > svg[aria-label="Flèche vers le haut"]'
)
document.querySelector('.DocSearch-Commands-Key > svg[aria-label="Flèche vers le haut"]'),
).toBeInTheDocument();
expect(document.querySelector('.DocSearch-Commands-Key > svg[aria-label="Flèche le bas"]')).toBeInTheDocument();
expect(
document.querySelector(
'.DocSearch-Commands-Key > svg[aria-label="Flèche le bas"]'
)
).toBeInTheDocument();
expect(
document.querySelector(
'.DocSearch-Commands-Key > svg[aria-label="Touche d\'entrée"]'
)
document.querySelector('.DocSearch-Commands-Key > svg[aria-label="Touche d\'entrée"]'),
).toBeInTheDocument();
});
});
@ -237,7 +213,7 @@ describe('api', () => {
search: noResultSearch,
};
}}
/>
/>,
);
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
@ -253,9 +229,7 @@ describe('api', () => {
});
expect(screen.getByText(/No results for/)).toBeInTheDocument();
expect(
document.querySelector('.DocSearch-Help a')
).not.toBeInTheDocument();
expect(document.querySelector('.DocSearch-Help a')).not.toBeInTheDocument();
});
it('render the link to the repository', async () => {
@ -267,10 +241,8 @@ describe('api', () => {
search: noResultSearch,
};
}}
getMissingResultsUrl={({ query }) =>
`https://github.com/algolia/docsearch/issues/new?title=${query}`
}
/>
getMissingResultsUrl={({ query }) => `https://github.com/algolia/docsearch/issues/new?title=${query}`}
/>,
);
await act(async () => {
@ -286,9 +258,7 @@ describe('api', () => {
expect(screen.getByText(/No results for/)).toBeInTheDocument();
const link = document.querySelector('.DocSearch-Help a');
expect(link).toBeInTheDocument();
expect(link?.getAttribute('href')).toBe(
'https://github.com/algolia/docsearch/issues/new?title=q'
);
expect(link?.getAttribute('href')).toBe('https://github.com/algolia/docsearch/issues/new?title=q');
});
});
});

View file

@ -1,6 +1,6 @@
import React from 'react';
export function ControlKeyIcon() {
export function ControlKeyIcon(): JSX.Element {
return (
<svg width="15" height="15" className="DocSearch-Control-Key-Icon">
<path

View file

@ -1,6 +1,6 @@
import React from 'react';
export function ErrorIcon() {
export function ErrorIcon(): JSX.Element {
return (
<svg
width="40"

View file

@ -1,6 +1,6 @@
import React from 'react';
export function GoToExternal() {
export function GoToExternal(): JSX.Element {
return (
<svg width="20" height="20">
<path

View file

@ -1,6 +1,6 @@
import React from 'react';
export function LoadingIcon() {
export function LoadingIcon(): JSX.Element {
return (
<svg viewBox="0 0 38 38" stroke="currentColor" strokeOpacity=".5">
<g fill="none" fillRule="evenodd">

View file

@ -1,6 +1,6 @@
import React from 'react';
export function NoResultsIcon() {
export function NoResultsIcon(): JSX.Element {
return (
<svg
width="40"

View file

@ -1,15 +1,9 @@
import React from 'react';
export function RecentIcon() {
export function RecentIcon(): JSX.Element {
return (
<svg width="20" height="20" viewBox="0 0 20 20">
<g
stroke="currentColor"
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
>
<g stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round">
<path d="M3.18 6.6a8.23 8.23 0 1112.93 9.94h0a8.23 8.23 0 01-11.63 0" />
<path d="M6.44 7.25H2.55V3.36M10.45 6v5.6M10.45 11.6L13 13" />
</g>

View file

@ -1,6 +1,6 @@
import React from 'react';
export function ResetIcon() {
export function ResetIcon(): JSX.Element {
return (
<svg width="20" height="20" viewBox="0 0 20 20">
<path

View file

@ -1,14 +1,8 @@
import React from 'react';
export function SearchIcon() {
export function SearchIcon(): JSX.Element {
return (
<svg
width="20"
height="20"
className="DocSearch-Search-Icon"
viewBox="0 0 20 20"
aria-hidden="true"
>
<svg width="20" height="20" className="DocSearch-Search-Icon" viewBox="0 0 20 20" aria-hidden="true">
<path
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
stroke="currentColor"

View file

@ -1,20 +1,9 @@
import React from 'react';
export function SelectIcon() {
export function SelectIcon(): JSX.Element {
return (
<svg
className="DocSearch-Hit-Select-Icon"
width="20"
height="20"
viewBox="0 0 20 20"
>
<g
stroke="currentColor"
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
>
<svg className="DocSearch-Hit-Select-Icon" width="20" height="20" viewBox="0 0 20 20">
<g stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 3v4c0 2-2 4-4 4H2" />
<path d="M8 17l-6-6 6-6" />
</g>

View file

@ -14,7 +14,7 @@ const LvlIcon: React.FC = () => {
);
};
export function SourceIcon(props: { type: string }) {
export function SourceIcon(props: { type: string }): JSX.Element {
switch (props.type) {
case 'lvl1':
return <LvlIcon />;
@ -25,7 +25,7 @@ export function SourceIcon(props: { type: string }) {
}
}
function AnchorIcon() {
function AnchorIcon(): JSX.Element {
return (
<svg width="20" height="20" viewBox="0 0 20 20">
<path
@ -40,7 +40,7 @@ function AnchorIcon() {
);
}
function ContentIcon() {
function ContentIcon(): JSX.Element {
return (
<svg width="20" height="20" viewBox="0 0 20 20">
<path

View file

@ -1,6 +1,6 @@
import React from 'react';
export function StarIcon() {
export function StarIcon(): JSX.Element {
return (
<svg width="20" height="20" viewBox="0 0 20 20">
<path

View file

@ -1,6 +1,6 @@
import type { DocSearchHit, StoredDocSearchHit } from './types';
function isLocalStorageSupported() {
function isLocalStorageSupported(): boolean {
const key = '__TEST_KEY__';
try {
@ -8,23 +8,24 @@ function isLocalStorageSupported() {
localStorage.removeItem(key);
return true;
} catch (error) {
} catch {
return false;
}
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function createStorage<TItem>(key: string) {
if (isLocalStorageSupported() === false) {
return {
setItem() {},
getItem() {
setItem(): void {},
getItem(): TItem[] {
return [];
},
};
}
return {
setItem(item: TItem[]) {
setItem(item: TItem[]): void {
return window.localStorage.setItem(key, JSON.stringify(item));
},
getItem(): TItem[] {
@ -54,13 +55,10 @@ export function createStoredSearches<TItem extends StoredDocSearchHit>({
let items = storage.getItem().slice(0, limit);
return {
add(item: TItem) {
const { _highlightResult, _snippetResult, ...hit } =
item as unknown as DocSearchHit;
add(item: TItem): void {
const { _highlightResult, _snippetResult, ...hit } = item as unknown as DocSearchHit;
const isQueryAlreadySaved = items.findIndex(
(x) => x.objectID === hit.objectID
);
const isQueryAlreadySaved = items.findIndex((x) => x.objectID === hit.objectID);
if (isQueryAlreadySaved > -1) {
items.splice(isQueryAlreadySaved, 1);
@ -71,12 +69,12 @@ export function createStoredSearches<TItem extends StoredDocSearchHit>({
storage.setItem(items);
},
remove(item: TItem) {
remove(item: TItem): void {
items = items.filter((x) => x.objectID !== item.objectID);
storage.setItem(items);
},
getAll() {
getAll(): TItem[] {
return items;
},
};

View file

@ -1,12 +1,4 @@
type ContentType =
| 'content'
| 'lvl0'
| 'lvl1'
| 'lvl2'
| 'lvl3'
| 'lvl4'
| 'lvl5'
| 'lvl6';
type ContentType = 'content' | 'lvl0' | 'lvl1' | 'lvl2' | 'lvl3' | 'lvl4' | 'lvl5' | 'lvl6';
interface DocSearchHitAttributeHighlightResult {
value: string;

View file

@ -11,7 +11,6 @@ interface DocSearchContext extends AutocompleteContext {
};
}
export interface DocSearchState<TItem extends BaseItem>
extends AutocompleteState<TItem> {
export interface DocSearchState<TItem extends BaseItem> extends AutocompleteState<TItem> {
context: DocSearchContext;
}

View file

@ -1,6 +1,3 @@
import type { DocSearchHit } from './DocSearchHit';
export type StoredDocSearchHit = Omit<
DocSearchHit,
'_highlightResult' | '_snippetResult'
>;
export type StoredDocSearchHit = Omit<DocSearchHit, '_highlightResult' | '_snippetResult'>;

View file

@ -12,12 +12,7 @@ function isEditingContent(event: KeyboardEvent): boolean {
const element = event.target as HTMLElement;
const tagName = element.tagName;
return (
element.isContentEditable ||
tagName === 'INPUT' ||
tagName === 'SELECT' ||
tagName === 'TEXTAREA'
);
return element.isContentEditable || tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
}
export function useDocSearchKeyboardEvents({
@ -26,17 +21,16 @@ export function useDocSearchKeyboardEvents({
onClose,
onInput,
searchButtonRef,
}: UseDocSearchKeyboardEventsProps) {
}: UseDocSearchKeyboardEventsProps): void {
React.useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
function onKeyDown(event: KeyboardEvent): void {
if (
(event.code === 'Escape' && isOpen) ||
// The `Cmd+K` shortcut both opens and closes the modal.
// We need to check for `event.key` because it can be `undefined` with
// Chrome's autofill feature.
// See https://github.com/paperjs/paper.js/issues/1398
(event.key?.toLowerCase() === 'k' &&
(event.metaKey || event.ctrlKey)) ||
(event.key?.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) ||
// The `/` shortcut opens but doesn't close the modal because it's
// a character.
(!isEditingContent(event) && event.key === '/' && !isOpen)
@ -54,11 +48,7 @@ export function useDocSearchKeyboardEvents({
return;
}
if (
searchButtonRef &&
searchButtonRef.current === document.activeElement &&
onInput
) {
if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) {
if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) {
onInput(event);
}
@ -67,7 +57,7 @@ export function useDocSearchKeyboardEvents({
window.addEventListener('keydown', onKeyDown);
return () => {
return (): void => {
window.removeEventListener('keydown', onKeyDown);
};
}, [isOpen, onOpen, onClose, onInput, searchButtonRef]);

View file

@ -7,7 +7,7 @@ import { version } from './version';
export function useSearchClient(
appId: string,
apiKey: string,
transformSearchClient: (searchClient: LiteClient) => LiteClient
transformSearchClient: (searchClient: LiteClient) => LiteClient,
): LiteClient {
const searchClient = React.useMemo(() => {
const client = liteClient(appId, apiKey);
@ -18,10 +18,7 @@ export function useSearchClient(
// it would also be sent on a DocSearch.js integration.
// We therefore only add the `docsearch-react` user agent if `docsearch.js`
// is not present.
if (
/docsearch.js \(.*\)/.test(client.transporter.algoliaAgent.value) ===
false
) {
if (/docsearch.js \(.*\)/.test(client.transporter.algoliaAgent.value) === false) {
client.addAlgoliaAgent('docsearch-react', version);
}

View file

@ -13,7 +13,7 @@ export function useTouchEvents({
panelElement,
formElement,
inputElement,
}: UseTouchEventsProps) {
}: UseTouchEventsProps): void {
React.useEffect(() => {
if (!(panelElement && formElement && inputElement)) {
return undefined;
@ -28,7 +28,7 @@ export function useTouchEvents({
window.addEventListener('touchstart', onTouchStart);
window.addEventListener('touchmove', onTouchMove);
return () => {
return (): void => {
window.removeEventListener('touchstart', onTouchStart);
window.removeEventListener('touchmove', onTouchMove);
};

View file

@ -4,19 +4,19 @@ interface UseTrapFocusProps {
container: HTMLElement | null;
}
export function useTrapFocus({ container }: UseTrapFocusProps) {
export function useTrapFocus({ container }: UseTrapFocusProps): void {
React.useEffect(() => {
if (!container) {
return undefined;
}
const focusableElements = container.querySelectorAll<HTMLElement>(
'a[href]:not([disabled]), button:not([disabled]), input:not([disabled])'
'a[href]:not([disabled]), button:not([disabled]), input:not([disabled])',
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
function trapFocus(event: KeyboardEvent) {
function trapFocus(event: KeyboardEvent): void {
if (event.key !== 'Tab') {
return;
}
@ -34,7 +34,7 @@ export function useTrapFocus({ container }: UseTrapFocusProps) {
container.addEventListener('keydown', trapFocus);
return () => {
return (): void => {
container.removeEventListener('keydown', trapFocus);
};
}, [container]);

View file

@ -1,7 +1,7 @@
export function groupBy<TValue extends Record<string, unknown>>(
values: TValue[],
predicate: (value: TValue) => string,
maxResultsPerGroup?: number
maxResultsPerGroup?: number,
): Record<string, TValue[]> {
return values.reduce<Record<string, TValue[]>>((acc, item) => {
const key = predicate(item);

View file

@ -2,16 +2,8 @@
* Detect when an event is modified with a special key to let the browser
* trigger its default behavior.
*/
export function isModifierEvent<TEvent extends KeyboardEvent | MouseEvent>(
event: TEvent
): boolean {
export function isModifierEvent<TEvent extends KeyboardEvent | MouseEvent>(event: TEvent): boolean {
const isMiddleClick = (event as MouseEvent).button === 1;
return (
isMiddleClick ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey
);
return isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
}

View file

@ -3,9 +3,7 @@ import type { DocSearchHit, InternalDocSearchHit } from '../types';
const regexHighlightTags = /(<mark>|<\/mark>)/g;
const regexHasHighlightTags = RegExp(regexHighlightTags.source);
export function removeHighlightTags(
hit: DocSearchHit | InternalDocSearchHit
): string {
export function removeHighlightTags(hit: DocSearchHit | InternalDocSearchHit): string {
const internalDocSearchHit = hit as InternalDocSearchHit;
if (!internalDocSearchHit.__docsearch_parent && !hit._highlightResult) {
@ -20,7 +18,5 @@ export function removeHighlightTags(
return hit.hierarchy.lvl0;
}
return lvl0.value && regexHasHighlightTags.test(lvl0.value)
? lvl0.value.replace(regexHighlightTags, '')
: lvl0.value;
return lvl0.value && regexHasHighlightTags.test(lvl0.value) ? lvl0.value.replace(regexHighlightTags, '') : lvl0.value;
}

View file

@ -1,3 +1,3 @@
module.exports = {
export default {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View file

@ -1,178 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { themes } = require('prism-react-renderer');
// With JSDoc @type annotations, IDEs can provide config autocompletion
/** @type {import('@docusaurus/types').DocusaurusConfig} */
(
module.exports = {
title: 'DocSearch by Algolia',
tagline:
'The best search experience for docs, integrated in minutes, for free.',
url: 'https://docsearch.algolia.com',
baseUrl: '/',
favicon: 'img/favicon.ico',
organizationName: 'Algolia',
projectName: 'DocSearch',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'throw',
presets: [
[
'@docusaurus/preset-classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
path: 'docs',
sidebarPath: 'sidebars.js',
editUrl:
'https://github.com/algolia/docsearch/edit/main/packages/website/',
versions: {
current: {
label: 'current',
},
},
lastVersion: 'current',
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
plugins: ['my-loaders', 'tailwind-loader'],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
algolia: {
appId: 'R2IYF7ETH7',
apiKey: '599cec31baffa4868cae4e79f180729b',
indexName: 'docsearch',
contextualSearch: true,
},
metadata: [
{
name: 'google-site-verification',
content: '23yIHmCD_xnJb_6e3s-w7M29Kydahk-d86ObMWOrvRg',
},
],
navbar: {
hideOnScroll: true,
logo: {
alt: 'DocSearch x Algolia',
src: 'img/docsearch-x-algolia-logo-light-mode.png',
srcDark: 'img/docsearch-x-algolia-logo-dark-mode.png',
className: 'docsearch-nav-logo',
},
items: [
// left
{
label: 'Docs',
to: 'docs/what-is-docsearch',
position: 'left',
},
{
label: 'Apply',
to: 'apply',
position: 'left',
},
// right
{
type: 'docsVersionDropdown',
position: 'right',
},
{
href: 'https://github.com/algolia/docsearch',
position: 'right',
className: 'header-github-link',
},
],
},
colorMode: {
defaultMode: 'light',
disableSwitch: false,
respectPrefersColorScheme: true,
},
announcementBar: {
content:
'⭐️ If you like DocSearch, give it a star on <a target="_blank" rel="noopener noreferrer" href="https://github.com/algolia/docsearch">GitHub</a>! ⭐️',
},
footer: {
links: [
{
title: 'Docs',
items: [
{
label: 'Getting Started',
to: 'docs/what-is-docsearch',
},
{
label: 'FAQ',
to: 'docs/docsearch-program',
},
{
label: 'DocSearch v3',
to: 'docs/docsearch-v3',
},
],
},
{
title: 'DocSearch',
items: [
{
label: 'Apply',
to: 'apply',
},
{
label: 'Issues',
to: 'https://github.com/algolia/docsearch/issues',
},
{
label: 'Privacy',
to: 'https://www.algolia.com/policies/privacy/',
},
],
},
{
title: 'Community',
items: [
{
label: 'Discord',
to: 'https://discord.com/invite/W7kYfh7FKQ',
},
],
},
{
title: 'Social',
items: [
{
label: 'GitHub',
to: 'https://github.com/algolia/docsearch',
},
{
label: 'Twitter',
to: 'https://twitter.com/docsearch_',
},
{
label: 'Algolia Blog',
to: 'https://algolia.com/blog/',
},
],
},
],
logo: {
alt: 'Algolia',
src: 'img/Algolia-logo-blue.svg',
srcDark: 'img/Algolia-logo-white.svg',
width: 200,
},
copyright: 'DocSearch 2015-now • Designed and built by Algolia',
},
image: 'img/og_image.png',
prism: {
theme: themes.github,
darkTheme: themes.dracula,
},
}),
}
);

View file

@ -0,0 +1,176 @@
import { themes } from 'prism-react-renderer';
import myLoaders from './plugins/my-loaders.mjs';
import tailwindLoader from './plugins/tailwind-loader.mjs';
// With JSDoc @type annotations, IDEs can provide config autocompletion
/** @type {import('@docusaurus/types').DocusaurusConfig} */
export default {
title: 'DocSearch by Algolia',
tagline: 'The best search experience for docs, integrated in minutes, for free.',
url: 'https://docsearch.algolia.com',
baseUrl: '/',
favicon: 'img/favicon.ico',
organizationName: 'Algolia',
projectName: 'DocSearch',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'throw',
presets: [
[
'@docusaurus/preset-classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
path: 'docs',
sidebarPath: 'sidebars.js',
editUrl: 'https://github.com/algolia/docsearch/edit/main/packages/website/',
versions: {
current: {
label: 'current',
},
},
lastVersion: 'current',
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
plugins: [myLoaders, tailwindLoader],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
algolia: {
appId: 'R2IYF7ETH7',
apiKey: '599cec31baffa4868cae4e79f180729b',
indexName: 'docsearch',
contextualSearch: true,
},
metadata: [
{
name: 'google-site-verification',
content: '23yIHmCD_xnJb_6e3s-w7M29Kydahk-d86ObMWOrvRg',
},
],
navbar: {
hideOnScroll: true,
logo: {
alt: 'DocSearch x Algolia',
src: 'img/docsearch-x-algolia-logo-light-mode.png',
srcDark: 'img/docsearch-x-algolia-logo-dark-mode.png',
className: 'docsearch-nav-logo',
},
items: [
// left
{
label: 'Docs',
to: 'docs/what-is-docsearch',
position: 'left',
},
{
label: 'Apply',
to: 'apply',
position: 'left',
},
// right
{
type: 'docsVersionDropdown',
position: 'right',
},
{
href: 'https://github.com/algolia/docsearch',
position: 'right',
className: 'header-github-link',
},
],
},
colorMode: {
defaultMode: 'light',
disableSwitch: false,
respectPrefersColorScheme: true,
},
announcementBar: {
content:
'⭐️ If you like DocSearch, give it a star on <a target="_blank" rel="noopener noreferrer" href="https://github.com/algolia/docsearch">GitHub</a>! ⭐️',
},
footer: {
links: [
{
title: 'Docs',
items: [
{
label: 'Getting Started',
to: 'docs/what-is-docsearch',
},
{
label: 'FAQ',
to: 'docs/docsearch-program',
},
{
label: 'DocSearch v3',
to: 'docs/docsearch-v3',
},
],
},
{
title: 'DocSearch',
items: [
{
label: 'Apply',
to: 'apply',
},
{
label: 'Issues',
to: 'https://github.com/algolia/docsearch/issues',
},
{
label: 'Privacy',
to: 'https://www.algolia.com/policies/privacy/',
},
],
},
{
title: 'Community',
items: [
{
label: 'Discord',
to: 'https://discord.com/invite/W7kYfh7FKQ',
},
],
},
{
title: 'Social',
items: [
{
label: 'GitHub',
to: 'https://github.com/algolia/docsearch',
},
{
label: 'Twitter',
to: 'https://twitter.com/docsearch_',
},
{
label: 'Algolia Blog',
to: 'https://algolia.com/blog/',
},
],
},
],
logo: {
alt: 'Algolia',
src: 'img/Algolia-logo-blue.svg',
srcDark: 'img/Algolia-logo-white.svg',
width: 200,
},
copyright: 'DocSearch 2015-now • Designed and built by Algolia',
},
image: 'img/og_image.png',
prism: {
theme: themes.github,
darkTheme: themes.dracula,
},
}),
};

View file

@ -16,26 +16,21 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@algolia/ui-library": "4.0.0-beta.71",
"@algolia/ui-library": "5.86.0",
"@docsearch/react": "3.6.3",
"@docusaurus/core": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@docusaurus/core": "3.6.0",
"@docusaurus/preset-classic": "3.6.0",
"@mdx-js/react": "^3.1.0",
"clsx": "^1.1.1",
"file-loader": "6.2.0",
"my-loaders": "file:plugins/my-loaders",
"postcss": "8.4.25",
"postcss-import": "15.0.0",
"postcss-preset-env": "7.8.2",
"postcss": "8.4.47",
"postcss-import": "16.1.0",
"postcss-preset-env": "10.0.9",
"prism-react-renderer": "2.4.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwind-loader": "file:plugins/tailwind-loader",
"url-loader": "4.1.1"
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"postcss-loader": "6.2.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat"
"tailwindcss": "3.4.14"
},
"browserslist": {
"production": [

View file

@ -1,4 +1,4 @@
module.exports = function () {
export default function () {
return {
name: 'loaders',
configureWebpack() {
@ -15,4 +15,4 @@ module.exports = function () {
};
},
};
};
}

View file

@ -1,19 +1,22 @@
/* eslint-disable @typescript-eslint/no-var-requires */
module.exports = function () {
import postcssImport from 'postcss-import';
import postcssPresetEnv from 'postcss-preset-env';
import tailwindcss from 'tailwindcss';
export default function () {
return {
name: 'postcss-tailwindcss-loader',
configurePostCss(postcssOptions) {
postcssOptions.plugins.push(
require('postcss-import'),
require('tailwindcss'),
require('postcss-preset-env')({
postcssImport,
tailwindcss,
postcssPresetEnv({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 4,
})
}),
);
return postcssOptions;
},
};
};
}

View file

@ -9,7 +9,7 @@
* Create as many sidebars as you want.
*/
module.exports = {
export default {
docs: [
{
type: 'category',

View file

@ -1,12 +1,4 @@
import {
Button,
Card,
Heading1,
InlineLink,
Input,
LabelText,
Text,
} from '@algolia/ui-library';
import { Button, Card, Heading1, InlineLink, Input, LabelText, Text } from '@algolia/ui-library';
import { useBaseUrlUtils } from '@docusaurus/useBaseUrl';
import React, { useState } from 'react';
@ -68,7 +60,7 @@ function ApplyForm() {
setState({
status: 'failed',
message: 'Unable to submit your request.',
})
}),
);
};
@ -79,10 +71,7 @@ function ApplyForm() {
<>
<Heading1 className="apply-text">URL Already Submitted!</Heading1>
<br />
<Text
className="uil-pv-8 uil-d-block apply-text"
aria-label="Request has already been processed"
>
<Text className="uil-pv-8 uil-d-block apply-text" aria-label="Request has already been processed">
{state.message}
</Text>
</>
@ -90,13 +79,9 @@ function ApplyForm() {
<>
<Heading1 className="apply-text">Thank You!</Heading1>
<br />
<Text
className="uil-pv-8 uil-d-block apply-text"
aria-label="Request will be processed"
>
{state.message} We'll get back to you at <strong>{email}</strong>{' '}
with the snippet you'll need to integrate into{' '}
<InlineLink href={url}>{url}</InlineLink>.
<Text className="uil-pv-8 uil-d-block apply-text" aria-label="Request will be processed">
{state.message} We'll get back to you at <strong>{email}</strong> with the snippet you'll need to
integrate into <InlineLink href={url}>{url}</InlineLink>.
</Text>
<Text aria-label="recommendations" className="apply-text">
@ -137,12 +122,7 @@ function ApplyForm() {
We'll scrape pages at this address and index the content on Algolia.
</Text>
<LabelText
tag="label"
htmlFor="email"
key="email"
className="apply-text"
>
<LabelText tag="label" htmlFor="email" key="email" className="apply-text">
Email
<Input
required={true}
@ -157,16 +137,11 @@ function ApplyForm() {
</LabelText>
<Text small={true} className="uil-pv-8 uil-d-block apply-text">
We'll send you the snippet you'll have to integrate into your website
and grant access to your Algolia application.
We'll send you the snippet you'll have to integrate into your website and grant access to your Algolia
application.
</Text>
<LabelText
tag="label"
htmlFor="repoURL"
key="repoURL"
className="apply-text"
>
<LabelText tag="label" htmlFor="repoURL" key="repoURL" className="apply-text">
Repository URL (Optional)
<Input
required={false}
@ -181,12 +156,7 @@ function ApplyForm() {
</LabelText>
<div className="uil-ph-32 uil-d-flex uil-fxd-column">
<LabelText
className="uil-pt-12 apply-text"
tag="label"
htmlFor="public"
key="public"
>
<LabelText className="uil-pt-12 apply-text" tag="label" htmlFor="public" key="public">
<input
required={true}
id="public"
@ -195,16 +165,10 @@ function ApplyForm() {
type="checkbox"
className="uil-mr-8"
/>
My website is a publicly available developer documentation or a
technical blog.
My website is a publicly available developer documentation or a technical blog.
</LabelText>
<LabelText
className="uil-pt-12 apply-text"
tag="label"
htmlFor="owner"
key="owner"
>
<LabelText className="uil-pt-12 apply-text" tag="label" htmlFor="owner" key="owner">
<input
required={true}
id="owner"
@ -215,20 +179,12 @@ function ApplyForm() {
type="checkbox"
className="uil-mr-8"
/>
I am the owner of the website, or otherwise have obtained and
continue to maintain any required consents necessary to use
DocSearch on the requested domain. And I have{' '}
<InlineLink href={withBaseUrl('docs/who-can-apply')}>
read the checklist.
</InlineLink>
I am the owner of the website, or otherwise have obtained and continue to maintain any required consents
necessary to use DocSearch on the requested domain. And I have{' '}
<InlineLink href={withBaseUrl('docs/who-can-apply')}>read the checklist.</InlineLink>
</LabelText>
<LabelText
className="uil-pt-12 apply-text"
tag="label"
htmlFor="opensource"
key="opensource"
>
<LabelText className="uil-pt-12 apply-text" tag="label" htmlFor="opensource" key="opensource">
<input
required={true}
id="opensource"
@ -238,10 +194,7 @@ function ApplyForm() {
className="uil-mr-8"
/>
I understand that this is a Free Service as described in Algolia's{' '}
<InlineLink href="https://www.algolia.com/policies/terms/">
Terms of Service
</InlineLink>{' '}
and Algolia's{' '}
<InlineLink href="https://www.algolia.com/policies/terms/">Terms of Service</InlineLink> and Algolia's{' '}
<InlineLink href="https://www.algolia.com/policies/free-services-terms/">
Special Free Services Terms
</InlineLink>
@ -262,8 +215,7 @@ function ApplyForm() {
<Text small={true} className="uil-ta-center">
<InlineLink href="https://www.algolia.com/policies/privacy/">
Refer to Algolia's Privacy Policy for more information on how we use
and protect your data
Refer to Algolia's Privacy Policy for more information on how we use and protect your data
</InlineLink>
</Text>
@ -271,10 +223,7 @@ function ApplyForm() {
<strong>
Only apply if you don't have a DocSearch application yet. <br />
For support requests, make sure to first{' '}
<InlineLink href="/docs/docsearch-program#support">
read our policy
</InlineLink>
.
<InlineLink href="/docs/docsearch-program#support">read our policy</InlineLink>.
</strong>{' '}
</Text>
</form>

View file

@ -32,19 +32,10 @@ function Home() {
}
background="curves"
cta={[
<Button
key="get-started"
href={withBaseUrl('docs/what-is-docsearch')}
>
<Button key="get-started" href={withBaseUrl('docs/what-is-docsearch')}>
Get started
</Button>,
<Button
key="apply"
href={withBaseUrl('apply')}
background="blue"
color="white"
className="apply-button"
>
<Button key="apply" href={withBaseUrl('apply')} background="blue" color="white" className="apply-button">
Apply
</Button>,
]}
@ -71,10 +62,7 @@ function Home() {
<div className="max-w-4xl mx-auto">
<dl className="rounded-lg shadow-xl lg:grid lg:grid-cols-3 showcase">
<div className="flex flex-col border-b p-6 text-center lg:border-0 showcase-border">
<dt
className="order-2 mt-2 text-lg leading-6 font-medium text-description"
id="item-1"
>
<dt className="order-2 mt-2 text-lg leading-6 font-medium text-description" id="item-1">
Free Service
</dt>
<dd
@ -88,17 +76,11 @@ function Home() {
<dt className="order-2 mt-2 text-lg leading-6 font-medium text-description">
Searches per month
</dt>
<dd className="order-1 text-5xl leading-none font-extrabold text-algolia">
+170M
</dd>
<dd className="order-1 text-5xl leading-none font-extrabold text-algolia">+170M</dd>
</div>
<div className="flex flex-col border-t p-6 text-center lg:border-0 lg:border-l showcase-border">
<dt className="order-2 mt-2 text-lg leading-6 font-medium text-description">
DocSearch Live
</dt>
<dd className="order-1 text-5xl leading-none font-extrabold text-algolia">
+7000
</dd>
<dt className="order-2 mt-2 text-lg leading-6 font-medium text-description">DocSearch Live</dt>
<dd className="order-1 text-5xl leading-none font-extrabold text-algolia">+7000</dd>
</div>
</dl>
</div>
@ -111,10 +93,7 @@ function Home() {
return a.name.localeCompare(b.name);
})
.map(({ name, href, image }) => (
<div
key={href}
className="col-span-1 flex justify-center py-2 px-2 text-center"
>
<div key={href} className="col-span-1 flex justify-center py-2 px-2 text-center">
<a
href={href}
rel="noreferrer"
@ -126,9 +105,7 @@ function Home() {
src={withBaseUrl(image)}
alt={`Discover DocSearch on the ${name} documentation`}
/>
<div className="text-description uppercase text-xs py-2 font-semibold">
{name}
</div>
<div className="text-description uppercase text-xs py-2 font-semibold">{name}</div>
</a>
</div>
))}
@ -144,8 +121,7 @@ function Home() {
Solve docs challenges with a search engine
</h3>
<p className="mt-4 max-w-3xl mx-auto text-center text-xl leading-7 text-description">
Docs are only helpful when your users can find answers easily.
Enter DocSearch.
Docs are only helpful when your users can find answers easily. Enter DocSearch.
</p>
</div>
@ -155,11 +131,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-algolia text-white">
<svg
viewBox="0 0 20 20"
fill="currentColor"
className="search w-6 h-6"
>
<svg viewBox="0 0 20 20" fill="currentColor" className="search w-6 h-6">
<path
fillRule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
@ -169,14 +141,11 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h4 className="text-lg leading-6 font-medium">
Made for Developer docs
</h4>
<h4 className="text-lg leading-6 font-medium">Made for Developer docs</h4>
<p className="mt-2 text-base leading-6 text-description">
Initially created to fulfill our own documentation
needs, DocSearch eventually became a community project
for open source docs. But, now it is available for
any/all kinds of developer documentation.
Initially created to fulfill our own documentation needs, DocSearch eventually became a
community project for open source docs. But, now it is available for any/all kinds of developer
documentation.
</p>
</div>
</div>
@ -185,12 +154,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-algolia text-white">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="user-group w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="user-group w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -201,14 +165,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h4 className="text-lg leading-6 font-medium">
Customizable and fast
</h4>
<h4 className="text-lg leading-6 font-medium">Customizable and fast</h4>
<p className="mt-2 text-base leading-6 text-description">
DocSearch understands how the user input fits into the
context of your project and instantly presents the most
relevant content with fewer interactions than any other
method.
DocSearch understands how the user input fits into the context of your project and instantly
presents the most relevant content with fewer interactions than any other method.
</p>
</div>
</div>
@ -217,12 +177,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-algolia text-white">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="device-mobile w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="device-mobile w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -233,13 +188,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h4 className="text-lg leading-6 font-medium">
Mobile design
</h4>
<h4 className="text-lg leading-6 font-medium">Mobile design</h4>
<p className="mt-2 text-base leading-6 text-description">
With a design very close to the native experience on
mobile, we leverage users acquaintance with the
interaction patterns of each OS.
With a design very close to the native experience on mobile, we leverage users acquaintance with
the interaction patterns of each OS.
</p>
</div>
</div>
@ -258,9 +210,8 @@ function Home() {
How it works
</h2>
<p className="mt-4 max-w-2xl text-xl leading-7 text-gray-500 lg:mx-auto">
We scrape your documentation or technical blog, configure the
Algolia application and send you the snippet you'll have to
integrate. It's that simple.
We scrape your documentation or technical blog, configure the Algolia application and send you the
snippet you'll have to integrate. It's that simple.
</p>
</div>
</div>
@ -279,9 +230,7 @@ function Home() {
/>
</div>
<div className="mt-10 lg:mt-0 p-4">
<h5 className="text-lg leading-6 font-medium text-gray-900">
1. Scraping
</h5>
<h5 className="text-lg leading-6 font-medium text-gray-900">1. Scraping</h5>
<p className="mt-2 text-base leading-6 text-gray-600">
We leverage the{' '}
<InlineLink
@ -304,12 +253,10 @@ function Home() {
/>
</div>
<div>
<h5 className="text-lg leading-6 font-medium text-gray-900">
2. Configuration
</h5>
<h5 className="text-lg leading-6 font-medium text-gray-900">2. Configuration</h5>
<p className="mt-2 text-base leading-6 text-gray-600">
You don't need to configure any settings or even have an
Algolia account. We take care of this for you!
You don't need to configure any settings or even have an Algolia account. We take care of this
for you!
</p>
</div>
</div>
@ -323,13 +270,10 @@ function Home() {
/>
</div>
<div>
<h5 className="text-lg leading-6 font-medium text-gray-900">
3. Implementation
</h5>
<h5 className="text-lg leading-6 font-medium text-gray-900">3. Implementation</h5>
<p className="mt-2 text-base leading-6 text-gray-600">
We'll send you a small snippet to integrate DocSearch to
your website and an invite to your fully configured
Algolia application.
We'll send you a small snippet to integrate DocSearch to your website and an invite to your
fully configured Algolia application.
</p>
</div>
</div>
@ -348,9 +292,8 @@ function Home() {
Overview of DocSearch
</h4>
<p className="mt-3 text-lg leading-7 text-description">
Search doesn't have to be painful when a user searches
through your docs, they'll get a frictionless modal dialog to
work in. And it's a Free Service!
Search doesn't have to be painful — when a user searches through your docs, they'll get a frictionless
modal dialog to work in. And it's a Free Service!
</p>
<ul className="mt-10">
@ -358,12 +301,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-algolia text-white">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="sparkles w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="sparkles w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -374,15 +312,11 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h5 className="text-lg leading-6 font-medium">
All featured search box
</h5>
<h5 className="text-lg leading-6 font-medium">All featured search box</h5>
<p className="mt-2 text-base leading-6 text-description">
The conversation start here: you want to have the
state-of-the-art search box to represent your voice.
DocSearch comes with our extensive experience in
making this search box respects all the best
practices.
The conversation start here: you want to have the state-of-the-art search box to represent
your voice. DocSearch comes with our extensive experience in making this search box respects
all the best practices.
</p>
</div>
</div>
@ -391,12 +325,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-algolia text-white">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="menu-alt2 w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="menu-alt2 w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -407,12 +336,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h5 className="text-lg leading-6 font-medium">
Hierarchical display
</h5>
<h5 className="text-lg leading-6 font-medium">Hierarchical display</h5>
<p className="mt-2 text-base leading-6 text-description">
DocSearch organizes the search results into chunks
that reflect how your documentation is structured.
DocSearch organizes the search results into chunks that reflect how your documentation is
structured.
</p>
</div>
</div>
@ -424,11 +351,7 @@ function Home() {
<img
className="relative mx-auto rounded-lg shadow-lg image-rendering-crisp"
src={withBaseUrl(
`img/assets/${
colorMode === 'dark'
? 'docsearch-shadow-dark'
: 'docsearch-shadow'
}.png`
`img/assets/${colorMode === 'dark' ? 'docsearch-shadow-dark' : 'docsearch-shadow'}.png`,
)}
alt="docsearch-modal"
/>
@ -459,14 +382,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h5 className="text-lg leading-6 font-medium">
Inputs and shortcuts
</h5>
<h5 className="text-lg leading-6 font-medium">Inputs and shortcuts</h5>
<p className="mt-2 text-base leading-6 text-description">
It looks almost similar to a search input but it's a
button. When you click/touch or use the keyboard
shortcut, it opens a modal dropdown and focuses the
search input.
It looks almost similar to a search input but it's a button. When you click/touch or use the
keyboard shortcut, it opens a modal dropdown and focuses the search input.
</p>
</div>
</div>
@ -491,15 +410,11 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h5 className="text-lg leading-6 font-medium">
Always sized and positioned correctly
</h5>
<h5 className="text-lg leading-6 font-medium">Always sized and positioned correctly</h5>
<p className="mt-2 text-base leading-6 text-description">
The modal experience leverages behavior of most
popular Integrated Developement Environements (IDEs)
or native experience on mobile. An opaque layer is
making sure we keep the context of the documentation
but remove all visual pollution.
The modal experience leverages behavior of most popular Integrated Developement
Environements (IDEs) or native experience on mobile. An opaque layer is making sure we keep
the context of the documentation but remove all visual pollution.
</p>
</div>
</div>
@ -535,12 +450,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-white text-algolia">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="chip w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="chip w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -551,13 +461,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h5 className="text-lg leading-6 font-medium text-white">
Highly efficient
</h5>
<h5 className="text-lg leading-6 font-medium text-white">Highly efficient</h5>
<p className="mt-2 text-base leading-6 text-gray-300">
Most of our search queries take less than 20ms, so
Algolia can run a new search on every keystroke and
instantly highlight relevant content to your users.
Most of our search queries take less than 20ms, so Algolia can run a new search on every
keystroke and instantly highlight relevant content to your users.
</p>
</div>
</div>
@ -566,12 +473,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-white text-algolia">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="chat w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="chat w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -582,12 +484,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h5 className="text-lg leading-6 font-medium text-white">
Search as you type
</h5>
<h5 className="text-lg leading-6 font-medium text-white">Search as you type</h5>
<p className="mt-2 text-base leading-6 text-gray-300">
Instantly magnify relevant content to your users from
the first keystroke, thanks to Algolia speed.
Instantly magnify relevant content to your users from the first keystroke, thanks to Algolia
speed.
</p>
</div>
</div>
@ -601,12 +501,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-white text-algolia">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="backspace w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="backspace w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -621,9 +516,8 @@ function Home() {
Detailed and straightforward analytics
</h5>
<p className="mt-2 text-base leading-6 text-gray-300">
Use our analytics to keep an eye on your search
performance and to make sure that your users are
finding what they're searching for.
Use our analytics to keep an eye on your search performance and to make sure that your users
are finding what they're searching for.
</p>
</div>
</div>
@ -632,12 +526,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-white text-algolia">
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="chart-bar w-6 h-6"
>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" className="chart-bar w-6 h-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -648,13 +537,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h5 className="text-lg leading-6 font-medium text-white">
Typo tolerant
</h5>
<h5 className="text-lg leading-6 font-medium text-white">Typo tolerant</h5>
<p className="mt-2 text-base leading-6 text-gray-300">
Algolia knows what your users mean to search for right
out-of-the-box, so they won't be spending time
correcting typos.
Algolia knows what your users mean to search for right out-of-the-box, so they won't be
spending time correcting typos.
</p>
</div>
</div>
@ -673,9 +559,8 @@ function Home() {
It's all about keyboards
</h3>
<p className="mt-4 max-w-3xl mx-auto text-center text-xl text-description lg:mx-auto">
Once users become familiar with a system they will become better
and faster at doing what they need to. DocSearch offers
accelerators to experts.
Once users become familiar with a system they will become better and faster at doing what they need to.
DocSearch offers accelerators to experts.
</p>
<div className="pt-12 items-center justify-center flex">
<img
@ -692,12 +577,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-algolia text-white">
<svg
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -708,13 +588,10 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h4 className="text-lg leading-6 font-medium">
Keyboard Shortcut & Navigation
</h4>
<h4 className="text-lg leading-6 font-medium">Keyboard Shortcut & Navigation</h4>
<p className="mt-2 text-base leading-6 text-description">
We aim at keeping the experience as smooth as your
interaction with an IDE, which explains the Ctrl+K (+K
on macOS) keyboard shortcut to open the search modal.
We aim at keeping the experience as smooth as your interaction with an IDE, which explains the
Ctrl+K (+K on macOS) keyboard shortcut to open the search modal.
</p>
</div>
</div>
@ -723,12 +600,7 @@ function Home() {
<div className="flex">
<div className="flex-shrink-0">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-algolia text-white">
<svg
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -739,15 +611,11 @@ function Home() {
</div>
</div>
<div className="ml-4">
<h4 className="text-lg leading-6 font-medium">
Accessibility
</h4>
<h4 className="text-lg leading-6 font-medium">Accessibility</h4>
<p className="mt-2 text-base leading-6 text-description">
We have released this version with our best efforts on
addressing accessibility issues and we are willing to
make further changes. We'd like to work closely with an
expert in accessibility. Please contact us if you are
interested.
We have released this version with our best efforts on addressing accessibility issues and we
are willing to make further changes. We'd like to work closely with an expert in accessibility.
Please contact us if you are interested.
</p>
</div>
</div>

View file

@ -12,11 +12,7 @@ function ApplyPage() {
description="The easiest way to add search to your documentation - Powered by Algolia"
>
<div className="uil-pb-24">
<Hero
id="hero-apply"
title={<DocSearchLogo width="100%" />}
background="curves"
/>
<Hero id="hero-apply" title={<DocSearchLogo width="100%" />} background="curves" />
<ApplyForm />
</div>
</Layout>

View file

@ -1,5 +1,5 @@
module.exports = {
purge: ['./src/**/*.html', './src/**/*.js', './src/**/*.tsx'],
export default {
content: ['./src/**/*.html', './src/**/*.js', './src/**/*.tsx'],
corePlugins: { preflight: false, container: false },
important: '#tailwind',
theme: {

View file

@ -1,11 +1,20 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:js-lib", "algolia"],
"baseBranches": ["chore/renovateBaseBranch"],
"extends": [
"config:js-lib",
"github>algolia/renovate-config-algolia"
],
"baseBranches": [
"chore/renovateBaseBranch"
],
"packageRules": [
{
"packagePatterns": ["^@types"],
"depTypeList": ["devDependencies"],
"packagePatterns": [
"^@types"
],
"depTypeList": [
"devDependencies"
],
"rangeStrategy": "replace"
}
],

View file

@ -2,12 +2,13 @@ import { babel } from '@rollup/plugin-babel';
import json from '@rollup/plugin-json';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import dts from 'rollup-plugin-dts';
import terser from '@rollup/plugin-terser';
import { dts } from 'rollup-plugin-dts';
import filesize from 'rollup-plugin-filesize';
import { terser } from 'rollup-plugin-terser';
export const plugins = [
replace({
preventAssignment: true,
__DEV__: JSON.stringify(process.env.NODE_ENV === 'development'),
}),
json(),

View file

@ -1,12 +1,8 @@
import { execSync } from 'child_process';
export function getBundleBanner(pkg) {
const lastCommitHash = execSync('git rev-parse --short HEAD')
.toString()
.trim();
const version = process.env.SHIPJS
? pkg.version
: `${pkg.version} (UNRELEASED ${lastCommitHash})`;
const lastCommitHash = execSync('git rev-parse --short HEAD').toString().trim();
const version = process.env.SHIPJS ? pkg.version : `${pkg.version} (UNRELEASED ${lastCommitHash})`;
const authors = '© Algolia, Inc. and contributors';
return `/*! ${pkg.name} ${version} | MIT License | ${authors} | ${pkg.homepage} */`;

View file

@ -1,31 +0,0 @@
type MatchMediaProps = Partial<{
matches: boolean;
media: string;
onchange: null;
addListener: jest.Mock;
removeListener: jest.Mock;
addEventListener: jest.Mock;
removeEventListener: jest.Mock;
dispatchEvent: jest.Mock;
}>;
const createMatchMedia = (props: MatchMediaProps) => {
return jest.fn((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
...props,
}));
};
Object.defineProperty(window, 'scrollTo', jest.fn());
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: createMatchMedia({ matches: true }),
});

30
scripts/setupTests.ts Normal file
View file

@ -0,0 +1,30 @@
import type { Mock } from 'vitest';
import { vi } from 'vitest';
type MatchMediaProps = Partial<{
matches: boolean;
media: string;
onchange: null;
addListener: Mock;
removeListener: Mock;
addEventListener: Mock;
removeEventListener: Mock;
dispatchEvent: Mock;
}>;
const createMatchMedia = (props: MatchMediaProps): Mock => {
return vi.fn((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
...props,
}));
};
vi.stubGlobal('scrollTo', vi.fn());
vi.stubGlobal('matchMedia', createMatchMedia({ matches: true }));

View file

@ -1,14 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const path = require('path');
import fs from 'fs';
import path from 'path';
const packages = [
'packages/docsearch-css',
'packages/docsearch-react',
'packages/docsearch-js',
];
const packages = ['packages/docsearch-css', 'packages/docsearch-react', 'packages/docsearch-js'];
module.exports = {
export default {
monorepo: {
mainVersionFile: 'lerna.json',
// We rely on Lerna to bump our dependencies.
@ -20,9 +15,7 @@ module.exports = {
},
versionUpdated({ exec, dir, version }) {
// Update package dependencies
exec(
`yarn lerna version ${version} --exact --no-git-tag-version --no-push --yes`
);
exec(`yarn lerna version ${version} --exact --no-git-tag-version --no-push --yes`);
// Ship.js reads JSON and writes with `fs.writeFileSync(JSON.stringify(json, null, 2))`
// which causes a lint error in the `lerna.json` file.
@ -30,7 +23,7 @@ module.exports = {
fs.writeFileSync(
path.resolve(dir, 'packages', 'docsearch-react', 'src', 'version.ts'),
`export const version = '${version}';\n`
`export const version = '${version}';\n`,
);
},
};

View file

@ -17,7 +17,6 @@
"exclude": [
"**/__fixtures__/**/*",
"**/__mocks__/**/*",
"**/__tests__/**/*",
"**/dist",
"**/node_modules",
"cypress",

8
vite.config.mts Normal file
View file

@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
setupFiles: ['./scripts/setupTests.ts'],
}
});

42336
yarn.lock

File diff suppressed because it is too large Load diff