feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
+7 -12
View File
@@ -14,11 +14,6 @@ on:
- 'docs/**'
- '*.md'
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true
permissions:
contents: read
@@ -29,11 +24,11 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v6
- uses: actions/setup-node@v4
with:
node-version: '10'
cache: 'npm'
@@ -71,11 +66,11 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v6
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'npm'
@@ -85,21 +80,21 @@ jobs:
- name: Install dependencies
run: |
npm install --ignore-scripts
- if: ${{ matrix.os == 'windows-latest' }}
run: npx playwright install winldd
- name: Run browser tests
run: |
npm run test:browser:${{ matrix.browser }}
test:
needs:
- test-regression-check-node10
permissions:
contents: write
pull-requests: write
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v6
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5
with:
license-check: true
lint: true
+1 -1
View File
@@ -21,4 +21,4 @@ jobs:
test:
permissions:
contents: read
uses: fastify/workflows/.github/workflows/plugins-ci-package-manager.yml@v6
uses: fastify/workflows/.github/workflows/plugins-ci-package-manager.yml@v5
+3 -1
View File
@@ -1,7 +1,9 @@
Copyright (c) 2011-2021, Gary Court until https://github.com/garycourt/uri-js/commit/a1acf730b4bba3f1097c9f52e7d9d3aba8cdcaae
Copyright (c) 2021-present The Fastify team <https://github.com/fastify/fastify#team>
Copyright (c) 2021-present The Fastify team
All rights reserved.
The Fastify team members are listed at https://github.com/fastify/fastify#team.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
+4 -13
View File
@@ -1,9 +1,13 @@
# fast-uri
<div align="center">
[![NPM version](https://img.shields.io/npm/v/fast-uri.svg?style=flat)](https://www.npmjs.com/package/fast-uri)
[![CI](https://github.com/fastify/fast-uri/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fast-uri/actions/workflows/ci.yml)
[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)
</div>
Dependency-free RFC 3986 URI toolbox.
## Usage
@@ -12,8 +16,6 @@ Dependency-free RFC 3986 URI toolbox.
All of the above functions can accept an additional options argument that is an object that can contain one or more of the following properties:
Malformed authorities and out-of-range ports are reported through the parsed component's `error` field. `normalize()` leaves malformed string inputs unchanged, and `equal()` returns `false` when either string input is malformed.
* `scheme` (string)
Indicates the scheme that the URI should be treated as, overriding the URI's normal scheme parsing behavior.
@@ -68,17 +70,6 @@ uri.resolve("uri://a/b/c/d?q", "../../g")
"uri://a/g"
```
### Normalize
```js
const uri = require('fast-uri')
uri.normalize('http://example.com/a%2Fb')
// Output
"http://example.com/a%2Fb"
```
Reserved path escapes such as `%2F` and `%2E` are preserved as path data during normalization and comparison.
### Equal
```js
+24 -90
View File
@@ -1,6 +1,6 @@
'use strict'
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require('./lib/utils')
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require('./lib/utils')
const { SCHEMES, getSchemeHandler } = require('./lib/schemes')
/**
@@ -11,7 +11,7 @@ const { SCHEMES, getSchemeHandler } = require('./lib/schemes')
*/
function normalize (uri, options) {
if (typeof uri === 'string') {
uri = /** @type {T} */ (normalizeString(uri, options))
uri = /** @type {T} */ (serialize(parse(uri, options), options))
} else if (typeof uri === 'object') {
uri = /** @type {T} */ (parse(serialize(uri, options), options))
}
@@ -106,10 +106,21 @@ function resolveComponent (base, relative, options, skipNormalization) {
* @returns {boolean}
*/
function equal (uriA, uriB, options) {
const normalizedA = normalizeComparableURI(uriA, options)
const normalizedB = normalizeComparableURI(uriB, options)
if (typeof uriA === 'string') {
uriA = unescape(uriA)
uriA = serialize(normalizeComponentEncoding(parse(uriA, options), true), { ...options, skipEscape: true })
} else if (typeof uriA === 'object') {
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true })
}
return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase()
if (typeof uriB === 'string') {
uriB = unescape(uriB)
uriB = serialize(normalizeComponentEncoding(parse(uriB, options), true), { ...options, skipEscape: true })
} else if (typeof uriB === 'object') {
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true })
}
return uriA.toLowerCase() === uriB.toLowerCase()
}
/**
@@ -145,13 +156,13 @@ function serialize (cmpts, opts) {
if (component.path !== undefined) {
if (!options.skipEscape) {
component.path = escapePreservingEscapes(component.path)
component.path = escape(component.path)
if (component.scheme !== undefined) {
component.path = component.path.split('%3A').join(':')
}
} else {
component.path = normalizePercentEncoding(component.path)
component.path = unescape(component.path)
}
}
@@ -202,29 +213,12 @@ function serialize (cmpts, opts) {
const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
/**
* @param {import('./types/index').URIComponent} parsed
* @param {RegExpMatchArray} matches
* @returns {string|undefined}
*/
function getParseError (parsed, matches) {
if (matches[2] !== undefined && parsed.path && parsed.path[0] !== '/') {
return 'URI path must start with "/" when authority is present.'
}
if (typeof parsed.port === 'number' && (parsed.port < 0 || parsed.port > 65535)) {
return 'URI port is malformed.'
}
return undefined
}
/**
* @param {string} uri
* @param {import('./types/index').Options} [opts]
* @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean }}
* @returns
*/
function parseWithStatus (uri, opts) {
function parse (uri, opts) {
const options = Object.assign({}, opts)
/** @type {import('./types/index').URIComponent} */
const parsed = {
@@ -237,8 +231,6 @@ function parseWithStatus (uri, opts) {
fragment: undefined
}
let malformedAuthorityOrPort = false
let isIP = false
if (options.reference === 'suffix') {
if (options.scheme) {
@@ -264,13 +256,6 @@ function parseWithStatus (uri, opts) {
if (isNaN(parsed.port)) {
parsed.port = matches[5]
}
const parseError = getParseError(parsed, matches)
if (parseError !== undefined) {
parsed.error = parsed.error || parseError
malformedAuthorityOrPort = true
}
if (parsed.host) {
const ipv4result = isIPv4(parsed.host)
if (ipv4result === false) {
@@ -319,18 +304,14 @@ function parseWithStatus (uri, opts) {
parsed.scheme = unescape(parsed.scheme)
}
if (parsed.host !== undefined) {
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP)
parsed.host = unescape(parsed.host)
}
}
if (parsed.path) {
parsed.path = normalizePathEncoding(parsed.path)
parsed.path = escape(unescape(parsed.path))
}
if (parsed.fragment) {
try {
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment))
} catch {
parsed.error = parsed.error || 'URI malformed'
}
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment))
}
}
@@ -341,54 +322,7 @@ function parseWithStatus (uri, opts) {
} else {
parsed.error = parsed.error || 'URI can not be parsed.'
}
return { parsed, malformedAuthorityOrPort }
}
/**
* @param {string} uri
* @param {import('./types/index').Options} [opts]
* @returns
*/
function parse (uri, opts) {
return parseWithStatus(uri, opts).parsed
}
/**
* @param {string} uri
* @param {import('./types/index').Options} [opts]
* @returns {string}
*/
function normalizeString (uri, opts) {
return normalizeStringWithStatus(uri, opts).normalized
}
/**
* @param {string} uri
* @param {import('./types/index').Options} [opts]
* @returns {{ normalized: string, malformedAuthorityOrPort: boolean }}
*/
function normalizeStringWithStatus (uri, opts) {
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts)
return {
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
malformedAuthorityOrPort
}
}
/**
* @param {import ('./types/index').URIComponent|string} uri
* @param {import('./types/index').Options} [opts]
* @returns {string|undefined}
*/
function normalizeComparableURI (uri, opts) {
if (typeof uri === 'string') {
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts)
return malformedAuthorityOrPort ? undefined : normalized
}
if (typeof uri === 'object') {
return serialize(uri, opts)
}
return parsed
}
const fastUri = {
+22 -129
View File
@@ -6,15 +6,6 @@ const isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\d
/** @type {(value: string) => boolean} */
const isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u)
/** @type {(value: string) => boolean} */
const isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu)
/** @type {(value: string) => boolean} */
const isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu)
/** @type {(value: string) => boolean} */
const isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu)
/**
* @param {Array<string>} input
* @returns {string}
@@ -273,126 +264,31 @@ function removeDotSegments (path) {
}
/**
* Re-escape RFC 3986 gen-delims that must not appear literally in the host.
* After the URI regex parses, these characters cannot be literal in the host
* field, so any that appear after decoding came from percent-encoding and
* must be restored to prevent authority structure changes.
*
* @param {string} host
* @param {boolean} isIP - true for IPv4/IPv6 hosts (skip colon re-escaping)
* @returns {string}
* @param {import('../types/index').URIComponent} component
* @param {boolean} esc
* @returns {import('../types/index').URIComponent}
*/
const HOST_DELIMS = { '@': '%40', '/': '%2F', '?': '%3F', '#': '%23', ':': '%3A' }
const HOST_DELIM_RE = /[@/?#:]/g
const HOST_DELIM_NO_COLON_RE = /[@/?#]/g
function reescapeHostDelimiters (host, isIP) {
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE
re.lastIndex = 0
return host.replace(re, (ch) => HOST_DELIMS[ch])
}
/**
* Normalizes percent escapes and optionally decodes only unreserved ASCII bytes.
* Reserved delimiters such as `%2F` and `%2E` stay escaped.
*
* @param {string} input
* @param {boolean} [decodeUnreserved=false]
* @returns {string}
*/
function normalizePercentEncoding (input, decodeUnreserved = false) {
if (input.indexOf('%') === -1) {
return input
function normalizeComponentEncoding (component, esc) {
const func = esc !== true ? escape : unescape
if (component.scheme !== undefined) {
component.scheme = func(component.scheme)
}
let output = ''
for (let i = 0; i < input.length; i++) {
if (input[i] === '%' && i + 2 < input.length) {
const hex = input.slice(i + 1, i + 3)
if (isHexPair(hex)) {
const normalizedHex = hex.toUpperCase()
const decoded = String.fromCharCode(parseInt(normalizedHex, 16))
if (decodeUnreserved && isUnreserved(decoded)) {
output += decoded
} else {
output += '%' + normalizedHex
}
i += 2
continue
}
}
output += input[i]
if (component.userinfo !== undefined) {
component.userinfo = func(component.userinfo)
}
return output
}
/**
* Normalizes path data without turning reserved escapes into live path syntax.
* Valid escapes are uppercased, raw unsafe characters are escaped, and only
* unreserved bytes that are not `.` are decoded.
*
* @param {string} input
* @returns {string}
*/
function normalizePathEncoding (input) {
let output = ''
for (let i = 0; i < input.length; i++) {
if (input[i] === '%' && i + 2 < input.length) {
const hex = input.slice(i + 1, i + 3)
if (isHexPair(hex)) {
const normalizedHex = hex.toUpperCase()
const decoded = String.fromCharCode(parseInt(normalizedHex, 16))
if (decoded !== '.' && isUnreserved(decoded)) {
output += decoded
} else {
output += '%' + normalizedHex
}
i += 2
continue
}
}
if (isPathCharacter(input[i])) {
output += input[i]
} else {
output += escape(input[i])
}
if (component.host !== undefined) {
component.host = func(component.host)
}
return output
}
/**
* Escapes a component while preserving existing valid percent escapes.
*
* @param {string} input
* @returns {string}
*/
function escapePreservingEscapes (input) {
let output = ''
for (let i = 0; i < input.length; i++) {
if (input[i] === '%' && i + 2 < input.length) {
const hex = input.slice(i + 1, i + 3)
if (isHexPair(hex)) {
output += '%' + hex.toUpperCase()
i += 2
continue
}
}
output += escape(input[i])
if (component.path !== undefined) {
component.path = func(component.path)
}
return output
if (component.query !== undefined) {
component.query = func(component.query)
}
if (component.fragment !== undefined) {
component.fragment = func(component.fragment)
}
return component
}
/**
@@ -414,7 +310,7 @@ function recomposeAuthority (component) {
if (ipV6res.isIPV6 === true) {
host = `[${ipV6res.escapedHost}]`
} else {
host = reescapeHostDelimiters(host, false)
host = component.host
}
}
uriTokens.push(host)
@@ -431,10 +327,7 @@ function recomposeAuthority (component) {
module.exports = {
nonSimpleDomain,
recomposeAuthority,
reescapeHostDelimiters,
normalizePercentEncoding,
normalizePathEncoding,
escapePreservingEscapes,
normalizeComponentEncoding,
removeDotSegments,
isIPv4,
isUUID,
+4 -3
View File
@@ -1,7 +1,7 @@
{
"name": "fast-uri",
"description": "Dependency-free RFC 3986 URI toolbox",
"version": "3.1.2",
"version": "3.1.0",
"main": "index.js",
"type": "commonjs",
"types": "types/index.d.ts",
@@ -58,11 +58,12 @@
"test:typescript": "tsd"
},
"devDependencies": {
"@fastify/pre-commit": "^2.1.0",
"ajv": "^8.16.0",
"eslint": "^9.17.0",
"neostandard": "^0.13.0",
"neostandard": "^0.12.0",
"playwright-test": "^14.1.12",
"tape": "^5.8.1",
"tsd": "^0.33.0"
"tsd": "^0.32.0"
}
}
-9
View File
@@ -106,12 +106,3 @@ test('WSS Equal', (t) => {
runTest(t, suite)
t.end()
})
test('URI Equals tolerates malformed fragments', (t) => {
t.equal(
fastURI.equal('http://example.com/#%E0%A4A', 'http://example.com/#%E0%A4A'),
true,
'malformed fragment does not throw during equality checks'
)
t.end()
})
-5
View File
@@ -150,11 +150,6 @@ test('URI parse', (t) => {
t.equal(components.query, undefined, 'query')
t.equal(components.fragment, '%0D', 'fragment')
// malformed percent-encoded fragment must not throw
components = fastURI.parse('http://example.com/#%E0%A4A')
t.equal(components.error, 'URI malformed', 'malformed fragment errors')
t.equal(components.fragment, '%E0%A4A', 'malformed fragment is preserved')
// all
components = fastURI.parse('uri://user:pass@example.com:123/one/two.three?q1=a1&q2=a2#body')
t.equal(components.error, undefined, 'all errors')
-9
View File
@@ -76,12 +76,3 @@ test('URN Resolving', (t) => {
t.equal(fastURI.resolve('urn:some:other:prop', 'urn:some:ip:prop'), 'urn:some:ip:prop', 'urn:some:ip:prop')
t.end()
})
test('URI Resolving tolerates malformed fragments', (t) => {
t.equal(
fastURI.resolve('http://base.com/', 'http://example.com/#%E0%A4A'),
'http://example.com/#%E0%A4A',
'malformed fragment does not throw during resolve'
)
t.end()
})