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:
-3
@@ -134,9 +134,6 @@ export type Path = string | TokenData;
|
||||
* Transform a path into a match function.
|
||||
*/
|
||||
export declare function match<P extends ParamData>(path: Path | Path[], options?: MatchOptions & ParseOptions): MatchFunction<P>;
|
||||
/**
|
||||
* Transform a path into a regular expression and capture keys.
|
||||
*/
|
||||
export declare function pathToRegexp(path: Path | Path[], options?: PathToRegexpOptions & ParseOptions): {
|
||||
regexp: RegExp;
|
||||
keys: Keys;
|
||||
|
||||
+195
-217
@@ -10,7 +10,19 @@ const DEFAULT_DELIMITER = "/";
|
||||
const NOOP_VALUE = (value) => value;
|
||||
const ID_START = /^[$_\p{ID_Start}]$/u;
|
||||
const ID_CONTINUE = /^[$\u200c\u200d\p{ID_Continue}]$/u;
|
||||
const ID = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
|
||||
const SIMPLE_TOKENS = {
|
||||
// Groups.
|
||||
"{": "{",
|
||||
"}": "}",
|
||||
// Reserved.
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"[": "[",
|
||||
"]": "]",
|
||||
"+": "+",
|
||||
"?": "?",
|
||||
"!": "!",
|
||||
};
|
||||
/**
|
||||
* Escape text for stringify to path.
|
||||
*/
|
||||
@@ -53,91 +65,96 @@ exports.PathError = PathError;
|
||||
function parse(str, options = {}) {
|
||||
const { encodePath = NOOP_VALUE } = options;
|
||||
const chars = [...str];
|
||||
const tokens = [];
|
||||
let index = 0;
|
||||
function consumeUntil(end) {
|
||||
const output = [];
|
||||
let path = "";
|
||||
function writePath() {
|
||||
if (!path)
|
||||
return;
|
||||
output.push({
|
||||
type: "text",
|
||||
value: encodePath(path),
|
||||
});
|
||||
path = "";
|
||||
let pos = 0;
|
||||
function name() {
|
||||
let value = "";
|
||||
if (ID_START.test(chars[index])) {
|
||||
do {
|
||||
value += chars[index++];
|
||||
} while (ID_CONTINUE.test(chars[index]));
|
||||
}
|
||||
while (index < chars.length) {
|
||||
const value = chars[index++];
|
||||
if (value === end) {
|
||||
writePath();
|
||||
return output;
|
||||
}
|
||||
if (value === "\\") {
|
||||
if (index === chars.length) {
|
||||
throw new PathError(`Unexpected end after \\ at index ${index}`, str);
|
||||
else if (chars[index] === '"') {
|
||||
let quoteStart = index;
|
||||
while (index++ < chars.length) {
|
||||
if (chars[index] === '"') {
|
||||
index++;
|
||||
quoteStart = 0;
|
||||
break;
|
||||
}
|
||||
path += chars[index++];
|
||||
// Increment over escape characters.
|
||||
if (chars[index] === "\\")
|
||||
index++;
|
||||
value += chars[index];
|
||||
}
|
||||
if (quoteStart) {
|
||||
throw new PathError(`Unterminated quote at index ${quoteStart}`, str);
|
||||
}
|
||||
}
|
||||
if (!value) {
|
||||
throw new PathError(`Missing parameter name at index ${index}`, str);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
while (index < chars.length) {
|
||||
const value = chars[index];
|
||||
const type = SIMPLE_TOKENS[value];
|
||||
if (type) {
|
||||
tokens.push({ type, index: index++, value });
|
||||
}
|
||||
else if (value === "\\") {
|
||||
tokens.push({ type: "escape", index: index++, value: chars[index++] });
|
||||
}
|
||||
else if (value === ":") {
|
||||
tokens.push({ type: "param", index: index++, value: name() });
|
||||
}
|
||||
else if (value === "*") {
|
||||
tokens.push({ type: "wildcard", index: index++, value: name() });
|
||||
}
|
||||
else {
|
||||
tokens.push({ type: "char", index: index++, value });
|
||||
}
|
||||
}
|
||||
tokens.push({ type: "end", index, value: "" });
|
||||
function consumeUntil(endType) {
|
||||
const output = [];
|
||||
while (true) {
|
||||
const token = tokens[pos++];
|
||||
if (token.type === endType)
|
||||
break;
|
||||
if (token.type === "char" || token.type === "escape") {
|
||||
let path = token.value;
|
||||
let cur = tokens[pos];
|
||||
while (cur.type === "char" || cur.type === "escape") {
|
||||
path += cur.value;
|
||||
cur = tokens[++pos];
|
||||
}
|
||||
output.push({
|
||||
type: "text",
|
||||
value: encodePath(path),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (value === ":" || value === "*") {
|
||||
const type = value === ":" ? "param" : "wildcard";
|
||||
let name = "";
|
||||
if (ID_START.test(chars[index])) {
|
||||
do {
|
||||
name += chars[index++];
|
||||
} while (ID_CONTINUE.test(chars[index]));
|
||||
}
|
||||
else if (chars[index] === '"') {
|
||||
let quoteStart = index;
|
||||
while (index < chars.length) {
|
||||
if (chars[++index] === '"') {
|
||||
index++;
|
||||
quoteStart = 0;
|
||||
break;
|
||||
}
|
||||
// Increment over escape characters.
|
||||
if (chars[index] === "\\")
|
||||
index++;
|
||||
name += chars[index];
|
||||
}
|
||||
if (quoteStart) {
|
||||
throw new PathError(`Unterminated quote at index ${quoteStart}`, str);
|
||||
}
|
||||
}
|
||||
if (!name) {
|
||||
throw new PathError(`Missing parameter name at index ${index}`, str);
|
||||
}
|
||||
writePath();
|
||||
output.push({ type, name });
|
||||
if (token.type === "param" || token.type === "wildcard") {
|
||||
output.push({
|
||||
type: token.type,
|
||||
name: token.value,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (value === "{") {
|
||||
writePath();
|
||||
if (token.type === "{") {
|
||||
output.push({
|
||||
type: "group",
|
||||
tokens: consumeUntil("}"),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (value === "}" ||
|
||||
value === "(" ||
|
||||
value === ")" ||
|
||||
value === "[" ||
|
||||
value === "]" ||
|
||||
value === "+" ||
|
||||
value === "?" ||
|
||||
value === "!") {
|
||||
throw new PathError(`Unexpected ${value} at index ${index - 1}`, str);
|
||||
}
|
||||
path += value;
|
||||
throw new PathError(`Unexpected ${token.type} at index ${token.index}, expected ${endType}`, str);
|
||||
}
|
||||
if (end) {
|
||||
throw new PathError(`Unexpected end at index ${index}, expected ${end}`, str);
|
||||
}
|
||||
writePath();
|
||||
return output;
|
||||
}
|
||||
return new TokenData(consumeUntil(""), str);
|
||||
return new TokenData(consumeUntil("end"), str);
|
||||
}
|
||||
/**
|
||||
* Compile a string to a template function for the path.
|
||||
@@ -147,8 +164,7 @@ function compile(path, options = {}) {
|
||||
const data = typeof path === "object" ? path : parse(path, options);
|
||||
const fn = tokensToFunction(data.tokens, delimiter, encode);
|
||||
return function path(params = {}) {
|
||||
const missing = [];
|
||||
const path = fn(params, missing);
|
||||
const [path, ...missing] = fn(params);
|
||||
if (missing.length) {
|
||||
throw new TypeError(`Missing parameters: ${missing.join(", ")}`);
|
||||
}
|
||||
@@ -157,10 +173,12 @@ function compile(path, options = {}) {
|
||||
}
|
||||
function tokensToFunction(tokens, delimiter, encode) {
|
||||
const encoders = tokens.map((token) => tokenToFunction(token, delimiter, encode));
|
||||
return (data, missing) => {
|
||||
let result = "";
|
||||
return (data) => {
|
||||
const result = [""];
|
||||
for (const encoder of encoders) {
|
||||
result += encoder(data, missing);
|
||||
const [value, ...extras] = encoder(data);
|
||||
result[0] += value;
|
||||
result.push(...extras);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -170,51 +188,45 @@ function tokensToFunction(tokens, delimiter, encode) {
|
||||
*/
|
||||
function tokenToFunction(token, delimiter, encode) {
|
||||
if (token.type === "text")
|
||||
return () => token.value;
|
||||
return () => [token.value];
|
||||
if (token.type === "group") {
|
||||
const fn = tokensToFunction(token.tokens, delimiter, encode);
|
||||
return (data, missing) => {
|
||||
const len = missing.length;
|
||||
const value = fn(data, missing);
|
||||
if (missing.length === len)
|
||||
return value;
|
||||
missing.length = len; // Reset optional group.
|
||||
return "";
|
||||
return (data) => {
|
||||
const [value, ...missing] = fn(data);
|
||||
if (!missing.length)
|
||||
return [value];
|
||||
return [""];
|
||||
};
|
||||
}
|
||||
const encodeValue = encode || NOOP_VALUE;
|
||||
if (token.type === "wildcard" && encode !== false) {
|
||||
return (data, missing) => {
|
||||
return (data) => {
|
||||
const value = data[token.name];
|
||||
if (value == null) {
|
||||
missing.push(token.name);
|
||||
return "";
|
||||
}
|
||||
if (value == null)
|
||||
return ["", token.name];
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
throw new TypeError(`Expected "${token.name}" to be a non-empty array`);
|
||||
}
|
||||
let result = "";
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (typeof value[i] !== "string") {
|
||||
throw new TypeError(`Expected "${token.name}/${i}" to be a string`);
|
||||
}
|
||||
if (i > 0)
|
||||
result += delimiter;
|
||||
result += encodeValue(value[i]);
|
||||
}
|
||||
return result;
|
||||
return [
|
||||
value
|
||||
.map((value, index) => {
|
||||
if (typeof value !== "string") {
|
||||
throw new TypeError(`Expected "${token.name}/${index}" to be a string`);
|
||||
}
|
||||
return encodeValue(value);
|
||||
})
|
||||
.join(delimiter),
|
||||
];
|
||||
};
|
||||
}
|
||||
return (data, missing) => {
|
||||
return (data) => {
|
||||
const value = data[token.name];
|
||||
if (value == null) {
|
||||
missing.push(token.name);
|
||||
return "";
|
||||
}
|
||||
if (value == null)
|
||||
return ["", token.name];
|
||||
if (typeof value !== "string") {
|
||||
throw new TypeError(`Expected "${token.name}" to be a string`);
|
||||
}
|
||||
return encodeValue(value);
|
||||
return [encodeValue(value)];
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -246,53 +258,54 @@ function match(path, options = {}) {
|
||||
return { path, params };
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Transform a path into a regular expression and capture keys.
|
||||
*/
|
||||
function pathToRegexp(path, options = {}) {
|
||||
const { delimiter = DEFAULT_DELIMITER, end = true, sensitive = false, trailing = true, } = options;
|
||||
const keys = [];
|
||||
let source = "";
|
||||
let combinations = 0;
|
||||
function process(path) {
|
||||
if (Array.isArray(path)) {
|
||||
for (const p of path)
|
||||
process(p);
|
||||
return;
|
||||
const flags = sensitive ? "" : "i";
|
||||
const sources = [];
|
||||
for (const input of pathsToArray(path, [])) {
|
||||
const data = typeof input === "object" ? input : parse(input, options);
|
||||
for (const tokens of flatten(data.tokens, 0, [])) {
|
||||
sources.push(toRegExpSource(tokens, delimiter, keys, data.originalPath));
|
||||
}
|
||||
const data = typeof path === "object" ? path : parse(path, options);
|
||||
flatten(data.tokens, 0, [], (tokens) => {
|
||||
if (combinations >= 256) {
|
||||
throw new PathError("Too many path combinations", data.originalPath);
|
||||
}
|
||||
if (combinations > 0)
|
||||
source += "|";
|
||||
source += toRegExpSource(tokens, delimiter, keys, data.originalPath);
|
||||
combinations++;
|
||||
});
|
||||
}
|
||||
process(path);
|
||||
let pattern = `^(?:${source})`;
|
||||
let pattern = `^(?:${sources.join("|")})`;
|
||||
if (trailing)
|
||||
pattern += "(?:" + escape(delimiter) + "$)?";
|
||||
pattern += end ? "$" : "(?=" + escape(delimiter) + "|$)";
|
||||
return { regexp: new RegExp(pattern, sensitive ? "" : "i"), keys };
|
||||
pattern += `(?:${escape(delimiter)}$)?`;
|
||||
pattern += end ? "$" : `(?=${escape(delimiter)}|$)`;
|
||||
const regexp = new RegExp(pattern, flags);
|
||||
return { regexp, keys };
|
||||
}
|
||||
/**
|
||||
* Convert a path or array of paths into a flat array.
|
||||
*/
|
||||
function pathsToArray(paths, init) {
|
||||
if (Array.isArray(paths)) {
|
||||
for (const p of paths)
|
||||
pathsToArray(p, init);
|
||||
}
|
||||
else {
|
||||
init.push(paths);
|
||||
}
|
||||
return init;
|
||||
}
|
||||
/**
|
||||
* Generate a flat list of sequence tokens from the given tokens.
|
||||
*/
|
||||
function flatten(tokens, index, result, callback) {
|
||||
while (index < tokens.length) {
|
||||
const token = tokens[index++];
|
||||
if (token.type === "group") {
|
||||
const len = result.length;
|
||||
flatten(token.tokens, 0, result, (seq) => flatten(tokens, index, seq, callback));
|
||||
result.length = len;
|
||||
continue;
|
||||
}
|
||||
result.push(token);
|
||||
function* flatten(tokens, index, init) {
|
||||
if (index === tokens.length) {
|
||||
return yield init;
|
||||
}
|
||||
callback(result);
|
||||
const token = tokens[index];
|
||||
if (token.type === "group") {
|
||||
for (const seq of flatten(token.tokens, 0, init.slice())) {
|
||||
yield* flatten(tokens, index + 1, seq);
|
||||
}
|
||||
}
|
||||
else {
|
||||
init.push(token);
|
||||
}
|
||||
yield* flatten(tokens, index + 1, init);
|
||||
}
|
||||
/**
|
||||
* Transform a flat sequence of tokens into a regular expression.
|
||||
@@ -300,111 +313,72 @@ function flatten(tokens, index, result, callback) {
|
||||
function toRegExpSource(tokens, delimiter, keys, originalPath) {
|
||||
let result = "";
|
||||
let backtrack = "";
|
||||
let wildcardBacktrack = "";
|
||||
let prevCaptureType = 0;
|
||||
let hasSegmentCapture = 0;
|
||||
let index = 0;
|
||||
function hasInSegment(index, type) {
|
||||
while (index < tokens.length) {
|
||||
const token = tokens[index++];
|
||||
if (token.type === type)
|
||||
return true;
|
||||
if (token.type === "text") {
|
||||
if (token.value.includes(delimiter))
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function peekText(index) {
|
||||
let result = "";
|
||||
while (index < tokens.length) {
|
||||
const token = tokens[index++];
|
||||
if (token.type !== "text")
|
||||
break;
|
||||
result += token.value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
while (index < tokens.length) {
|
||||
const token = tokens[index++];
|
||||
let isSafeSegmentParam = true;
|
||||
for (const token of tokens) {
|
||||
if (token.type === "text") {
|
||||
result += escape(token.value);
|
||||
backtrack += token.value;
|
||||
if (prevCaptureType === 2)
|
||||
wildcardBacktrack += token.value;
|
||||
if (token.value.includes(delimiter))
|
||||
hasSegmentCapture = 0;
|
||||
isSafeSegmentParam || (isSafeSegmentParam = token.value.includes(delimiter));
|
||||
continue;
|
||||
}
|
||||
if (token.type === "param" || token.type === "wildcard") {
|
||||
if (prevCaptureType && !backtrack) {
|
||||
if (!isSafeSegmentParam && !backtrack) {
|
||||
throw new PathError(`Missing text before "${token.name}" ${token.type}`, originalPath);
|
||||
}
|
||||
if (token.type === "param") {
|
||||
result +=
|
||||
hasSegmentCapture & 2 // Seen wildcard in segment.
|
||||
? `(${negate(delimiter, backtrack)}+)`
|
||||
: hasInSegment(index, "wildcard") // See wildcard later in segment.
|
||||
? `(${negate(delimiter, peekText(index))}+)`
|
||||
: hasSegmentCapture & 1 // Seen parameter in segment.
|
||||
? `(${negate(delimiter, backtrack)}+|${escape(backtrack)})`
|
||||
: `(${negate(delimiter, "")}+)`;
|
||||
hasSegmentCapture |= prevCaptureType = 1;
|
||||
result += `(${negate(delimiter, isSafeSegmentParam ? "" : backtrack)}+)`;
|
||||
}
|
||||
else {
|
||||
result +=
|
||||
hasSegmentCapture & 2 // Seen wildcard in segment.
|
||||
? `(${negate(backtrack, "")}+)`
|
||||
: wildcardBacktrack // No capture in segment, seen wildcard in path.
|
||||
? `(${negate(wildcardBacktrack, "")}+|${negate(delimiter, "")}+)`
|
||||
: `([^]+)`;
|
||||
wildcardBacktrack = "";
|
||||
hasSegmentCapture |= prevCaptureType = 2;
|
||||
result += `([\\s\\S]+)`;
|
||||
}
|
||||
keys.push(token);
|
||||
backtrack = "";
|
||||
isSafeSegmentParam = false;
|
||||
continue;
|
||||
}
|
||||
throw new TypeError(`Unknown token type: ${token.type}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Block backtracking on previous text/delimiter.
|
||||
* Block backtracking on previous text and ignore delimiter string.
|
||||
*/
|
||||
function negate(a, b) {
|
||||
if (b.length > a.length)
|
||||
return negate(b, a); // Longest string first.
|
||||
if (a === b)
|
||||
b = ""; // Cleaner regex strings, no duplication.
|
||||
if (b.length > 1)
|
||||
return `(?:(?!${escape(a)}|${escape(b)})[^])`;
|
||||
if (a.length > 1)
|
||||
return `(?:(?!${escape(a)})[^${escape(b)}])`;
|
||||
return `[^${escape(a + b)}]`;
|
||||
function negate(delimiter, backtrack) {
|
||||
if (backtrack.length < 2) {
|
||||
if (delimiter.length < 2)
|
||||
return `[^${escape(delimiter + backtrack)}]`;
|
||||
return `(?:(?!${escape(delimiter)})[^${escape(backtrack)}])`;
|
||||
}
|
||||
if (delimiter.length < 2) {
|
||||
return `(?:(?!${escape(backtrack)})[^${escape(delimiter)}])`;
|
||||
}
|
||||
return `(?:(?!${escape(backtrack)}|${escape(delimiter)})[\\s\\S])`;
|
||||
}
|
||||
/**
|
||||
* Stringify an array of tokens into a path string.
|
||||
*/
|
||||
function stringifyTokens(tokens, index) {
|
||||
function stringifyTokens(tokens) {
|
||||
let value = "";
|
||||
while (index < tokens.length) {
|
||||
const token = tokens[index++];
|
||||
let i = 0;
|
||||
function name(value) {
|
||||
const isSafe = isNameSafe(value) && isNextNameSafe(tokens[i]);
|
||||
return isSafe ? value : JSON.stringify(value);
|
||||
}
|
||||
while (i < tokens.length) {
|
||||
const token = tokens[i++];
|
||||
if (token.type === "text") {
|
||||
value += escapeText(token.value);
|
||||
continue;
|
||||
}
|
||||
if (token.type === "group") {
|
||||
value += "{" + stringifyTokens(token.tokens, 0) + "}";
|
||||
value += `{${stringifyTokens(token.tokens)}}`;
|
||||
continue;
|
||||
}
|
||||
if (token.type === "param") {
|
||||
value += ":" + stringifyName(token.name, tokens[index]);
|
||||
value += `:${name(token.name)}`;
|
||||
continue;
|
||||
}
|
||||
if (token.type === "wildcard") {
|
||||
value += "*" + stringifyName(token.name, tokens[index]);
|
||||
value += `*${name(token.name)}`;
|
||||
continue;
|
||||
}
|
||||
throw new TypeError(`Unknown token type: ${token.type}`);
|
||||
@@ -415,17 +389,21 @@ function stringifyTokens(tokens, index) {
|
||||
* Stringify token data into a path string.
|
||||
*/
|
||||
function stringify(data) {
|
||||
return stringifyTokens(data.tokens, 0);
|
||||
return stringifyTokens(data.tokens);
|
||||
}
|
||||
/**
|
||||
* Stringify a parameter name, escaping when it cannot be emitted directly.
|
||||
* Validate the parameter name contains valid ID characters.
|
||||
*/
|
||||
function stringifyName(name, next) {
|
||||
if (!ID.test(name))
|
||||
return JSON.stringify(name);
|
||||
if ((next === null || next === void 0 ? void 0 : next.type) === "text" && ID_CONTINUE.test(next.value[0])) {
|
||||
return JSON.stringify(name);
|
||||
}
|
||||
return name;
|
||||
function isNameSafe(name) {
|
||||
const [first, ...rest] = name;
|
||||
return ID_START.test(first) && rest.every((char) => ID_CONTINUE.test(char));
|
||||
}
|
||||
/**
|
||||
* Validate the next token does not interfere with the current param name.
|
||||
*/
|
||||
function isNextNameSafe(token) {
|
||||
if (token && token.type === "text")
|
||||
return !ID_CONTINUE.test(token.value[0]);
|
||||
return true;
|
||||
}
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user