@rr0/cms 0.2.2 → 0.2.4

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.
Files changed (38) hide show
  1. package/dist/RR0Build.js +4 -2
  2. package/dist/people/PeopleDirectoryStep.d.ts +3 -1
  3. package/dist/people/PeopleDirectoryStep.js +3 -2
  4. package/dist/people/PeopleDirectoryStepFactory.d.ts +3 -1
  5. package/dist/people/PeopleDirectoryStepFactory.js +14 -13
  6. package/dist/people/PeopleFactory.d.ts +3 -3
  7. package/dist/people/PeopleFactory.js +32 -21
  8. package/dist/people/PeopleFactory.test.js +24 -76
  9. package/dist/people/PeopleHtmlRenderer.d.ts +9 -0
  10. package/dist/people/PeopleHtmlRenderer.js +93 -0
  11. package/dist/people/PeopleJson.d.ts +17 -22
  12. package/dist/people/PeopleJson.js +1 -19
  13. package/dist/people/PeopleRegexReplaceCommand.test.js +3 -1
  14. package/dist/people/PeopleReplacer.d.ts +3 -1
  15. package/dist/people/PeopleReplacer.js +3 -2
  16. package/dist/people/PeopleReplacer.test.js +7 -2
  17. package/dist/people/PeopleReplacerFactory.d.ts +3 -1
  18. package/dist/people/PeopleReplacerFactory.js +3 -2
  19. package/dist/people/PeopleService.d.ts +0 -4
  20. package/dist/people/PeopleService.js +2 -96
  21. package/dist/people/PeopleService.test.d.ts +1 -0
  22. package/dist/people/PeopleService.test.js +82 -0
  23. package/dist/place/PlaceRenderer.js +4 -0
  24. package/dist/search/SearchComponent.d.mts +6 -0
  25. package/dist/search/SearchComponent.mjs +17 -1
  26. package/dist/search/SearchIndexStep.js +3 -1
  27. package/dist/source/SourceFactory.js +2 -1
  28. package/dist/time/datasource/ChronologyReplacer.d.ts +1 -1
  29. package/dist/time/datasource/ChronologyReplacer.js +6 -2
  30. package/dist/time/datasource/baseovnifrance/{BaseOvniFranceCaseSummaryMapper.d.ts → BaseOvniFranceCaseSummaryCsvMapper.d.ts} +1 -1
  31. package/dist/time/datasource/baseovnifrance/{BaseOvniFranceCaseSummaryMapper.js → BaseOvniFranceCaseSummaryCsvMapper.js} +1 -1
  32. package/dist/time/datasource/baseovnifrance/BaseOvniFranceCsv.test.js +2 -2
  33. package/dist/time/datasource/baseovnifrance/BaseOvniFranceHttpDatasource.js +1 -1
  34. package/dist/time/datasource/baseovnifrance/index.d.ts +1 -1
  35. package/dist/time/datasource/baseovnifrance/index.js +1 -1
  36. package/dist/time/datasource/rr0/RR0Mapping.d.ts +1 -1
  37. package/dist/time/datasource/rr0/RR0Mapping.js +1 -1
  38. package/package.json +2 -2
package/dist/RR0Build.js CHANGED
@@ -31,6 +31,7 @@ import { writeFile } from "@javarome/fileutil";
31
31
  import { AllDataService, EventDataFactory, RR0EventFactory, TypedDataFactory } from "@rr0/data";
32
32
  import { GooglePlaceService } from "@rr0/place";
33
33
  import { GeipanRR0Mapping } from "./org/eu/fr/cnes/geipan/geipan/GeipanRR0Mapping";
34
+ import { PeopleHtmlRenderer } from "./people/PeopleHtmlRenderer";
34
35
  const outputFunc = async (context, outFile) => {
35
36
  try {
36
37
  if (context.file instanceof HtmlFileContents) {
@@ -100,7 +101,8 @@ export class RR0Build {
100
101
  const ufoCaseDirectoryFile = this.options.ufoCaseDirectoryFile;
101
102
  const ufoCasesExclusions = this.options.ufoCasesExclusions;
102
103
  const ufoCasesStep = new CaseDirectoryStep(caseService, caseService.files, ufoCasesExclusions, ufoCaseDirectoryFile, outputFunc, config);
103
- const peopleDirectoryFactory = new PeopleDirectoryStepFactory(outputFunc, config, peopleService, this.options.directoryExcluded);
104
+ const peopleRenderer = new PeopleHtmlRenderer();
105
+ const peopleDirectoryFactory = new PeopleDirectoryStepFactory(outputFunc, config, peopleService, peopleRenderer, this.options.directoryExcluded);
104
106
  const directoryOptions = this.options.directoryOptions;
105
107
  for (const directoryOption in directoryOptions) {
106
108
  directoryOptions[directoryOption] = directoryOptions[directoryOption];
@@ -173,7 +175,7 @@ export class RR0Build {
173
175
  new ClassDomReplaceCommand(sourceReplacerFactory, "source"),
174
176
  new DomReplaceCommand("time", new TimeReplacerFactory(timeReplacer)),
175
177
  new DomReplaceCommand("code", new CodeReplacerFactory()),
176
- new ClassDomReplaceCommand(new PeopleReplacerFactory(peopleService), "people"),
178
+ new ClassDomReplaceCommand(new PeopleReplacerFactory(peopleService, peopleRenderer), "people"),
177
179
  new ClassDomReplaceCommand(new PlaceReplacerFactory(), "place"),
178
180
  new ClassDomReplaceCommand(new WitnessReplacerFactory(), "temoin", "temoin1", "temoin2", "temoin3"),
179
181
  new ClassDomReplaceCommand(noteReplacerFactory, "note"),
@@ -4,6 +4,7 @@ import { HtmlRR0Context } from "../RR0Context.js";
4
4
  import { DirectoryStep, FileWriteConfig, OutputFunc } from "ssg-api";
5
5
  import { PeopleService } from "./PeopleService.js";
6
6
  import { CountryCode } from "../org/country/CountryCode.js";
7
+ import { PeopleHtmlRenderer } from "./PeopleHtmlRenderer";
7
8
  export type PeopleFilter = (p: People) => boolean;
8
9
  export declare function peopleOccupationFilter(filterOccupations: Occupation[]): PeopleFilter;
9
10
  /**
@@ -12,8 +13,9 @@ export declare function peopleOccupationFilter(filterOccupations: Occupation[]):
12
13
  export declare class PeopleDirectoryStep extends DirectoryStep {
13
14
  protected outputFunc: OutputFunc;
14
15
  protected service: PeopleService;
16
+ protected renderer: PeopleHtmlRenderer;
15
17
  protected filter: PeopleFilter;
16
- constructor(name: string, rootDirs: string[], excludedDirs: string[], templateFileName: string, outputFunc: OutputFunc, config: FileWriteConfig, service: PeopleService, filter?: PeopleFilter);
18
+ constructor(name: string, rootDirs: string[], excludedDirs: string[], templateFileName: string, outputFunc: OutputFunc, config: FileWriteConfig, service: PeopleService, renderer: PeopleHtmlRenderer, filter?: PeopleFilter);
17
19
  protected processDirs(context: HtmlRR0Context, dirNames: string[]): Promise<void>;
18
20
  protected toList(context: HtmlRR0Context, peopleList: People[], pseudoPeopleList: People[], allCountries: Set<CountryCode>, occupations: Set<Occupation>): HTMLUListElement;
19
21
  protected toListItem(context: HtmlRR0Context, people: People, pseudoPeopleList: People[], allCountries: Set<CountryCode>, occupations: Set<Occupation>): HTMLLIElement;
@@ -10,10 +10,11 @@ export function peopleOccupationFilter(filterOccupations) {
10
10
  * Scan directories for people information, then populates a template with collected data.
11
11
  */
12
12
  export class PeopleDirectoryStep extends DirectoryStep {
13
- constructor(name, rootDirs, excludedDirs, templateFileName, outputFunc, config, service, filter = (_people) => true) {
13
+ constructor(name, rootDirs, excludedDirs, templateFileName, outputFunc, config, service, renderer, filter = (_people) => true) {
14
14
  super({ rootDirs, excludedDirs, templateFileName, getOutputPath: config.getOutputPath }, name);
15
15
  this.outputFunc = outputFunc;
16
16
  this.service = service;
17
+ this.renderer = renderer;
17
18
  this.filter = filter;
18
19
  }
19
20
  async processDirs(context, dirNames) {
@@ -70,7 +71,7 @@ export class PeopleDirectoryStep extends DirectoryStep {
70
71
  return ul;
71
72
  }
72
73
  toListItem(context, people, pseudoPeopleList, allCountries, occupations) {
73
- const ref = this.service.getLink(context, people, pseudoPeopleList, allCountries, occupations
74
+ const ref = this.renderer.renderLink(context, people, pseudoPeopleList, allCountries, occupations
74
75
  /*this.filter*/ // TODO: Restore removal of already-known (as from people occupation subset) occupation
75
76
  );
76
77
  const item = context.file.document.createElement("li");
@@ -1,6 +1,7 @@
1
1
  import { FileWriteConfig, OutputFunc } from "ssg-api";
2
2
  import { PeopleService } from "./PeopleService.js";
3
3
  import { PeopleDirectoryStep } from "./PeopleDirectoryStep.js";
4
+ import { PeopleHtmlRenderer } from "./PeopleHtmlRenderer";
4
5
  export type PeopleDirectoryStepOptions = {
5
6
  root: string;
6
7
  scientists?: string;
@@ -21,8 +22,9 @@ export declare class PeopleDirectoryStepFactory {
21
22
  protected outputFunc: OutputFunc;
22
23
  protected config: FileWriteConfig;
23
24
  protected service: PeopleService;
25
+ protected renderer: PeopleHtmlRenderer;
24
26
  protected excludedDirs: string[];
25
- constructor(outputFunc: OutputFunc, config: FileWriteConfig, service: PeopleService, excludedDirs: string[]);
27
+ constructor(outputFunc: OutputFunc, config: FileWriteConfig, service: PeopleService, renderer: PeopleHtmlRenderer, excludedDirs: string[]);
26
28
  create(options: PeopleDirectoryStepOptions): Promise<PeopleDirectoryStep[]>;
27
29
  createLetters(): Promise<PeopleDirectoryStep[]>;
28
30
  createAll(dirs: string[], templateFileName: string): PeopleDirectoryStep;
@@ -7,10 +7,11 @@ import path from "path";
7
7
  * Scan directories for people information, then populates a template with collected data.
8
8
  */
9
9
  export class PeopleDirectoryStepFactory {
10
- constructor(outputFunc, config, service, excludedDirs) {
10
+ constructor(outputFunc, config, service, renderer, excludedDirs) {
11
11
  this.outputFunc = outputFunc;
12
12
  this.config = config;
13
13
  this.service = service;
14
+ this.renderer = renderer;
14
15
  this.excludedDirs = excludedDirs;
15
16
  }
16
17
  async create(options) {
@@ -66,46 +67,46 @@ export class PeopleDirectoryStepFactory {
66
67
  for (const peopleLetterFile of peopleLetterFiles) {
67
68
  const c = peopleLetterFile.charAt(peopleLetterFile.length - 1);
68
69
  const peopleDir = `people/${c}/`;
69
- const peopleDirectoryStep = new PeopleDirectoryStep(`directory of people with name starting with "${c}"`, [peopleDir], [], path.join(peopleDir, "index.html"), this.outputFunc, this.config, this.service, (p) => p.dirName.startsWith(peopleDir));
70
+ const peopleDirectoryStep = new PeopleDirectoryStep(`directory of people with name starting with "${c}"`, [peopleDir], [], path.join(peopleDir, "index.html"), this.outputFunc, this.config, this.service, this.renderer, (p) => p.dirName.startsWith(peopleDir));
70
71
  letterDirectorySteps.push(peopleDirectoryStep);
71
72
  }
72
73
  return letterDirectorySteps;
73
74
  }
74
75
  createAll(dirs, templateFileName) {
75
- return new PeopleDirectoryStep("all people directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service);
76
+ return new PeopleDirectoryStep("all people directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer);
76
77
  }
77
78
  createMilitary(dirs, templateFileName) {
78
- return new PeopleDirectoryStep("military people directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.military]));
79
+ return new PeopleDirectoryStep("military people directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.military]));
79
80
  }
80
81
  createPoliticians(dirs, templateFileName, rulersTemplateFileName) {
81
82
  const steps = [
82
- new PeopleDirectoryStep("politicians directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.politician]))
83
+ new PeopleDirectoryStep("politicians directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.politician]))
83
84
  ];
84
85
  if (rulersTemplateFileName) {
85
- steps.push(new PeopleDirectoryStep("politician leaders directories", dirs, this.excludedDirs, rulersTemplateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.leader])));
86
+ steps.push(new PeopleDirectoryStep("politician leaders directories", dirs, this.excludedDirs, rulersTemplateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.leader])));
86
87
  }
87
88
  return steps;
88
89
  }
89
90
  createSoftwareEngineers(dirs, templateFileName) {
90
- return new PeopleDirectoryStep("software engineers directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.softwareEngineer]));
91
+ return new PeopleDirectoryStep("software engineers directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.softwareEngineer]));
91
92
  }
92
93
  createPilots(dirs, templateFileName) {
93
- return new PeopleDirectoryStep("pilots directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.astronaut, Occupation.pilot]));
94
+ return new PeopleDirectoryStep("pilots directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.astronaut, Occupation.pilot]));
94
95
  }
95
96
  createContactees(dirs, templateFileName) {
96
- return new PeopleDirectoryStep("contactees directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.contactee]));
97
+ return new PeopleDirectoryStep("contactees directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.contactee]));
97
98
  }
98
99
  createAstronomers(dirs, templateFileName) {
99
- return new PeopleDirectoryStep("astronomers directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.astronomer]));
100
+ return new PeopleDirectoryStep("astronomers directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.astronomer]));
100
101
  }
101
102
  createUfoWitnesses(dirs, templateFileName) {
102
- return new PeopleDirectoryStep(`UFO witnesses directories`, dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.ufoWitness, Occupation.ufoWitness2, Occupation.abductee, Occupation.contactee]));
103
+ return new PeopleDirectoryStep(`UFO witnesses directories`, dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.ufoWitness, Occupation.ufoWitness2, Occupation.abductee, Occupation.contactee]));
103
104
  }
104
105
  createUfologists(dirs, templateFileName) {
105
- return new PeopleDirectoryStep("ufologists directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([Occupation.ufologist]));
106
+ return new PeopleDirectoryStep("ufologists directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([Occupation.ufologist]));
106
107
  }
107
108
  createScientists(dirs, templateFileName) {
108
- return new PeopleDirectoryStep("scientists directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, peopleOccupationFilter([
109
+ return new PeopleDirectoryStep("scientists directories", dirs, this.excludedDirs, templateFileName, this.outputFunc, this.config, this.service, this.renderer, peopleOccupationFilter([
109
110
  Occupation.anthropologist, Occupation.astronomer, Occupation.astrophysicist, Occupation.archeologist,
110
111
  Occupation.biochemist, Occupation.biologist, Occupation.biophysicist, Occupation.botanist,
111
112
  Occupation.chemist,
@@ -3,11 +3,11 @@ import { RR0EventFactory, TypedDataFactory } from "@rr0/data";
3
3
  import { PeopleJson } from "./PeopleJson";
4
4
  export declare class PeopleFactory extends TypedDataFactory<People, PeopleJson> {
5
5
  constructor(eventFactory: RR0EventFactory);
6
+ parse(peopleJson: PeopleJson): People;
6
7
  /**
7
8
  * Determine people name from directory name.
8
9
  *
9
- * @param dirName
10
+ * @param peopleJson
10
11
  */
11
- createFromDirName(dirName: string): People;
12
- parse(peopleJson: PeopleJson): People;
12
+ protected completeFromDirName(peopleJson: PeopleJson): PeopleJson;
13
13
  }
@@ -1,31 +1,16 @@
1
1
  import { People } from "./People.js";
2
- import path from "path";
3
- import { StringUtil } from "../util/index.js";
4
2
  import { TypedDataFactory } from "@rr0/data";
3
+ import { StringUtil } from "../util";
4
+ import path from "path";
5
5
  export class PeopleFactory extends TypedDataFactory {
6
6
  constructor(eventFactory) {
7
7
  super(eventFactory, "people");
8
8
  }
9
- /**
10
- * Determine people name from directory name.
11
- *
12
- * @param dirName
13
- */
14
- createFromDirName(dirName) {
15
- const lastSlash = dirName.lastIndexOf("/");
16
- const lastDir = dirName.substring(lastSlash + 1);
17
- const title = StringUtil.camelToText(lastDir);
18
- const firstSpace = title.indexOf(" ");
19
- const lastName = title.substring(0, firstSpace);
20
- const firstNameStr = title.substring(firstSpace + 1);
21
- const firstNames = firstNameStr.split(" ");
22
- const id = path.basename(dirName);
23
- return new People(firstNames, lastName, undefined, undefined, undefined, false, undefined, undefined, undefined, id, dirName);
24
- }
25
9
  parse(peopleJson) {
26
- const people = this.createFromDirName(peopleJson.dirName);
10
+ peopleJson = this.completeFromDirName(peopleJson);
11
+ const peopleData = super.parse(peopleJson);
12
+ const people = new People(peopleData.firstNames, peopleData.lastName, peopleData.occupations, peopleData.occupations, peopleData.countries, peopleData.discredited, peopleData.birthTime, peopleData.deathTime, peopleData.gender, peopleData.id, peopleData.dirName, peopleData.image, peopleData.url, peopleData.events);
27
13
  peopleJson.name = people.name;
28
- Object.assign(people, super.parse(peopleJson));
29
14
  let title = peopleJson.title;
30
15
  let qualifier;
31
16
  if (title) {
@@ -49,7 +34,33 @@ export class PeopleFactory extends TypedDataFactory {
49
34
  }
50
35
  }
51
36
  people.title = people.firstAndLastName + (qualifier ? ` (${qualifier})` : "");
52
- Object.assign(people, { events: peopleJson.events.map(this.eventFactory.parse) });
53
37
  return people;
54
38
  }
39
+ /**
40
+ * Determine people name from directory name.
41
+ *
42
+ * @param peopleJson
43
+ */
44
+ completeFromDirName(peopleJson) {
45
+ const result = { ...peopleJson };
46
+ const dirName = peopleJson.dirName;
47
+ if (dirName) {
48
+ const lastSlash = dirName.lastIndexOf("/");
49
+ const lastDir = dirName.substring(lastSlash + 1);
50
+ const title = StringUtil.camelToText(lastDir);
51
+ const firstSpace = title.indexOf(" ");
52
+ const lastName = title.substring(0, firstSpace);
53
+ result.lastName = peopleJson.lastName || lastName;
54
+ const firstNameStr = title.substring(firstSpace + 1);
55
+ const firstNames = firstNameStr.split(" ");
56
+ result.firstNames = peopleJson.firstNames || firstNames;
57
+ const id = path.basename(dirName);
58
+ result.id = peopleJson.id || id;
59
+ result.title = `${firstNames.join(" ")} ${lastName}`;
60
+ }
61
+ else {
62
+ result.dirName = "";
63
+ }
64
+ return result;
65
+ }
55
66
  }
@@ -1,81 +1,29 @@
1
- import { PeopleService } from "./PeopleService.js";
2
- import { People } from "./People.js";
3
1
  import { describe, expect, test } from "@javarome/testscript";
4
- import { rr0TestUtil } from "../test/index.js";
5
- import path from "path";
2
+ import { PeopleFactory } from "./PeopleFactory";
3
+ import { RR0EventFactory } from "@rr0/data";
4
+ import { People } from "./People";
5
+ import { Occupation } from "./Occupation";
6
+ import { CountryCode } from "../org";
6
7
  describe("PeopleFactory", () => {
7
- const dataService = rr0TestUtil.dataService;
8
- const peopleRoot = "src/people";
9
- const peopleFiles = [
10
- path.join(peopleRoot, "a/Aristote"),
11
- path.join(peopleRoot, "b/BeauJerome"),
12
- path.join(peopleRoot, "b/BeauJeromePierre"),
13
- path.join(peopleRoot, "c/CondonEdwardU"),
14
- path.join(peopleRoot, "h/HynekJosefAllen"),
15
- path.join(peopleRoot, "v/VonBraunWerner")
16
- ];
17
- const factory = new PeopleService(dataService, rr0TestUtil.peopleFactory, peopleFiles, rr0TestUtil.time.getService());
18
- test("build people with one first name", () => {
19
- expect(factory.createFromFullName("Jérôme Beau")).toEqual(new People(["Jérôme"], "Beau", [], [], [], false, undefined, undefined, undefined, "people/b/BeauJerome"));
20
- });
8
+ const eventFactory = new RR0EventFactory();
9
+ const factory = new PeopleFactory(eventFactory);
21
10
  test("build people with two first names", () => {
22
- const people = factory.createFromFullName("Jérôme Pierre Beau");
23
- expect(people.title).toBe("Beau, Jérôme Pierre");
24
- expect(people.countries).toBe([]);
25
- expect(people.lastName).toBe("Beau");
26
- expect(people.firstNames).toBe(["Jérôme", "Pierre"]);
27
- expect(people.hoax).toBe(false);
28
- expect(people.discredited).toBe(false);
29
- expect(people.dirName).toBe("people/b/BeauJeromePierre");
30
- expect(people.occupations).toBe([]);
31
- expect(people.pseudonyms).toBe([]);
32
- });
33
- test("build people with two last names", () => {
34
- const people = factory.createFromFullName("Werner VonBraun");
35
- expect(people.title).toBe("Von Braun, Werner");
36
- expect(people.countries).toBe([]);
37
- expect(people.lastName).toBe("VonBraun");
38
- expect(people.firstNames).toBe(["Werner"]);
39
- expect(people.hoax).toBe(false);
40
- expect(people.discredited).toBe(false);
41
- expect(people.dirName).toBe("people/v/VonBraunWerner");
42
- expect(people.occupations).toBe([]);
43
- expect(people.pseudonyms).toBe([]);
44
- });
45
- test("build people with one initial first names", () => {
46
- const people = factory.createFromFullName("Edward U. Condon");
47
- expect(people.title).toBe("Condon, Edward U.");
48
- expect(people.countries).toBe([]);
49
- expect(people.lastName).toBe("Condon");
50
- expect(people.firstNames).toBe(["Edward", "U."]);
51
- expect(people.hoax).toBe(false);
52
- expect(people.discredited).toBe(false);
53
- expect(people.dirName).toBe("people/c/CondonEdwardU");
54
- expect(people.occupations).toBe([]);
55
- expect(people.pseudonyms).toBe([]);
56
- });
57
- test("build people with last name first", () => {
58
- const people = factory.createFromFullName("Hynek, Josef Allen");
59
- expect(people.title).toBe("Hynek, Josef Allen");
60
- expect(people.countries).toBe([]);
61
- expect(people.lastName).toBe("Hynek");
62
- expect(people.firstNames).toBe(["Josef", "Allen"]);
63
- expect(people.hoax).toBe(false);
64
- expect(people.discredited).toBe(false);
65
- expect(people.dirName).toBe("people/h/HynekJosefAllen");
66
- expect(people.occupations).toBe([]);
67
- expect(people.pseudonyms).toBe([]);
68
- });
69
- test("Single name", () => {
70
- const people = factory.createFromFullName("Aristote");
71
- expect(people.title).toBe("Aristote");
72
- expect(people.countries).toBe([]);
73
- expect(people.lastName).toBe("Aristote");
74
- expect(people.firstNames).toBe([]);
75
- expect(people.hoax).toBe(false);
76
- expect(people.discredited).toBe(false);
77
- expect(people.dirName).toBe("people/a/Aristote");
78
- expect(people.occupations).toBe([]);
79
- expect(people.pseudonyms).toBe([]);
11
+ const villaJson = {
12
+ "birthTime": "1916-09-24",
13
+ "deathTime": "1980-11-22",
14
+ "occupations": [
15
+ "contactee",
16
+ "mechanic"
17
+ ],
18
+ "countries": [
19
+ "us"
20
+ ],
21
+ "pseudonyms": [
22
+ "Paul Villa"
23
+ ]
24
+ };
25
+ const parsed = factory.parse(villaJson);
26
+ const expected = new People(undefined, undefined, ["Paul Villa"], [Occupation.contactee, Occupation.mechanic], [CountryCode.us], true);
27
+ expect(parsed).toEqual(expected);
80
28
  });
81
29
  });
@@ -0,0 +1,9 @@
1
+ import { People } from "./People.js";
2
+ import { HtmlRR0Context } from "../RR0Context.js";
3
+ import { CountryCode } from "../org/index.js";
4
+ import { Occupation } from "./Occupation.js";
5
+ export declare class PeopleHtmlRenderer {
6
+ protected defaultPreviewFileNames: string[];
7
+ constructor(defaultPreviewFileNames?: string[]);
8
+ renderLink(context: HtmlRR0Context, people: People, pseudoPeopleList: People[], allCountries: Set<CountryCode>, occupations: Set<Occupation>, filterOccupations?: Occupation[], content?: string): HTMLElement;
9
+ }
@@ -0,0 +1,93 @@
1
+ import path from "path";
2
+ import { Gender } from "@rr0/common";
3
+ export class PeopleHtmlRenderer {
4
+ constructor(defaultPreviewFileNames = ["portrait.jpg", "portrait.gif", "portrait.png", "portrait.webp"]) {
5
+ this.defaultPreviewFileNames = defaultPreviewFileNames;
6
+ }
7
+ renderLink(context, people, pseudoPeopleList, allCountries, occupations, filterOccupations = [], content) {
8
+ var _a, _b, _c;
9
+ const dirName = people.dirName;
10
+ const events = people.events;
11
+ const titles = [];
12
+ const classList = ["data-resolved", "people-resolved"];
13
+ if (pseudoPeopleList.indexOf(people) >= 0 || people.pseudonyms.includes(content)) {
14
+ classList.push("pseudonym");
15
+ titles.push(`(pseudonyme de ${people.firstAndLastName})`);
16
+ }
17
+ if (people.hoax) {
18
+ classList.push("canular");
19
+ }
20
+ const birthTimeStr = (_a = people.birthTime) === null || _a === void 0 ? void 0 : _a.year.toString();
21
+ const deathTimeStr = (_b = people.deathTime) === null || _b === void 0 ? void 0 : _b.year.toString();
22
+ if (people.isDeceased()) {
23
+ classList.push("deceased");
24
+ }
25
+ if (birthTimeStr || deathTimeStr) {
26
+ const timeStr = birthTimeStr ? deathTimeStr ? `${birthTimeStr}-${deathTimeStr}` : `${birthTimeStr}-` : `-${deathTimeStr}`;
27
+ titles.push(timeStr);
28
+ }
29
+ const age = people.getAge();
30
+ if (age) {
31
+ titles.push(`${age} ans`);
32
+ }
33
+ const countries = people.countries;
34
+ if (countries) {
35
+ for (const country of countries) {
36
+ allCountries.add(country);
37
+ const countryLabel = (_c = context.messages.country[country]) === null || _c === void 0 ? void 0 : _c.title;
38
+ if (!countryLabel) {
39
+ throw new Error(`No title for country "${country}"`);
40
+ }
41
+ titles.push(countryLabel);
42
+ classList.push(`country-${country}`);
43
+ }
44
+ }
45
+ const gender = people.gender || Gender.male;
46
+ for (const occupation of people.occupations) {
47
+ if (filterOccupations.length > 1 || !filterOccupations.includes(occupation)) {
48
+ occupations.add(occupation);
49
+ const occupationMsg = context.messages.people.occupation[occupation];
50
+ if (!occupationMsg) {
51
+ throw Error(`No message to translate occupation "${occupation}" in ${context.locale}, as specified in ${people.dirName}/people*.json`);
52
+ }
53
+ classList.push(`occupation-${occupation}`);
54
+ titles.push(occupationMsg(gender));
55
+ }
56
+ }
57
+ const text = content || people.lastAndFirstName;
58
+ const doc = context.file.document;
59
+ const link = doc.createElement("a");
60
+ link.innerHTML = text;
61
+ link.href = `/${dirName}/`;
62
+ if (people.discredited) {
63
+ link.append(" 🤥");
64
+ titles.push("discrédité");
65
+ }
66
+ const elem = doc.createElement("span");
67
+ if (titles.length > 0) {
68
+ elem.title = titles.join(", ");
69
+ }
70
+ if (classList.length > 0) {
71
+ elem.classList.add(...classList);
72
+ }
73
+ let portraitUrl = people.image;
74
+ const imageEvents = events.filter(event => event.eventType === "image");
75
+ if (!portraitUrl) {
76
+ const portraitEvent = imageEvents.find(event => this.defaultPreviewFileNames.includes(event.url));
77
+ if (portraitEvent) {
78
+ portraitUrl = path.join("/", people.dirName, portraitEvent.url);
79
+ }
80
+ }
81
+ if (portraitUrl) {
82
+ const portraitElem = doc.createElement("img");
83
+ portraitElem.src = path.join("/", portraitUrl);
84
+ portraitElem.alt = people.lastAndFirstName;
85
+ portraitElem.className = "portrait";
86
+ portraitElem.loading = "lazy";
87
+ portraitElem.width = 75;
88
+ link.append(portraitElem);
89
+ }
90
+ elem.append(link);
91
+ return elem;
92
+ }
93
+ }
@@ -1,39 +1,34 @@
1
- import { Occupation } from "./Occupation.js";
2
- import { Gender } from "@rr0/common";
3
- import { CountryCode } from "../org/country/CountryCode.js";
4
- import { Level2Date as EdtfDate } from "@rr0/time";
5
1
  import { RR0DataJson } from "@rr0/data/dist/RR0DataJson";
6
2
  import { RR0EventJson } from "@rr0/data/dist/event/RR0EventJson";
7
- export declare class PeopleJson implements RR0DataJson {
8
- readonly type = "people";
9
- title: string;
10
- name: string;
3
+ export type PeopleJson = RR0DataJson & {
4
+ type?: "people";
5
+ title?: string;
6
+ name?: string;
11
7
  /**
12
8
  * The people actually doesn't exist.
13
9
  */
14
- hoax: boolean;
15
- lastAndFirstName: string;
16
- firstNames: string[];
17
- lastName: string;
18
- pseudonyms: string[];
19
- occupations: Occupation[];
20
- countries: CountryCode[];
10
+ hoax?: boolean;
11
+ firstNames?: string[];
12
+ lastName?: string;
13
+ pseudonyms?: string[];
14
+ occupations?: string[];
15
+ countries?: string[];
21
16
  /**
22
17
  * The people has been caught lying or has confessed a hoax.
23
18
  */
24
- discredited: boolean;
19
+ discredited?: boolean;
25
20
  /**
26
21
  * @deprecated Use a "birth"-typed event instead.
27
22
  */
28
- birthTime?: EdtfDate;
23
+ birthTime?: string;
29
24
  /**
30
25
  * @deprecated Use a "death"-typed event instead.
31
26
  */
32
- deathTime?: EdtfDate;
33
- gender?: Gender;
34
- id: string;
27
+ deathTime?: string;
28
+ gender?: "male" | "female";
29
+ id?: string;
35
30
  dirName?: string;
36
31
  image?: string;
37
32
  url?: string;
38
- events: RR0EventJson[];
39
- }
33
+ events?: RR0EventJson[];
34
+ };
@@ -1,19 +1 @@
1
- export class PeopleJson {
2
- constructor() {
3
- this.type = "people";
4
- /**
5
- * The people actually doesn't exist.
6
- */
7
- this.hoax = false;
8
- this.firstNames = [];
9
- this.lastName = "";
10
- this.pseudonyms = [];
11
- this.occupations = [];
12
- this.countries = [];
13
- /**
14
- * The people has been caught lying or has confessed a hoax.
15
- */
16
- this.discredited = false;
17
- this.events = [];
18
- }
19
- }
1
+ export {};
@@ -4,12 +4,14 @@ import { ClassDomReplaceCommand } from "ssg-api";
4
4
  import { describe, expect, test } from "@javarome/testscript";
5
5
  import { PeopleService } from "./PeopleService.js";
6
6
  import path from "path";
7
+ import { PeopleHtmlRenderer } from "./PeopleHtmlRenderer";
7
8
  describe("ClassDomReplaceCommand", () => {
8
9
  test("replaces", async () => {
9
10
  const peopleRoot = "src/people";
10
11
  const peopleFiles = [path.join(peopleRoot, "b/BeauJerome")];
12
+ const peopleRenderer = new PeopleHtmlRenderer();
11
13
  const peopleService = new PeopleService(rr0TestUtil.dataService, rr0TestUtil.peopleFactory, peopleFiles, rr0TestUtil.time.getService());
12
- const command = new ClassDomReplaceCommand(new PeopleReplacerFactory(peopleService), "people");
14
+ const command = new ClassDomReplaceCommand(new PeopleReplacerFactory(peopleService, peopleRenderer), "people");
13
15
  const context = rr0TestUtil.time.newHtmlContext("1/9/9/0/08/index.html", `<span class="people">Jérôme Beau</span>`);
14
16
  await command.execute(context);
15
17
  expect(context.file.contents).toBe(`<html><head></head><body><span title="1972-, 50 ans, France, ufologue, Informaticien" class="country-fr occupation-ufologist occupation-softwareEngineer" translate="no"><a href="/src/people/b/BeauJerome/">Jérôme Beau</a></span></body></html>`);
@@ -1,8 +1,10 @@
1
1
  import { PeopleService } from "./PeopleService.js";
2
2
  import { DomReplacement } from "../time/DomReplacement.js";
3
3
  import { HtmlRR0Context } from "../RR0Context.js";
4
+ import { PeopleHtmlRenderer } from "./PeopleHtmlRenderer";
4
5
  export declare class PeopleReplacer implements DomReplacement<HtmlRR0Context> {
5
6
  protected service: PeopleService;
6
- constructor(service: PeopleService);
7
+ protected renderer: PeopleHtmlRenderer;
8
+ constructor(service: PeopleService, renderer: PeopleHtmlRenderer);
7
9
  replacement(context: HtmlRR0Context, element: HTMLElement): Promise<HTMLElement>;
8
10
  }
@@ -1,6 +1,7 @@
1
1
  export class PeopleReplacer {
2
- constructor(service) {
2
+ constructor(service, renderer) {
3
3
  this.service = service;
4
+ this.renderer = renderer;
4
5
  }
5
6
  async replacement(context, element) {
6
7
  const title = element.title;
@@ -29,7 +30,7 @@ export class PeopleReplacer {
29
30
  const allCountries = new Set();
30
31
  const occupations = new Set;
31
32
  const peopl = peopleList[0] || people;
32
- replacement = this.service.getLink(context, peopl, pseudoPeopleList, allCountries, occupations, [], peopleContent);
33
+ replacement = this.renderer.renderLink(context, peopl, pseudoPeopleList, allCountries, occupations, [], peopleContent);
33
34
  }
34
35
  else {
35
36
  const span = context.file.document.createElement("span");
@@ -5,6 +5,7 @@ import { describe, expect, test } from "@javarome/testscript";
5
5
  import { PeopleFactory } from "./PeopleFactory.js";
6
6
  import path from "path";
7
7
  import { AllDataService, RR0EventFactory } from "@rr0/data";
8
+ import { PeopleHtmlRenderer } from "./PeopleHtmlRenderer";
8
9
  describe("PeopleReplacer", () => {
9
10
  const peopleRoot = "src/people";
10
11
  const peopleFiles = [
@@ -22,7 +23,9 @@ describe("PeopleReplacer", () => {
22
23
  }
23
24
  test("ignore brackets", async () => {
24
25
  const dataService = new AllDataService([peopleFactory]);
25
- const replacer = new PeopleReplacer(new PeopleService(dataService, peopleFactory, peopleFiles, rr0TestUtil.time.getService()));
26
+ const peopleService = new PeopleService(dataService, peopleFactory, peopleFiles, rr0TestUtil.time.getService());
27
+ const peopleRenderer = new PeopleHtmlRenderer();
28
+ const replacer = new PeopleReplacer(peopleService, peopleRenderer);
26
29
  const context = rr0TestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
27
30
  {
28
31
  const lastnameFirstElement = createPeopleElement(context, "Hynek, Josef Allen (Northwestern University, Evanston, Illinois)");
@@ -37,7 +40,9 @@ describe("PeopleReplacer", () => {
37
40
  });
38
41
  test("replace people tags", async () => {
39
42
  const dataService = new AllDataService([peopleFactory]);
40
- const replacer = new PeopleReplacer(new PeopleService(dataService, peopleFactory, peopleFiles, rr0TestUtil.time.getService()));
43
+ const peopleService = new PeopleService(dataService, peopleFactory, peopleFiles, rr0TestUtil.time.getService());
44
+ const peopleRenderer = new PeopleHtmlRenderer();
45
+ const replacer = new PeopleReplacer(peopleService, peopleRenderer);
41
46
  const context = rr0TestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
42
47
  {
43
48
  const peopleWithTitle = createPeopleElement(context, "Ronald Reagan", "Ronald Wilson Reagan");
@@ -2,13 +2,15 @@ import { PeopleReplacer } from "./PeopleReplacer.js";
2
2
  import { DomReplacer, ReplacerFactory } from "ssg-api";
3
3
  import { PeopleService } from "./PeopleService.js";
4
4
  import { HtmlRR0Context } from "../RR0Context.js";
5
+ import { PeopleHtmlRenderer } from "./PeopleHtmlRenderer";
5
6
  /**
6
7
  * Creates replacers for people HTML in a given context.
7
8
  */
8
9
  export declare class PeopleReplacerFactory implements ReplacerFactory<DomReplacer> {
9
10
  protected service: PeopleService;
11
+ protected renderer: PeopleHtmlRenderer;
10
12
  protected singleton?: PeopleReplacer;
11
- constructor(service: PeopleService);
13
+ constructor(service: PeopleService, renderer: PeopleHtmlRenderer);
12
14
  create(context: HtmlRR0Context): Promise<DomReplacer>;
13
15
  protected getInstance(): Promise<PeopleReplacer>;
14
16
  }
@@ -3,8 +3,9 @@ import { PeopleReplacer } from "./PeopleReplacer.js";
3
3
  * Creates replacers for people HTML in a given context.
4
4
  */
5
5
  export class PeopleReplacerFactory {
6
- constructor(service) {
6
+ constructor(service, renderer) {
7
7
  this.service = service;
8
+ this.renderer = renderer;
8
9
  }
9
10
  async create(context) {
10
11
  const instance = await this.getInstance();
@@ -16,7 +17,7 @@ export class PeopleReplacerFactory {
16
17
  }
17
18
  async getInstance() {
18
19
  if (!this.singleton) {
19
- this.singleton = new PeopleReplacer(this.service);
20
+ this.singleton = new PeopleReplacer(this.service, this.renderer);
20
21
  }
21
22
  return this.singleton;
22
23
  }
@@ -1,7 +1,4 @@
1
1
  import { People } from "./People.js";
2
- import { HtmlRR0Context } from "../RR0Context.js";
3
- import { CountryCode } from "../org/index.js";
4
- import { Occupation } from "./Occupation.js";
5
2
  import { PeopleFactory } from "./PeopleFactory.js";
6
3
  import { AbstractDataService, AllDataService } from "@rr0/data";
7
4
  import { TimeService } from "../time";
@@ -15,5 +12,4 @@ export declare class PeopleService extends AbstractDataService<People, PeopleJso
15
12
  getAll(): Promise<People[]>;
16
13
  getFromDirs(dirNames: string[]): Promise<People[]>;
17
14
  getFromDir(dirName: string): Promise<People[]>;
18
- getLink(context: HtmlRR0Context, people: People, pseudoPeopleList: People[], allCountries: Set<CountryCode>, occupations: Set<Occupation>, filterOccupations?: Occupation[], content?: string): HTMLElement;
19
15
  }
@@ -1,7 +1,5 @@
1
1
  import { People } from "./People.js";
2
- import path from "path";
3
- import { Gender } from "@rr0/common";
4
- import { AbstractDataFactory, AbstractDataService } from "@rr0/data";
2
+ import { AbstractDataService } from "@rr0/data";
5
3
  export class PeopleService extends AbstractDataService {
6
4
  constructor(dataService, peopleFactory, files, timeService) {
7
5
  super(dataService, peopleFactory, files);
@@ -34,13 +32,7 @@ export class PeopleService extends AbstractDataService {
34
32
  if (this.files.indexOf(dirName) < 0) {
35
33
  dirName = undefined;
36
34
  }
37
- let created;
38
- if (dirName && !lastName && (firstNames === null || firstNames === void 0 ? void 0 : firstNames.length) <= 0) {
39
- created = this.peopleFactory.createFromDirName(dirName);
40
- }
41
- else {
42
- created = new People(firstNames, lastName, undefined, undefined, undefined, false, undefined, undefined, undefined, undefined, dirName);
43
- }
35
+ const created = this.peopleFactory.parse({ firstNames, lastName, dirName });
44
36
  this.cache.set(lastName, created);
45
37
  return created;
46
38
  }
@@ -59,90 +51,4 @@ export class PeopleService extends AbstractDataService {
59
51
  const fileSpec = ["people*.json"];
60
52
  return this.dataService.getFromDir(dirName, ["people", undefined], fileSpec);
61
53
  }
62
- getLink(context, people, pseudoPeopleList, allCountries, occupations, filterOccupations = [], content) {
63
- var _a, _b, _c;
64
- const dirName = people.dirName;
65
- const events = people.events;
66
- const titles = [];
67
- const classList = ["data-resolved", "people-resolved"];
68
- if (pseudoPeopleList.indexOf(people) >= 0 || people.pseudonyms.includes(content)) {
69
- classList.push("pseudonym");
70
- titles.push(`(pseudonyme de ${people.firstAndLastName})`);
71
- }
72
- if (people.hoax) {
73
- classList.push("canular");
74
- }
75
- const birthTimeStr = (_a = people.birthTime) === null || _a === void 0 ? void 0 : _a.year.toString();
76
- const deathTimeStr = (_b = people.deathTime) === null || _b === void 0 ? void 0 : _b.year.toString();
77
- if (people.isDeceased()) {
78
- classList.push("deceased");
79
- }
80
- if (birthTimeStr || deathTimeStr) {
81
- const timeStr = birthTimeStr ? deathTimeStr ? `${birthTimeStr}-${deathTimeStr}` : `${birthTimeStr}-` : `-${deathTimeStr}`;
82
- titles.push(timeStr);
83
- }
84
- const age = people.getAge();
85
- if (age) {
86
- titles.push(`${age} ans`);
87
- }
88
- const countries = people.countries;
89
- if (countries) {
90
- for (const country of countries) {
91
- allCountries.add(country);
92
- const countryLabel = (_c = context.messages.country[country]) === null || _c === void 0 ? void 0 : _c.title;
93
- if (!countryLabel) {
94
- throw new Error(`No title for country "${country}"`);
95
- }
96
- titles.push(countryLabel);
97
- classList.push(`country-${country}`);
98
- }
99
- }
100
- const gender = people.gender || Gender.male;
101
- for (const occupation of people.occupations) {
102
- if (filterOccupations.length > 1 || !filterOccupations.includes(occupation)) {
103
- occupations.add(occupation);
104
- const occupationMsg = context.messages.people.occupation[occupation];
105
- if (!occupationMsg) {
106
- throw Error(`No message to translate occupation "${occupation}" in ${context.locale}, as specified in ${people.dirName}/people*.json`);
107
- }
108
- classList.push(`occupation-${occupation}`);
109
- titles.push(occupationMsg(gender));
110
- }
111
- }
112
- const text = content || people.lastAndFirstName;
113
- const doc = context.file.document;
114
- const link = doc.createElement("a");
115
- link.innerHTML = text;
116
- link.href = `/${dirName}/`;
117
- if (people.discredited) {
118
- link.append(" 🤥");
119
- titles.push("discrédité");
120
- }
121
- const elem = doc.createElement("span");
122
- if (titles.length > 0) {
123
- elem.title = titles.join(", ");
124
- }
125
- if (classList.length > 0) {
126
- elem.classList.add(...classList);
127
- }
128
- let portraitUrl = people.image;
129
- const imageEvents = events.filter(event => event.eventType === "image");
130
- if (!portraitUrl) {
131
- const portraitEvent = imageEvents.find(event => AbstractDataFactory.defaultImageFileNames.includes(event.url));
132
- if (portraitEvent) {
133
- portraitUrl = path.join("/", people.dirName, portraitEvent.url);
134
- }
135
- }
136
- if (portraitUrl) {
137
- const portraitElem = doc.createElement("img");
138
- portraitElem.src = path.join("/", portraitUrl);
139
- portraitElem.alt = people.lastAndFirstName;
140
- portraitElem.className = "portrait";
141
- portraitElem.loading = "lazy";
142
- portraitElem.width = 75;
143
- link.append(portraitElem);
144
- }
145
- elem.append(link);
146
- return elem;
147
- }
148
54
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import { PeopleService } from "./PeopleService.js";
2
+ import { People } from "./People.js";
3
+ import { describe, expect, test } from "@javarome/testscript";
4
+ import { rr0TestUtil } from "../test/index.js";
5
+ import path from "path";
6
+ describe("PeopleService", () => {
7
+ const dataService = rr0TestUtil.dataService;
8
+ const peopleRoot = "test/people";
9
+ const peopleFiles = [
10
+ path.join(peopleRoot, "a/Aristote"),
11
+ path.join(peopleRoot, "b/BeauJerome"),
12
+ path.join(peopleRoot, "b/BeauJeromePierre"),
13
+ path.join(peopleRoot, "c/CondonEdwardU"),
14
+ path.join(peopleRoot, "h/HynekJosefAllen"),
15
+ path.join(peopleRoot, "v/VonBraunWerner"),
16
+ path.join("test/science/crypto/ufo/enquete/dossier", "Villa")
17
+ ];
18
+ const service = new PeopleService(dataService, rr0TestUtil.peopleFactory, peopleFiles, rr0TestUtil.time.getService());
19
+ test("build people with one first name", () => {
20
+ expect(service.createFromFullName("Jérôme Beau")).toEqual(new People(["Jérôme"], "Beau", [], [], [], false, undefined, undefined, undefined, "people/b/BeauJerome"));
21
+ });
22
+ test("build people with two first names", () => {
23
+ const people = service.createFromFullName("Jérôme Pierre Beau");
24
+ expect(people.title).toBe("Beau, Jérôme Pierre");
25
+ expect(people.countries).toBe([]);
26
+ expect(people.lastName).toBe("Beau");
27
+ expect(people.firstNames).toBe(["Jérôme", "Pierre"]);
28
+ expect(people.hoax).toBe(false);
29
+ expect(people.discredited).toBe(false);
30
+ expect(people.dirName).toBe("people/b/BeauJeromePierre");
31
+ expect(people.occupations).toBe([]);
32
+ expect(people.pseudonyms).toBe([]);
33
+ });
34
+ test("build people with two last names", () => {
35
+ const people = service.createFromFullName("Werner VonBraun");
36
+ expect(people.title).toBe("Von Braun, Werner");
37
+ expect(people.countries).toBe([]);
38
+ expect(people.lastName).toBe("VonBraun");
39
+ expect(people.firstNames).toBe(["Werner"]);
40
+ expect(people.hoax).toBe(false);
41
+ expect(people.discredited).toBe(false);
42
+ expect(people.dirName).toBe("people/v/VonBraunWerner");
43
+ expect(people.occupations).toBe([]);
44
+ expect(people.pseudonyms).toBe([]);
45
+ });
46
+ test("build people with one initial first names", () => {
47
+ const people = service.createFromFullName("Edward U. Condon");
48
+ expect(people.title).toBe("Condon, Edward U.");
49
+ expect(people.countries).toBe([]);
50
+ expect(people.lastName).toBe("Condon");
51
+ expect(people.firstNames).toBe(["Edward", "U."]);
52
+ expect(people.hoax).toBe(false);
53
+ expect(people.discredited).toBe(false);
54
+ expect(people.dirName).toBe("people/c/CondonEdwardU");
55
+ expect(people.occupations).toBe([]);
56
+ expect(people.pseudonyms).toBe([]);
57
+ });
58
+ test("build people with last name first", () => {
59
+ const people = service.createFromFullName("Hynek, Josef Allen");
60
+ expect(people.title).toBe("Hynek, Josef Allen");
61
+ expect(people.countries).toBe([]);
62
+ expect(people.lastName).toBe("Hynek");
63
+ expect(people.firstNames).toBe(["Josef", "Allen"]);
64
+ expect(people.hoax).toBe(false);
65
+ expect(people.discredited).toBe(false);
66
+ expect(people.dirName).toBe("people/h/HynekJosefAllen");
67
+ expect(people.occupations).toBe([]);
68
+ expect(people.pseudonyms).toBe([]);
69
+ });
70
+ test("Single name", () => {
71
+ const people = service.createFromFullName("Aristote");
72
+ expect(people.title).toBe("Aristote");
73
+ expect(people.countries).toBe([]);
74
+ expect(people.lastName).toBe("Aristote");
75
+ expect(people.firstNames).toBe([]);
76
+ expect(people.hoax).toBe(false);
77
+ expect(people.discredited).toBe(false);
78
+ expect(people.dirName).toBe("people/a/Aristote");
79
+ expect(people.occupations).toBe([]);
80
+ expect(people.pseudonyms).toBe([]);
81
+ });
82
+ });
@@ -1,4 +1,5 @@
1
1
  import { OrganizationPlace } from "./OrganizationPlace";
2
+ import { NamedPlace } from "@rr0/place";
2
3
  /**
3
4
  * Convert a place to a <span class="place"> tag with the place name (or location)
4
5
  */
@@ -9,6 +10,9 @@ export class PlaceRenderer {
9
10
  const orgPlace = place;
10
11
  placeName = orgPlace.org.getTitle(context, { parent: true });
11
12
  }
13
+ else if (place instanceof NamedPlace) {
14
+ placeName = place.name;
15
+ }
12
16
  else {
13
17
  placeName = place.locations.map(location => location.toDMS(context.place)).join(",");
14
18
  }
@@ -4,7 +4,13 @@ export class SearchComponent extends HTMLElement {
4
4
  * @type {string}
5
5
  */
6
6
  static readonly NAME: string;
7
+ static get observedAttributes(): string[];
7
8
  shadow: ShadowRoot;
9
+ /**
10
+ * @param {string} value
11
+ */
12
+ set placeholder(value: string);
13
+ attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
8
14
  connectedCallback(): void;
9
15
  #private;
10
16
  }
@@ -46,11 +46,27 @@ export class SearchComponent extends HTMLElement {
46
46
  this.shadow = this.attachShadow({ mode: "closed" });
47
47
  this.shadow.appendChild(template.content.cloneNode(true));
48
48
  }
49
+ static get observedAttributes() {
50
+ return ["placeholder"];
51
+ }
52
+ /**
53
+ * @param {string} value
54
+ */
55
+ set placeholder(value) {
56
+ const input = this.shadow.getElementById("search-input");
57
+ input.placeholder = value;
58
+ }
59
+ attributeChangedCallback(name, oldValue, newValue) {
60
+ switch (name) {
61
+ case "placeholder":
62
+ this.placeholder = newValue;
63
+ }
64
+ }
49
65
  connectedCallback() {
50
66
  this.onmouseover = __classPrivateFieldGet(this, _SearchComponent_instances, "m", _SearchComponent_siteSearchLoad).bind(this);
51
67
  const input = this.shadow.getElementById("search-input");
52
68
  input.oninput = __classPrivateFieldGet(this, _SearchComponent_instances, "m", _SearchComponent_siteSearchChange).bind(this);
53
- input.placeholder = this.getAttribute("placeholder") || "Recherche";
69
+ this.placeholder = "Recherche";
54
70
  }
55
71
  }
56
72
  _SearchComponent_siteIndex = new WeakMap(), _SearchComponent_loading = new WeakMap(), _SearchComponent_maxResultCount = new WeakMap(), _SearchComponent_instances = new WeakSet(), _SearchComponent_siteSearchChange = function _SearchComponent_siteSearchChange(e) {
@@ -43,7 +43,9 @@ export class SearchIndexStep {
43
43
  context.warn("Could not find", this.fileName, "Will create it");
44
44
  existingIndex = newIndex;
45
45
  }
46
- context.log("Saving search index of", existingIndex.pages.length, "pages at", this.fileName);
46
+ const indexSize = existingIndex.pages.length;
47
+ context.setVar("indexSize", indexSize);
48
+ context.log("Saving search index of", indexSize, "pages at", this.fileName);
47
49
  const indexJson = JSON.stringify(existingIndex);
48
50
  return writeFile(this.fileName, indexJson, "utf-8");
49
51
  }
@@ -29,6 +29,7 @@ export class SourceFactory {
29
29
  * @param href
30
30
  */
31
31
  async createInternal(context, href) {
32
+ var _a;
32
33
  if (path.dirname(href).startsWith("/")) {
33
34
  href = href.substring(1);
34
35
  }
@@ -65,7 +66,7 @@ export class SourceFactory {
65
66
  }
66
67
  const publication = source.publication;
67
68
  if (publication && !publication.time) {
68
- publication.time = this.time.contextFromFileName(context, href).date;
69
+ publication.time = (_a = this.time.contextFromFileName(context, href)) === null || _a === void 0 ? void 0 : _a.date;
69
70
  }
70
71
  if (hash) {
71
72
  source.index = hash;
@@ -18,6 +18,6 @@ export declare class ChronologyReplacer implements DomReplacement<HtmlRR0Context
18
18
  constructor(mappings: RR0CaseMapping<any>[], renderer: CaseSummaryRenderer);
19
19
  replacement(context: HtmlRR0Context, element: HTMLUListElement): Promise<HTMLUListElement>;
20
20
  protected aggregate(context: HtmlRR0Context, element: HTMLUListElement): Promise<void>;
21
- protected aggregateDatasource(mapping: RR0CaseMapping<any>, context: HtmlRR0Context, existingCases: RR0CaseSummary[], casesToAdd: RR0CaseSummary[]): Promise<void>;
21
+ protected aggregateDatasource(context: HtmlRR0Context, mapping: RR0CaseMapping<any>, existingCases: RR0CaseSummary[], casesToAdd: RR0CaseSummary[]): Promise<void>;
22
22
  protected merge(context: HtmlRR0Context, sourceCases: any[], fetchTime: Date, mapping: RR0CaseMapping<any>, existingCases: RR0CaseSummary[]): RR0CaseSummary[];
23
23
  }
@@ -36,7 +36,7 @@ export class ChronologyReplacer {
36
36
  }
37
37
  const datasourceKey = context.file.name + "$" + datasource.copyright;
38
38
  if (!this.done.has(datasourceKey)) {
39
- await this.aggregateDatasource(mapping, context, existingCases, casesToAdd);
39
+ await this.aggregateDatasource(context, mapping, existingCases, casesToAdd);
40
40
  this.done.add(datasourceKey);
41
41
  const merge = mapping.actions.write.includes("pages");
42
42
  if (merge) {
@@ -51,10 +51,14 @@ export class ChronologyReplacer {
51
51
  }
52
52
  }
53
53
  }
54
- async aggregateDatasource(mapping, context, existingCases, casesToAdd) {
54
+ async aggregateDatasource(context, mapping, existingCases, casesToAdd) {
55
55
  let fetched;
56
56
  const datasource = mapping.datasource;
57
57
  const backupDatasource = mapping.backupDatasource;
58
+ if (!backupDatasource) {
59
+ context.warn(`${mapping.constructor.name} has no backupDatasource`);
60
+ return;
61
+ }
58
62
  for (const readMethod of mapping.actions.read) {
59
63
  if (fetched) {
60
64
  break;
@@ -5,7 +5,7 @@ import { BaseOvniFranceCase } from "./BaseOvniFranceCase.js";
5
5
  /**
6
6
  * Maps a Base OVNI France CSV case to a Base OVNI France case.
7
7
  */
8
- export declare class BaseOvniFranceCaseSummaryMapper implements CaseMapper<RR0Context, BaseOvniFranceCase, BaseOvniFranceCaseSummary> {
8
+ export declare class BaseOvniFranceCaseSummaryCsvMapper implements CaseMapper<RR0Context, BaseOvniFranceCase, BaseOvniFranceCaseSummary> {
9
9
  readonly baseUrl: URL;
10
10
  readonly copyright: string;
11
11
  readonly authors: string[];
@@ -2,7 +2,7 @@ import { Level2Date as EdtfDate, Level2Timeshift } from "@rr0/time";
2
2
  /**
3
3
  * Maps a Base OVNI France CSV case to a Base OVNI France case.
4
4
  */
5
- export class BaseOvniFranceCaseSummaryMapper {
5
+ export class BaseOvniFranceCaseSummaryCsvMapper {
6
6
  constructor(baseUrl, copyright, authors) {
7
7
  this.baseUrl = baseUrl;
8
8
  this.copyright = copyright;
@@ -4,7 +4,7 @@ import { rr0TestUtil } from "../../../test/index.js";
4
4
  import { baseOvniFranceTestCases } from "./BaseOvniFranceTestCases.js";
5
5
  import fs from "fs";
6
6
  import { baseOvniFranceDatasource, baseOvniFranceSortComparator } from "./BaseOvniFranceRR0Mapping.js";
7
- import { BaseOvniFranceCaseSummaryMapper } from "./BaseOvniFranceCaseSummaryMapper.js";
7
+ import { BaseOvniFranceCaseSummaryCsvMapper } from "./BaseOvniFranceCaseSummaryCsvMapper.js";
8
8
  describe("Base OVNI France CSV mapping", () => {
9
9
  const dataDate = new Date("2024-08-12 00:00:00 GMT+1");
10
10
  let context;
@@ -34,7 +34,7 @@ ${case1.id},http://baseovnifrance.free.fr/listgen.php?typlist=20&page=0&numobs=2
34
34
  test("read", () => {
35
35
  const fileMapper = new CsvMapper("&");
36
36
  const data = fs.readFileSync("time/1/9/7/7/03/05_03_24_1709676761.txt", { encoding: "latin1" });
37
- const csvMapper = new BaseOvniFranceCaseSummaryMapper(baseOvniFranceDatasource.baseUrl, baseOvniFranceDatasource.searchPath, baseOvniFranceDatasource.authors);
37
+ const csvMapper = new BaseOvniFranceCaseSummaryCsvMapper(baseOvniFranceDatasource.baseUrl, baseOvniFranceDatasource.searchPath, baseOvniFranceDatasource.authors);
38
38
  const cases = fileMapper.parse(data)
39
39
  .map(csvCase => csvMapper.map(context, csvCase, dataDate))
40
40
  .sort(baseOvniFranceSortComparator);
@@ -64,7 +64,7 @@ export class BaseOvniFranceHttpDatasource extends BaseOvniFranceDatasource {
64
64
  const minutes = timeFields ? parseInt(timeFields[2], 10) : undefined;
65
65
  dateTime.hour = hour;
66
66
  dateTime.minute = minutes;
67
- dateTime.timeshift = Level2Timeshift.fromString("GMT+1");
67
+ dateTime.timeshift = Level2Timeshift.fromString("+01"); // GMT+1/UTC+1
68
68
  }
69
69
  getFromRow(context, row) {
70
70
  const columns = row.querySelectorAll("td");
@@ -1,6 +1,6 @@
1
1
  export * from "./BaseOvniFranceCase.js";
2
2
  export * from "./BaseOvniFranceCaseSummary.js";
3
- export * from "./BaseOvniFranceCaseSummaryMapper.js";
3
+ export * from "./BaseOvniFranceCaseSummaryCsvMapper.js";
4
4
  export * from "./BaseOvniFranceCaseSummaryRR0Mapper.js";
5
5
  export * from "./BaseOvniFranceDatasource.js";
6
6
  export * from "./BaseOvniFranceHttpDatasource.js";
@@ -1,6 +1,6 @@
1
1
  export * from "./BaseOvniFranceCase.js";
2
2
  export * from "./BaseOvniFranceCaseSummary.js";
3
- export * from "./BaseOvniFranceCaseSummaryMapper.js";
3
+ export * from "./BaseOvniFranceCaseSummaryCsvMapper.js";
4
4
  export * from "./BaseOvniFranceCaseSummaryRR0Mapper.js";
5
5
  export * from "./BaseOvniFranceDatasource.js";
6
6
  export * from "./BaseOvniFranceHttpDatasource.js";
@@ -10,7 +10,7 @@ export declare class RR0Mapping implements RR0CaseMapping<RR0CaseSummary> {
10
10
  static baseUrl: URL;
11
11
  static searchPath: string;
12
12
  readonly datasource: RR0Datasource;
13
- readonly fileDatasource: RR0FileDatasource;
13
+ readonly backupDatasource: RR0FileDatasource;
14
14
  readonly mapper: RR0CaseSummaryMapper;
15
15
  constructor(cityService: CityService, actions: ChronologyReplacerActions);
16
16
  }
@@ -6,7 +6,7 @@ export class RR0Mapping {
6
6
  this.actions = actions;
7
7
  const rr0HttpDatasource = this.datasource = new RR0HttpDatasource(RR0Mapping.baseUrl, RR0Mapping.searchPath, cityService);
8
8
  const csvMapper = this.mapper = new RR0CaseSummaryMapper(rr0HttpDatasource.baseUrl, rr0HttpDatasource.searchPath, rr0HttpDatasource.authors);
9
- this.fileDatasource = new RR0FileDatasource(csvMapper);
9
+ this.backupDatasource = new RR0FileDatasource(csvMapper);
10
10
  }
11
11
  }
12
12
  RR0Mapping.baseUrl = new URL("https://rr0.org");
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.2.2",
5
+ "version": "0.2.4",
6
6
  "description": "RR0 Content Management System (CMS)",
7
7
  "exports": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "@rr0/lang": "^0.1.12",
33
33
  "@rr0/time": "^0.9.1",
34
34
  "@rr0/place": "^0.5.1",
35
- "@rr0/data": "^0.2.1",
35
+ "@rr0/data": "^0.2.3",
36
36
  "@javarome/fileutil": "^0.3.6",
37
37
  "ssg-api": "^1.16.12",
38
38
  "canvas": "^2.11.2",