74 lines
3.8 KiB
TypeScript
74 lines
3.8 KiB
TypeScript
// The area plugins of wayBetter for X on the site (/store/areas/<id>/): Compose+, Reader+, … are
|
|
// built into the app, so they are not in the signed catalog and have no Install button. Their
|
|
// names, descriptions and feature lists come from the app's own feature table and words
|
|
// (apps/desktop/src/areas/table.ts, apps/desktop/locales/<lang>.json); the folder
|
|
// store/x/areas/<id>/item.json adds category, tags, licence, credits and media
|
|
// (scripts/store/areas.mjs). So the page, the app and the Store check read one source.
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
// @ts-expect-error: a plain ES module shared with scripts/check-store.mjs
|
|
import { readAreaSource } from '../../scripts/store/areas.mjs';
|
|
import { X_TABLE } from '../desktop/src/areas/table.ts';
|
|
import { featureWords } from '../../waybetter/packages/runtime/src/featureTable.ts';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const REPO = resolve(HERE, '../..');
|
|
const LOCALES = join(REPO, 'apps/desktop/locales');
|
|
/** The app version these pages speak of: the first public release has every area. */
|
|
export const AREAS_SINCE = '1.0.0';
|
|
|
|
export type AreaFeature = { id: string; label: string; description: string };
|
|
export type AreaItem = {
|
|
id: string; name: string; description: string; category: string; tags: string[];
|
|
license?: string; credits?: string; features: AreaFeature[];
|
|
};
|
|
|
|
type Area = { id: string; category: string; tags: string[]; license?: string; credits?: string; dir: string };
|
|
let source: Area[] | null = null;
|
|
const words = new Map<string, Record<string, string>>();
|
|
|
|
/** The area folders (store/x/areas/), read once; a problem stops the build as the Store check would. */
|
|
function areaSource(): Area[] {
|
|
if (source) return source;
|
|
const read = readAreaSource({ root: REPO, app: 'x', ids: X_TABLE.areas.map(area => area.id) }) as { entries: Area[]; problems: string[] };
|
|
if (read.problems.length) throw new Error(`the Store's area pages have problems (bun run check:store explains each):\n ${read.problems.join('\n ')}`);
|
|
source = read.entries;
|
|
return source;
|
|
}
|
|
|
|
/** The app's words in a language, English underneath (a site language the app lacks reads English). */
|
|
function appWords(lang: string): Record<string, string> {
|
|
const cached = words.get(lang);
|
|
if (cached) return cached;
|
|
const load = (code: string) => {
|
|
const file = join(LOCALES, `${code}.json`);
|
|
return existsSync(file) ? (JSON.parse(readFileSync(file, 'utf8')) as { messages: Record<string, string> }).messages : {};
|
|
};
|
|
const out = { ...load('en'), ...(lang === 'en' ? {} : load(lang)) };
|
|
words.set(lang, out);
|
|
return out;
|
|
}
|
|
|
|
/** Every area in the table's order, in a language (the app's own words for it). */
|
|
export function areaItems(lang = 'en'): AreaItem[] {
|
|
const text = appWords(lang);
|
|
const say = (key: string, fallback: string) => (text[key] ?? fallback).replace(/\s+/g, ' ').trim();
|
|
return areaSource().map(entry => {
|
|
const area = X_TABLE.areas.find(candidate => candidate.id === entry.id)!;
|
|
const features = X_TABLE.features
|
|
.filter(feature => feature.area === area.id)
|
|
.map(feature => {
|
|
const keys = featureWords(feature);
|
|
return { id: feature.id, label: say(keys.label, feature.id), description: say(keys.description, '') };
|
|
});
|
|
return {
|
|
id: area.id, name: area.name, description: say(area.description, area.name), category: entry.category, tags: entry.tags,
|
|
...(entry.license ? { license: entry.license } : {}), ...(entry.credits ? { credits: entry.credits } : {}), features,
|
|
};
|
|
});
|
|
}
|
|
|
|
export const areaPath = (id: string): string => `/store/areas/${id}/`;
|
|
/** The folder with the area's item.json and media ("store/x/areas/reader"). */
|
|
export const areaFolder = (id: string): string => join(REPO, 'store', 'x', 'areas', id);
|