@rr0/cms 0.3.64 → 0.3.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -130,6 +130,12 @@ export declare class CMSGenerator implements CMSContext {
130
130
  timeElementFactory: TimeElementFactory;
131
131
  timeReplacer: TimeReplacer;
132
132
  };
133
+ /**
134
+ * The other names each people's page can be searched by — their pseudonyms and their surnames, the latter said as
135
+ * "<surname> <lastName>" so that a surname alone doesn't leave the reader guessing whose it is. Keyed by page URL,
136
+ * the people's own page only: their other pages (a CV, say) are not that person's name.
137
+ */
138
+ protected peopleNames(peopleService: PeopleService): Promise<Map<string, string[]>>;
133
139
  protected setupPeople(context: RR0ContextImpl, peopleRenderer: PeopleHtmlRenderer, copies: string[]): Promise<{
134
140
  peopleService: PeopleService;
135
141
  peopleSteps: import("./index.js").PeopleDirectoryStep[];
@@ -243,7 +243,8 @@ export class CMSGenerator {
243
243
  }
244
244
  const reindex = args.reindex;
245
245
  if (reindex === null || reindex === void 0 ? void 0 : reindex.includes("search")) {
246
- ssg.add(new SearchIndexStep("search/index.json", searchVisitor, outDir, (context, fileName) => timeService.setContextFromFile(context, fileName)));
246
+ const peopleNames = await this.peopleNames(peopleService);
247
+ ssg.add(new SearchIndexStep("search/index.json", searchVisitor, outDir, (context, fileName) => timeService.setContextFromFile(context, fileName), url => peopleNames.get(url) || []));
247
248
  }
248
249
  if (reindex === null || reindex === void 0 ? void 0 : reindex.includes("sources")) {
249
250
  ssg.add(new SourceIndexStep(this.options.sourceRegistryFileName, sourceFactory));
@@ -288,6 +289,25 @@ export class CMSGenerator {
288
289
  const timeReplacer = new TimeReplacer(timeElementFactory);
289
290
  return { timeFiles, timeElementFactory, timeReplacer };
290
291
  }
292
+ /**
293
+ * The other names each people's page can be searched by — their pseudonyms and their surnames, the latter said as
294
+ * "<surname> <lastName>" so that a surname alone doesn't leave the reader guessing whose it is. Keyed by page URL,
295
+ * the people's own page only: their other pages (a CV, say) are not that person's name.
296
+ */
297
+ async peopleNames(peopleService) {
298
+ const namesByUrl = new Map();
299
+ for (const people of await peopleService.getAll()) {
300
+ const lastName = people.lastName;
301
+ const names = [
302
+ ...people.pseudonyms,
303
+ ...(lastName ? people.surnames.map(surname => `${surname} ${lastName}`) : [])
304
+ ];
305
+ if (names.length > 0 && people.dirName) {
306
+ namesByUrl.set(`${people.dirName}/index.html`, names);
307
+ }
308
+ }
309
+ return namesByUrl;
310
+ }
291
311
  async setupPeople(context, peopleRenderer, copies) {
292
312
  const peopleFiles = await this.peopleFactory.getFiles();
293
313
  const peopleService = new PeopleService(this.dataService, this.peopleFactory, { files: peopleFiles, rootDir: "people" });
@@ -14,6 +14,11 @@ export declare class PeopleDirectoryStep extends DirectoryStep {
14
14
  protected filter: PeopleFilter;
15
15
  constructor(name: string, rootDirs: string[], excludedDirs: string[], templateFileName: string, outputFunc: OutputFunc, config: FileWriteConfig, service: PeopleService, renderer: PeopleHtmlRenderer, filter?: PeopleFilter);
16
16
  protected processDirs(context: HtmlRR0Context, dirNames: string[]): Promise<void>;
17
+ /**
18
+ * An extra directory entry for another name the same people is known by (a pseudonym, a surname), pointing at that
19
+ * same people's page.
20
+ */
21
+ protected alsoKnownAs(people: People, name: string): People;
17
22
  protected toList(context: HtmlRR0Context, peopleList: People[], pseudoPeopleList: People[], allCountries: Set<CountryCode>, occupations: Set<Occupation>): HTMLUListElement;
18
23
  protected toListItem(context: HtmlRR0Context, people: People, pseudoPeopleList: People[], allCountries: Set<CountryCode>, occupations: Set<Occupation>): HTMLLIElement;
19
24
  }
@@ -24,17 +24,21 @@ export class PeopleDirectoryStep extends DirectoryStep {
24
24
  const outputPath = config.getOutputPath(context);
25
25
  const output = context.newOutput(outputPath);
26
26
  const pseudoPeopleList = peopleList.reduce((prevPeopleList, peopleInfo) => {
27
- var _a;
28
- if (((_a = peopleInfo.pseudonyms) === null || _a === void 0 ? void 0 : _a.length) > 0) {
29
- for (const pseudonym of peopleInfo.pseudonyms) {
30
- const pseudo = new People(peopleInfo.firstNames, peopleInfo.lastName, peopleInfo.pseudonyms, peopleInfo.occupations, peopleInfo.countries, peopleInfo.discredited, peopleInfo.gender, peopleInfo.id, peopleInfo.dirName, peopleInfo.image);
31
- pseudo.lastAndFirstName = pseudonym;
32
- prevPeopleList.push(pseudo);
27
+ for (const pseudonym of peopleInfo.pseudonyms || []) {
28
+ prevPeopleList.push(this.alsoKnownAs(peopleInfo, pseudonym));
29
+ }
30
+ return prevPeopleList;
31
+ }, []);
32
+ const surnamePeopleList = peopleList.reduce((prevPeopleList, peopleInfo) => {
33
+ const lastName = peopleInfo.lastName;
34
+ if (lastName) { // A surname alone would say nothing about who it belongs to
35
+ for (const surname of peopleInfo.surnames) {
36
+ prevPeopleList.push(this.alsoKnownAs(peopleInfo, `${surname} ${lastName}`));
33
37
  }
34
38
  }
35
39
  return prevPeopleList;
36
40
  }, []);
37
- peopleList = peopleList.concat(pseudoPeopleList).sort((p1, p2) => p1.lastAndFirstName.localeCompare(p2.lastAndFirstName));
41
+ peopleList = peopleList.concat(pseudoPeopleList, surnamePeopleList).sort((p1, p2) => p1.lastAndFirstName.localeCompare(p2.lastAndFirstName));
38
42
  const allCountries = new Set();
39
43
  const occupations = new Set();
40
44
  const ul = this.toList(context, peopleList, pseudoPeopleList, allCountries, occupations);
@@ -63,6 +67,15 @@ export class PeopleDirectoryStep extends DirectoryStep {
63
67
  }
64
68
  await this.outputFunc(context, output);
65
69
  }
70
+ /**
71
+ * An extra directory entry for another name the same people is known by (a pseudonym, a surname), pointing at that
72
+ * same people's page.
73
+ */
74
+ alsoKnownAs(people, name) {
75
+ const alias = new People(people.firstNames, people.lastName, people.pseudonyms, people.occupations, people.countries, people.discredited, people.gender, people.id, people.dirName, people.image);
76
+ alias.lastAndFirstName = name; // Which is both what the entry displays and where it sorts
77
+ return alias;
78
+ }
66
79
  toList(context, peopleList, pseudoPeopleList, allCountries, occupations) {
67
80
  const file = context.file;
68
81
  const listItems = peopleList.map(people => this.toListItem(context, people, pseudoPeopleList, allCountries, occupations));
@@ -10,7 +10,7 @@ export class PeopleHtmlRenderer {
10
10
  const classList = ["data-resolved", "people-resolved"];
11
11
  if (pseudoPeopleList.indexOf(people) >= 0 || people.pseudonyms.includes(content)) {
12
12
  classList.push("pseudonym");
13
- titles.push(`(pseudonyme de ${people.firstAndLastName})`);
13
+ titles.push(`(pseudonyme de ${people.titleOverride || people.firstAndLastName})`);
14
14
  }
15
15
  if (people.hoax) {
16
16
  classList.push("canular");
@@ -52,7 +52,7 @@ export class PeopleHtmlRenderer {
52
52
  titles.push(occupationMsg(gender));
53
53
  }
54
54
  }
55
- const text = content || people.lastAndFirstName || people.title;
55
+ const text = content || people.titleOverride || people.lastAndFirstName || people.title;
56
56
  const doc = context.file.document;
57
57
  const link = doc.createElement("a");
58
58
  link.innerHTML = text;
@@ -9,7 +9,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _SearchComponent_instances, _SearchComponent_siteIndex, _SearchComponent_loading, _SearchComponent_maxResultCount, _SearchComponent_siteSearchChange, _SearchComponent_siteSearchLoad, _SearchComponent_setDataList;
12
+ var _SearchComponent_instances, _SearchComponent_siteIndex, _SearchComponent_entries, _SearchComponent_loading, _SearchComponent_maxResultCount, _SearchComponent_siteSearchChange, _SearchComponent_siteSearchLoad, _SearchComponent_setDataList;
13
13
  const template = document.createElement("template");
14
14
  const html = `
15
15
  <input id="search-input" list="values" type="search" part="input">
@@ -32,6 +32,13 @@ export class SearchComponent extends HTMLElement {
32
32
  * @type SearchIndex
33
33
  */
34
34
  _SearchComponent_siteIndex.set(this, void 0);
35
+ /**
36
+ * One searchable entry per name: a page's title, plus each of the other names it is known by. A datalist filters on
37
+ * its options' own text, so a surname or a pseudonym is only reachable when it IS an option.
38
+ *
39
+ * @type {{label: string, url: string}[]}
40
+ */
41
+ _SearchComponent_entries.set(this, []);
35
42
  _SearchComponent_loading.set(this, false
36
43
  /**
37
44
  * @readonly
@@ -69,19 +76,17 @@ export class SearchComponent extends HTMLElement {
69
76
  this.placeholder = "Recherche";
70
77
  }
71
78
  }
72
- _SearchComponent_siteIndex = new WeakMap(), _SearchComponent_loading = new WeakMap(), _SearchComponent_maxResultCount = new WeakMap(), _SearchComponent_instances = new WeakSet(), _SearchComponent_siteSearchChange = function _SearchComponent_siteSearchChange(e) {
73
- var _a;
79
+ _SearchComponent_siteIndex = new WeakMap(), _SearchComponent_entries = new WeakMap(), _SearchComponent_loading = new WeakMap(), _SearchComponent_maxResultCount = new WeakMap(), _SearchComponent_instances = new WeakSet(), _SearchComponent_siteSearchChange = function _SearchComponent_siteSearchChange(e) {
74
80
  const value = e.target.value.trim();
75
- const pages = ((_a = __classPrivateFieldGet(this, _SearchComponent_siteIndex, "f")) === null || _a === void 0 ? void 0 : _a.pages) || [];
81
+ const entries = __classPrivateFieldGet(this, _SearchComponent_entries, "f");
76
82
  if (e.inputType === "insertReplacementText" || e.inputType == null) {
77
- const pageIndex = pages.findIndex(page => page.title === value);
78
- if (pageIndex >= 0) {
79
- const page = pages[pageIndex];
80
- window.location.href = "/" + page.url;
83
+ const entry = entries.find(entry => entry.label === value);
84
+ if (entry) {
85
+ window.location.href = "/" + entry.url;
81
86
  }
82
87
  }
83
88
  const lowValue = value.toLowerCase();
84
- const dataList = pages.filter(page => page.title.toLowerCase().indexOf(lowValue) >= 0);
89
+ const dataList = entries.filter(entry => entry.label.toLowerCase().indexOf(lowValue) >= 0);
85
90
  __classPrivateFieldGet(this, _SearchComponent_instances, "m", _SearchComponent_setDataList).call(this, dataList.length <= __classPrivateFieldGet(this, _SearchComponent_maxResultCount, "f") ? dataList : []);
86
91
  }, _SearchComponent_siteSearchLoad = function _SearchComponent_siteSearchLoad() {
87
92
  if (!__classPrivateFieldGet(this, _SearchComponent_siteIndex, "f") && !__classPrivateFieldGet(this, _SearchComponent_loading, "f")) {
@@ -89,22 +94,27 @@ _SearchComponent_siteIndex = new WeakMap(), _SearchComponent_loading = new WeakM
89
94
  fetch("/search/index.json").then(async (response) => {
90
95
  if (response.ok) {
91
96
  __classPrivateFieldSet(this, _SearchComponent_siteIndex, await response.json(), "f");
97
+ const entries = [];
92
98
  for (const page of __classPrivateFieldGet(this, _SearchComponent_siteIndex, "f").pages) {
93
99
  const timeStr = page.time;
94
100
  const title = page.title;
95
101
  page.title += (timeStr && timeStr !== title.toLowerCase() ? ` (${timeStr})` : "");
102
+ entries.push({ label: page.title, url: page.url });
103
+ for (const name of page.names || []) {
104
+ entries.push({ label: name, url: page.url });
105
+ }
96
106
  }
107
+ __classPrivateFieldSet(this, _SearchComponent_entries, entries, "f");
97
108
  }
98
109
  __classPrivateFieldSet(this, _SearchComponent_loading, false, "f");
99
110
  });
100
111
  }
101
- }, _SearchComponent_setDataList = function _SearchComponent_setDataList(pages) {
112
+ }, _SearchComponent_setDataList = function _SearchComponent_setDataList(entries) {
102
113
  const datalist = this.shadow.getElementById("values");
103
114
  datalist.innerHTML = "";
104
- for (let i = 0; i < pages.length; i++) {
105
- const page = pages[i];
115
+ for (let i = 0; i < entries.length; i++) {
106
116
  const option = document.createElement("option");
107
- option.value = page.title;
117
+ option.value = entries[i].label;
108
118
  datalist.append(option);
109
119
  }
110
120
  };
@@ -9,12 +9,16 @@ export declare class SearchIndexStep implements SsgStep {
9
9
  protected searchCommand: SearchVisitor;
10
10
  protected contentRoot?: string;
11
11
  protected prepareContext?: (context: HtmlRR0Context, fileName: string) => void;
12
+ protected alternateNames?: (url: string) => string[];
12
13
  protected encoding: BufferEncoding;
13
14
  /**
14
15
  * @param fileName The index file path
15
16
  * @param searchCommand The command that collected the pages info.
17
+ * @param contentRoot
18
+ * @param prepareContext
19
+ * @param alternateNames Other names a page can be searched by, such as a people's surnames and pseudonyms.
16
20
  */
17
- constructor(fileName: string, searchCommand: SearchVisitor, contentRoot?: string, prepareContext?: (context: HtmlRR0Context, fileName: string) => void);
21
+ constructor(fileName: string, searchCommand: SearchVisitor, contentRoot?: string, prepareContext?: (context: HtmlRR0Context, fileName: string) => void, alternateNames?: (url: string) => string[]);
18
22
  /**
19
23
  * Write the search index file.
20
24
  *
@@ -10,12 +10,16 @@ export class SearchIndexStep {
10
10
  /**
11
11
  * @param fileName The index file path
12
12
  * @param searchCommand The command that collected the pages info.
13
+ * @param contentRoot
14
+ * @param prepareContext
15
+ * @param alternateNames Other names a page can be searched by, such as a people's surnames and pseudonyms.
13
16
  */
14
- constructor(fileName, searchCommand, contentRoot, prepareContext) {
17
+ constructor(fileName, searchCommand, contentRoot, prepareContext, alternateNames) {
15
18
  this.fileName = fileName;
16
19
  this.searchCommand = searchCommand;
17
20
  this.contentRoot = contentRoot;
18
21
  this.prepareContext = prepareContext;
22
+ this.alternateNames = alternateNames;
19
23
  this.encoding = "utf8";
20
24
  }
21
25
  /**
@@ -24,7 +28,7 @@ export class SearchIndexStep {
24
28
  * @param context
25
29
  */
26
30
  async execute(context) {
27
- var _a;
31
+ var _a, _b;
28
32
  const newIndex = this.searchCommand.index;
29
33
  let existingIndex;
30
34
  if (this.contentRoot) {
@@ -45,7 +49,8 @@ export class SearchIndexStep {
45
49
  const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
46
50
  const outputTitle = titleMatch ? this.decodeTitle(titleMatch[1]) : "";
47
51
  const title = this.readSourceTitle(sourceFile) || outputTitle;
48
- const pageInfo = this.searchCommand.pageInfoFrom(title, url.split(path.sep).join("/"), pageContext);
52
+ const pageUrl = url.split(path.sep).join("/");
53
+ const pageInfo = this.searchCommand.pageInfoFrom(title, pageUrl, pageContext, (_b = this.alternateNames) === null || _b === void 0 ? void 0 : _b.call(this, pageUrl));
49
54
  if (pageInfo) {
50
55
  const resultTitle = SearchVisitor.resultTitle(pageInfo);
51
56
  const titleIndexed = pagesByResultTitle.get(resultTitle);
@@ -7,6 +7,10 @@ export type PageInfo = {
7
7
  title: string;
8
8
  url: string;
9
9
  time: string;
10
+ /**
11
+ * Other names the page can be searched by, such as a people's surnames and pseudonyms.
12
+ */
13
+ names?: string[];
10
14
  html?: string;
11
15
  };
12
16
  type WordCount = {
@@ -37,7 +41,7 @@ export declare class SearchVisitor implements FileVisitor {
37
41
  contentStepEnd(): Promise<void>;
38
42
  visit(context: HtmlRR0Context): Promise<void>;
39
43
  pageInfo(context: HtmlRR0Context, contentRoot?: string): PageInfo | undefined;
40
- pageInfoFrom(title: string, url: string, context: HtmlRR0Context): PageInfo | undefined;
44
+ pageInfoFrom(title: string, url: string, context: HtmlRR0Context, names?: string[]): PageInfo | undefined;
41
45
  protected handleAlreadyIndexed(resultTitle: string, url: string, titleIndexed: PageInfo): void;
42
46
  protected getContents(doc: Document): string;
43
47
  protected indexContent(context: HtmlRR0Context, outputFile: HtmlFileContents): void;
@@ -59,12 +59,17 @@ export class SearchVisitor {
59
59
  : file.name.startsWith("out/") ? file.name.substring("out/".length) : file.name;
60
60
  return this.pageInfoFrom(file.title, url, context);
61
61
  }
62
- pageInfoFrom(title, url, context) {
62
+ pageInfoFrom(title, url, context, names = []) {
63
63
  if (!title || this.config.notIndexedUrls.includes(url)) {
64
64
  return undefined;
65
65
  }
66
66
  const time = this.timeTextBuilder.build(context, { year: "numeric", month: "short", day: "numeric" }).toLowerCase();
67
- return { title, url, time };
67
+ const pageInfo = { title, url, time };
68
+ const otherNames = Array.from(new Set(names.filter(name => name && name !== title)));
69
+ if (otherNames.length > 0) {
70
+ pageInfo.names = otherNames;
71
+ }
72
+ return pageInfo;
68
73
  }
69
74
  handleAlreadyIndexed(resultTitle, url, titleIndexed) {
70
75
  throw new Error(`Search result "${resultTitle}" with URL ${url} is already indexed with URL ${titleIndexed.url}`);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@rr0/cms",
3
3
  "type": "module",
4
4
  "author": "Jérôme Beau <rr0@rr0.org> (https://rr0.org)",
5
- "version": "0.3.64",
5
+ "version": "0.3.66",
6
6
  "description": "RR0 Content Management System (CMS)",
7
7
  "exports": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -31,7 +31,7 @@
31
31
  "dependencies": {
32
32
  "@javarome/fileutil": "^0.3.7",
33
33
  "@rr0/common": "^1.1.5",
34
- "@rr0/data": "^0.3.41",
34
+ "@rr0/data": "^0.3.43",
35
35
  "@rr0/lang": "^0.1.13",
36
36
  "@rr0/place": "^0.5.4",
37
37
  "@rr0/time": "^0.11.2",