diff --git a/front/.browserslistrc b/front/.browserslistrc new file mode 100644 index 000000000..2de50cd6e --- /dev/null +++ b/front/.browserslistrc @@ -0,0 +1,24 @@ +# Browser support targeting 95% coverage while enabling modern features +# This targets browsers that support ES2020+ and modern CSS features + +# Cover 95% of global usage +> 1% +last 2 versions +not dead + +# Exclude problematic browsers +not ie 11 +not op_mini all +not android <= 4.4 +not samsung <= 4 + +# Ensure modern browser support for ES2020+ features +chrome >= 87 +firefox >= 78 +safari >= 14 +edge >= 88 + +# Mobile browsers +ios >= 14 +and_chr >= 87 +and_ff >= 78 diff --git a/front/package.json b/front/package.json index d45378956..b0a7b8c34 100644 --- a/front/package.json +++ b/front/package.json @@ -100,6 +100,7 @@ "@vue/eslint-config-typescript": "12.0.0", "@vue/test-utils": "2.4.1", "@vue/tsconfig": "0.6.0", + "autoprefixer": "10.4.21", "cypress": "13.6.4", "eslint": "8.57.0", "eslint-config-standard": "17.1.0", @@ -115,6 +116,7 @@ "msw-auto-mock": "0.18.0", "openapi-typescript": "7.6.0", "patch-package": "8.0.0", + "postcss": "8.5.6", "rollup-plugin-visualizer": "5.9.0", "sass": "1.68.0", "sinon": "15.0.2", diff --git a/front/postcss.config.js b/front/postcss.config.js new file mode 100644 index 000000000..4e98956fe --- /dev/null +++ b/front/postcss.config.js @@ -0,0 +1,18 @@ +export default { + plugins: { + autoprefixer: { + overrideBrowserslist: [ + '> 1%', + 'last 2 versions', + 'not dead', + 'not ie 11', + 'not op_mini all', + 'chrome >= 87', + 'firefox >= 78', + 'safari >= 14', + 'edge >= 88', + 'ios >= 14' + ] + } + } +} diff --git a/front/src/utils/search.ts b/front/src/utils/search.ts index 19ea40ac6..2a6d4fcfd 100644 --- a/front/src/utils/search.ts +++ b/front/src/utils/search.ts @@ -3,19 +3,9 @@ export interface Token { value: string } -/** - * Normalizes a query string by splitting it into tokens while respecting quoted phrases. - * - * @param query - The input query string to normalize - * @returns Array of normalized tokens with quoted phrases preserved as single tokens - * - * @example - * ``` - * normalizeQuery('this is "my query" go') - * // Returns: ['this', 'is', 'my query', 'go'] - * ``` - */ export function normalizeQuery (query: string): string[] { + // given a string such as 'this is "my query" go', returns + // an array of tokens like this: ['this', 'is', 'my query', 'go'] if (!query) return [] const match = query.match(/\\?.|^$/g) @@ -42,46 +32,51 @@ const unquote = (str: string) => { return str } -const quoteIfNecessary = (str: string) => - str.includes(' ') - ? `"${str}"` - : str - -/** - * Parses an array of normalized query tokens into structured Token objects. - * - * @param normalizedQuery - Array of tokens as returned by normalizeQuery - * @returns Array of Token objects with field and value properties - * - * @example - * ``` - * parseTokens(['status:pending', 'hello']) - * // Returns: - * // [ - * // { field: 'status', value: 'pending' }, - * // { field: null, value: 'hello' } - * // ] - * ``` - */ -export const parseTokens = (normalizedQuery: string[]): Token[] => - normalizedQuery.map(t => { - // Split the token on ":" to separate field from value +export function parseTokens (normalizedQuery: string[]): Token[] { + // given an array of tokens as returned by normalizeQuery, + // returns a list of objects such as [ + // { + // field: 'status', + // value: 'pending' + // }, + // { + // field: null, + // value: 'hello' + // } + // ] + return normalizedQuery.map(t => { + // we split the token on ":" const parts = t.split(/:(.+)/) - return parts.length === 1 - ? { field: null, value: t } // No field specified - : { field: parts[0], value: unquote(parts[1]) } // Field:value format, remove quotes + if (parts.length === 1) { + // no field specified + return { field: null, value: t } + } + + // first item is the field, second is the value, possibly quoted + const [field, value] = parts + + // we remove surrounding quotes if any + return { field, value: unquote(value) } + }) +} + +export function compileTokens (tokens: Token[]) { + // given a list of tokens as returned by parseTokens, + // returns a string query + const parts = tokens.map(token => { + const { field } = token + let { value } = token + + if (value.includes(' ')) { + value = `"${value}"` + } + + if (field) { + return `${field}:${value}` + } + + return value }) -/** - * Compiles an array of Token objects back into a query string. - * - * @param tokens - Array of Token objects as returned by parseTokens - * @returns A formatted query string - */ -export const compileTokens = (tokens: Token[]) => - tokens.map(({field, value}) =>{ - field - ? `${field}:${quoteIfNecessary(value)}` - : quoteIfNecessary(value) - }) - .join(' ') + return parts.join(' ') +} diff --git a/front/tsconfig.json b/front/tsconfig.json index 32ade7f9a..573c0c88f 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -6,7 +6,7 @@ "noUnusedLocals": true, "noImplicitAny": true, "experimentalDecorators": true, - "lib": ["ES2022", "DOM", "DOM.Iterable"], + "lib": ["ES2023", "DOM", "DOM.Iterable"], "typeRoots": ["node_modules", "node_modules/@types"], "types": [ "vitest/globals", diff --git a/front/vite.config.ts b/front/vite.config.ts index b0a9b1920..ad8ee4f00 100644 --- a/front/vite.config.ts +++ b/front/vite.config.ts @@ -4,6 +4,7 @@ import { VitePWA } from 'vite-plugin-pwa' import { fileURLToPath, URL } from 'node:url' import UnoCSS from 'unocss/vite' + import manifest from './pwa-manifest.json' import VueI18n from '@intlify/unplugin-vue-i18n/vite' @@ -93,7 +94,14 @@ export default defineConfig(({ mode }) => ({ } } }, + esbuild: { + target: 'es2020', + supported: { + 'top-level-await': true + } + }, build: { + target: ['es2020', 'chrome87', 'firefox78', 'safari14', 'edge88'], sourcemap: true, // https://rollupjs.org/configuration-options/ rollupOptions: { diff --git a/front/yarn.lock b/front/yarn.lock index d3254ebab..23860895a 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -4434,6 +4434,18 @@ automation-events@^7.0.9: "@babel/runtime" "^7.27.6" tslib "^2.8.1" +autoprefixer@10.4.21: + version "10.4.21" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.21.tgz#77189468e7a8ad1d9a37fbc08efc9f480cf0a95d" + integrity sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ== + dependencies: + browserslist "^4.24.4" + caniuse-lite "^1.0.30001702" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.1.1" + postcss-value-parser "^4.2.0" + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -4647,7 +4659,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.24.0, browserslist@^4.25.1: +browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.1: version "4.25.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.1.tgz#ba9e8e6f298a1d86f829c9b975e07948967bb111" integrity sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw== @@ -4783,6 +4795,11 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +caniuse-lite@^1.0.30001702: + version "1.0.30001731" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz#277c07416ea4613ec564e5b0ffb47e7b60f32e2f" + integrity sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg== + caniuse-lite@^1.0.30001726: version "1.0.30001727" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" @@ -6411,6 +6428,11 @@ form-data@^4.0.0, form-data@~4.0.0: hasown "^2.0.2" mime-types "^2.1.12" +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + fs-extra@^11.2.0: version "11.3.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" @@ -8215,6 +8237,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + npm-run-path@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -8790,7 +8817,12 @@ postcss-selector-parser@^6.0.15: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss@^8.4.32, postcss@^8.4.38, postcss@^8.4.43, postcss@^8.4.48, postcss@^8.5.6: +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@8.5.6, postcss@^8.4.32, postcss@^8.4.38, postcss@^8.4.43, postcss@^8.4.48, postcss@^8.5.6: version "8.5.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==