webapps/packages/layers/content/utils/content-reduced.ts
Dominik Milacher 73083ded58
Some checks failed
Build and deploy updated apps / Build & deploy (push) Failing after 1m7s
Overhaul content management
2025-10-22 19:31:38 +02:00

122 lines
3.0 KiB
TypeScript

import {type JsonValue, fromCanonicalJson, CanonicalPrimitive} from "./content-canonical";
export function buildContent(
locale: string | undefined,
locales: string[],
variant: string | undefined,
variants: string[],
content: JsonValue
): [JsonValue, boolean] {
function check(all: string[], specified: string[], query: string): boolean {
const related = specified.filter(t => all.includes(t))
all = related.length ? related : all
return all.includes(query)
}
function predicate(tags?: string[]) {
if (!tags || !tags.length) {
return true
}
if (locale && !check(locales, tags, locale)) {
return false
}
return !variant || check(variants, tags, variant)
}
const options = {predicate: predicate, expand: false}
const canonical = fromCanonicalJson(content)
const reduced = canonical.reduce(options)
return [reduced, options.expand]
}
export function expandContent(content: JsonValue, sources: JsonValue[]): JsonValue {
function resolve(key: string): JsonValue {
for (const source of sources) {
const value = getPath(source, key)
if (value !== undefined) {
return value
}
}
return `$\{${key}\}`
}
function traverse(item: JsonValue): JsonValue {
if (item !== null) {
if (item instanceof CanonicalPrimitive) {
return item.resolve(resolve)
} else if (Array.isArray(item)) {
return item.map(i => traverse(i))
} else if (typeof item === 'object') {
const record: Record<string, JsonValue> = {}
for (const [k, v] of Object.entries(item)) {
record[k] = traverse(v)
}
return record
}
}
return item
}
return traverse(content)
}
// function getPath(obj: any, path: string, sep = '.'): any { // TODO REFACTOR THIS
// const keys = path.split(sep)
// let cur = obj
//
// for (const key of keys) {
// if (cur == null || typeof cur !== 'object') return undefined
// cur = cur[key]
// }
//
// return cur
// }
function toPath(path: string): Array<string | number> {
// Matches:
// - bare tokens: foo, bar
// - [123] numeric indices
// - ["str"] or ['str'] quoted keys
const re = /[^.[\]]+|\[(?:([0-9]+)|(["'])(.*?)\2)\]/g;
const tokens: Array<string | number> = [];
let m: RegExpExecArray | null;
while ((m = re.exec(path))) {
if (m[1] !== undefined) {
tokens.push(Number(m[1])); // [123]
} else if (m[3] !== undefined) {
tokens.push(m[3]); // ["key"] / ['key']
} else {
tokens.push(m[0]); // bare token
}
}
return tokens;
}
function getPath(obj: unknown, path: string): JsonValue | undefined {
const steps = toPath(path);
let cur: any = obj;
for (const step of steps) {
if (cur == null) return undefined;
if (typeof step === 'number') {
if (!Array.isArray(cur) || step < 0 || step >= cur.length) return undefined;
cur = cur[step];
} else {
cur = cur[step];
}
}
return cur as JsonValue;
}