@rr0/cms 0.1.21 → 0.1.23

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 (34) hide show
  1. package/dist/OpenGraphCommand.d.ts +3 -3
  2. package/dist/OpenGraphCommand.js +4 -5
  3. package/dist/OpenGraphCommand.test.js +1 -3
  4. package/dist/RR0Build.d.ts +0 -2
  5. package/dist/RR0Build.js +36 -29
  6. package/dist/RR0Build.test.js +30 -31
  7. package/dist/RR0ContentStep.d.ts +13 -9
  8. package/dist/RR0ContentStep.js +15 -35
  9. package/dist/book/BookService.test.js +5 -4
  10. package/dist/people/People.test.js +1 -1
  11. package/dist/people/PeopleFactory.test.js +1 -1
  12. package/dist/people/PeopleRegexReplaceCommand.test.js +1 -1
  13. package/dist/people/PeopleReplacer.test.js +2 -2
  14. package/dist/people/PeopleService.d.ts +3 -1
  15. package/dist/people/PeopleService.js +4 -4
  16. package/dist/source/PersistentSourceRegistry.d.ts +2 -1
  17. package/dist/source/PersistentSourceRegistry.js +2 -2
  18. package/dist/source/SourceFactory.d.ts +3 -1
  19. package/dist/source/SourceFactory.js +3 -3
  20. package/dist/source/SourceRegistry.d.ts +2 -2
  21. package/dist/source/SourceRegistry.js +2 -2
  22. package/dist/test/RR0TestUtil.js +1 -2
  23. package/dist/time/Time.d.ts +0 -15
  24. package/dist/time/Time.js +0 -57
  25. package/dist/time/TimeEventRenderer.test.js +1 -1
  26. package/dist/time/TimeLinkDefaultHandler.js +12 -10
  27. package/dist/time/TimeService.d.ts +19 -1
  28. package/dist/time/TimeService.js +78 -1
  29. package/dist/time/{Time.test.js → TimeService.test.js} +6 -6
  30. package/dist/time/TitleReplaceCommand.test.js +1 -2
  31. package/dist/time/datasource/ChronologyReplacer.test.js +4 -3
  32. package/dist/time/datasource/DatasourceTestCase.js +1 -1
  33. package/package.json +2 -2
  34. /package/dist/time/{Time.test.d.ts → TimeService.test.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { ReplaceCommand } from "ssg-api/dist/src/step/content/replace/ReplaceCommand.js";
2
2
  import { HtmlRR0Context } from "./RR0Context.js";
3
3
  import { Canvas, CanvasRenderingContext2D } from "canvas";
4
- import { TimeTextBuilder } from "./time/index.js";
4
+ import { TimeService } from "./time/index.js";
5
5
  /**
6
6
  * Create a preview image for each page sharing.
7
7
  */
@@ -9,11 +9,11 @@ export declare class OpenGraphCommand implements ReplaceCommand<HtmlRR0Context>
9
9
  protected outDir: string;
10
10
  protected timeFiles: string[];
11
11
  protected baseUrl: string;
12
- protected timeTextBuilder: TimeTextBuilder;
12
+ protected timeService: TimeService;
13
13
  protected width: number;
14
14
  protected height: number;
15
15
  protected num: number;
16
- constructor(outDir: string, timeFiles: string[], baseUrl: string, timeTextBuilder: TimeTextBuilder, width?: number, height?: number);
16
+ constructor(outDir: string, timeFiles: string[], baseUrl: string, timeService: TimeService, width?: number, height?: number);
17
17
  execute(context: HtmlRR0Context): Promise<void>;
18
18
  getInfoStr(context: HtmlRR0Context): string;
19
19
  contentStepEnd(): Promise<void>;
@@ -1,17 +1,16 @@
1
1
  import { createCanvas, loadImage } from "canvas";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { RR0ContentStep } from "./RR0ContentStep.js";
5
4
  import assert from "assert";
6
5
  /**
7
6
  * Create a preview image for each page sharing.
8
7
  */
9
8
  export class OpenGraphCommand {
10
- constructor(outDir, timeFiles, baseUrl, timeTextBuilder, width = 1200, height = 600) {
9
+ constructor(outDir, timeFiles, baseUrl, timeService, width = 1200, height = 600) {
11
10
  this.outDir = outDir;
12
11
  this.timeFiles = timeFiles;
13
12
  this.baseUrl = baseUrl;
14
- this.timeTextBuilder = timeTextBuilder;
13
+ this.timeService = timeService;
15
14
  this.width = width;
16
15
  this.height = height;
17
16
  this.num = 0;
@@ -49,14 +48,14 @@ export class OpenGraphCommand {
49
48
  timeStr = "Chronologie";
50
49
  }
51
50
  else {
52
- const timeContext = RR0ContentStep.setTimeFromPath(context, fileName);
51
+ const timeContext = this.timeService.gSetTimeFromPath(context, fileName);
53
52
  if (timeContext) {
54
53
  context.time.setYear(timeContext.getYear());
55
54
  context.time.setMonth(timeContext.getMonth());
56
55
  context.time.setDayOfMonth(timeContext.getDayOfMonth());
57
56
  context.time.setHour(undefined);
58
57
  context.time.setMinutes(undefined);
59
- timeStr = this.timeTextBuilder.build(context);
58
+ timeStr = this.timeService.textBuilder.build(context);
60
59
  }
61
60
  }
62
61
  const copyrightStr = context.file.meta.copyright || "RR0.org";
@@ -1,14 +1,12 @@
1
1
  import { OpenGraphCommand } from "./OpenGraphCommand.js";
2
2
  import { rr0TestUtil } from "./test/index.js";
3
3
  import { describe, expect, test } from "@javarome/testscript";
4
- import { TimeTextBuilder } from "./time/index.js";
5
4
  describe("OpenGraphCommand", () => {
6
5
  const outDir = "/out";
7
6
  test("time page", () => {
8
7
  const timeFile = rr0TestUtil.time.filePath("0/0/6/5/index.html");
9
8
  const context = rr0TestUtil.newHtmlContext(timeFile, "");
10
- const timeTextBuilder = new TimeTextBuilder(rr0TestUtil.intlOptions);
11
- const command = new OpenGraphCommand(outDir, [context.file.name], "https://rr0.org", timeTextBuilder);
9
+ const command = new OpenGraphCommand(outDir, [context.file.name], "https://rr0.org", rr0TestUtil.time.getService());
12
10
  expect(command.getInfoStr(context)).toBe("Chronologie, RR0.org");
13
11
  });
14
12
  });
@@ -11,14 +11,12 @@ export interface RR0BuildOptions {
11
11
  timeOptions: TimeServiceOptions;
12
12
  siteBaseUrl: string;
13
13
  timeFormat: Intl.DateTimeFormatOptions;
14
- timeFiles: string[];
15
14
  directoryPages: string[];
16
15
  ufoCaseDirectoryFile: string;
17
16
  ufoCasesExclusions: string[];
18
17
  sourceRegistryFileName: string;
19
18
  directoryExcluded: string[];
20
19
  directoryOptions: PeopleDirectoryStepOptions;
21
- inDir?: (path: string) => string;
22
20
  }
23
21
  export interface RR0BuildArgs {
24
22
  /**
package/dist/RR0Build.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import { CaseSummaryRenderer, ChronologyReplacerFactory, CsvMapper, EventReplacer, EventReplacerFactory, HttpSource, RR0Mapping, SsiTitleReplaceCommand, Time, TimeElementFactory, TimeLinkDefaultHandler, TimeReplacer, TimeReplacerFactory, TimeService, TimeTextBuilder, TimeUrlBuilder } from "./time";
3
+ import { CaseSummaryRenderer, ChronologyReplacerFactory, CsvMapper, EventReplacer, EventReplacerFactory, HttpSource, RR0Mapping, SsiTitleReplaceCommand, TimeElementFactory, TimeLinkDefaultHandler, TimeReplacer, TimeReplacerFactory, TimeService, TimeTextBuilder, TimeUrlBuilder } from "./time";
4
4
  import { CaseDirectoryStep, CaseFactory, CaseService } from "./science/index.js";
5
5
  import { GooglePlaceService, PlaceReplacerFactory } from "./place/index.js";
6
6
  import { OrganizationFactory, OrganizationService } from "./org/index.js";
@@ -56,7 +56,7 @@ export class RR0Build {
56
56
  this.orgService = new OrganizationService([], "org", undefined);
57
57
  const timeTextBuilder = this.timeTextBuilder = new TimeTextBuilder(options.timeFormat);
58
58
  const timeOptions = options.timeOptions;
59
- const timeRoot = this.options.inDir(timeOptions.root);
59
+ const timeRoot = timeOptions.root;
60
60
  const timeUrlBuilder = new TimeUrlBuilder({ rootDir: timeRoot });
61
61
  const eventFactory = new RR0EventFactory();
62
62
  const sightingFactory = new TypedDataFactory(eventFactory, "sighting", ["index"]);
@@ -74,7 +74,7 @@ export class RR0Build {
74
74
  }
75
75
  async run(args) {
76
76
  const context = this.context;
77
- const timeFiles = this.options.timeFiles;
77
+ const timeFiles = this.options.timeOptions.files;
78
78
  context.setVar("timeFilesCount", timeFiles.length);
79
79
  const timeService = this.timeService;
80
80
  const timeElementFactory = new TimeElementFactory(timeService.renderer);
@@ -83,19 +83,19 @@ export class RR0Build {
83
83
  let dataService = this.dataService;
84
84
  const caseService = new CaseService(dataService, this.caseFactory, timeElementFactory, caseFiles);
85
85
  const peopleFiles = await this.peopleFactory.getFiles();
86
- const peopleService = new PeopleService(dataService, this.peopleFactory, peopleFiles);
86
+ const peopleService = new PeopleService(dataService, this.peopleFactory, peopleFiles, timeService);
87
87
  const peopleList = await peopleService.getAll();
88
88
  context.setVar("peopleFilesCount", peopleList.length);
89
89
  const bookMeta = new Map();
90
90
  const bookLinks = new Map();
91
91
  const config = this.config;
92
- const ufoCaseDirectoryFile = this.options.inDir(this.options.ufoCaseDirectoryFile);
93
- const ufoCasesExclusions = this.options.ufoCasesExclusions.map(this.options.inDir);
92
+ const ufoCaseDirectoryFile = this.options.ufoCaseDirectoryFile;
93
+ const ufoCasesExclusions = this.options.ufoCasesExclusions;
94
94
  const ufoCasesStep = new CaseDirectoryStep(caseService, caseService.files, ufoCasesExclusions, ufoCaseDirectoryFile, outputFunc, config);
95
95
  const peopleDirectoryFactory = new PeopleDirectoryStepFactory(outputFunc, config, peopleService, this.options.directoryExcluded);
96
96
  const directoryOptions = this.options.directoryOptions;
97
97
  for (const directoryOption in directoryOptions) {
98
- directoryOptions[directoryOption] = this.options.inDir(directoryOptions[directoryOption]);
98
+ directoryOptions[directoryOption] = directoryOptions[directoryOption];
99
99
  }
100
100
  const peopleSteps = await peopleDirectoryFactory.create(directoryOptions);
101
101
  // Publish case.json files so that vraiufo.com will find them
@@ -103,10 +103,11 @@ export class RR0Build {
103
103
  copies.push(...(ufoCasesStep.config.rootDirs).map(dir => path.join(dir, "case.json")));
104
104
  const outDir = this.options.outDir;
105
105
  await writeFile(path.join(outDir, "casesDirs.json"), JSON.stringify(ufoCasesStep.config.rootDirs), "utf-8");
106
- copies.push(...(peopleSteps.reduce((rootDirs, peopleStep) => {
106
+ const dirsContainingPeopleJson = peopleSteps.reduce((rootDirs, peopleStep) => {
107
107
  rootDirs.push(...peopleStep.config.rootDirs);
108
108
  return rootDirs;
109
- }, [])).map(dir => path.join(dir, "people.json")));
109
+ }, []);
110
+ copies.push(...dirsContainingPeopleJson.map(dir => path.join(dir, "people.json")));
110
111
  await writeFile(path.join(outDir, "peopleDirs.json"), JSON.stringify(peopleList.map(people => people.dirName)), "utf-8");
111
112
  const timeTextBuilder = this.timeTextBuilder;
112
113
  const searchVisitor = new SearchVisitor({ notIndexedUrls: ["404.html", "Referencement.html"], indexWords: false }, timeTextBuilder);
@@ -114,8 +115,8 @@ export class RR0Build {
114
115
  const http = new HttpSource();
115
116
  const baseUrl = this.options.siteBaseUrl;
116
117
  const timeFormat = this.options.timeFormat;
117
- const sourceRegistryFileName = this.options.inDir(this.options.sourceRegistryFileName);
118
- const sourceFactory = new PersistentSourceRegistry(dataService, http, baseUrl, sourceRegistryFileName, timeFormat);
118
+ const sourceRegistryFileName = this.options.sourceRegistryFileName;
119
+ const sourceFactory = new PersistentSourceRegistry(dataService, http, baseUrl, sourceRegistryFileName, timeFormat, timeService);
119
120
  const noteCounter = new NoteFileCounter();
120
121
  const noteRenderer = new NoteRenderer(noteCounter);
121
122
  const caseRenderer = new CaseSummaryRenderer(noteRenderer, sourceFactory, sourceRenderer, timeElementFactory);
@@ -128,7 +129,7 @@ export class RR0Build {
128
129
  ], caseRenderer));
129
130
  const timeDefaultHandler = (context) => {
130
131
  let title;
131
- title = Time.titleFromFile(context, context.file.name, timeTextBuilder);
132
+ title = timeService.titleFromFile(context, context.file.name, timeTextBuilder);
132
133
  return title;
133
134
  };
134
135
  const pageReplaceCommands = [
@@ -188,19 +189,22 @@ export class RR0Build {
188
189
  return path.join(outDir, "netlify.toml");
189
190
  }
190
191
  };
191
- const options = this.options;
192
- const contentRoots = options.contentRoots.map(options.inDir);
193
- const includeStep = new RR0ContentStep([htAccessToNetlifyConfig, {
194
- roots: contentRoots,
195
- replacements: [new class extends SsiIncludeReplaceCommand {
196
- filePath(context, fileNameArg) {
197
- const dirName = path.dirname(context.file.name);
198
- return fileNameArg.startsWith("/") ?
199
- path.join(process.cwd(), options.inDir(fileNameArg)) : path.join(dirName, fileNameArg);
200
- }
201
- }([csvTransformer])],
202
- getOutputPath
203
- }], outputFunc, [], [], force, "content includes", toProcess);
192
+ const contentRoots = this.options.contentRoots;
193
+ const contentStepOptions = {
194
+ contentConfigs: [htAccessToNetlifyConfig, {
195
+ roots: contentRoots,
196
+ replacements: [new class extends SsiIncludeReplaceCommand {
197
+ filePath(context, fileNameArg) {
198
+ const dirName = path.dirname(context.file.name);
199
+ return fileNameArg.startsWith("/") ?
200
+ path.join(process.cwd(), fileNameArg) : path.join(dirName, fileNameArg);
201
+ }
202
+ }([csvTransformer])],
203
+ getOutputPath
204
+ }],
205
+ outputFunc, fileVisitors: [], contentVisitors: [], force, name: "content includes", toProcess
206
+ };
207
+ const includeStep = new RR0ContentStep(contentStepOptions, timeService);
204
208
  ssg.add(includeStep);
205
209
  ssg.add(ufoCasesStep);
206
210
  ssg.add(...peopleSteps);
@@ -216,9 +220,12 @@ export class RR0Build {
216
220
  new OutlineReplaceCommand(),
217
221
  new AnchorReplaceCommand(baseUrl, [new CaseAnchorHandler(caseService, timeTextBuilder), new DataAnchorHandler(dataService)]),
218
222
  new ImageCommand(outDir, 275, 500),
219
- new OpenGraphCommand(outDir, timeFiles, baseUrl, timeTextBuilder)
223
+ new OpenGraphCommand(outDir, timeFiles, baseUrl, timeService)
220
224
  ];
221
- ssg.add(new RR0ContentStep([{ roots: contentRoots, replacements: contentReplacements, getOutputPath }], outputFunc, [], contentVisitors, force, "contents replacements", toProcess));
225
+ ssg.add(new RR0ContentStep({
226
+ contentConfigs: [{ roots: contentRoots, replacements: contentReplacements, getOutputPath }],
227
+ outputFunc, fileVisitors: [], contentVisitors, force, name: "contents replacements", toProcess
228
+ }, timeService));
222
229
  }
223
230
  if (args.books) {
224
231
  ssg.add(await BookDirectoryStep.create(outputFunc, config, bookMeta, bookLinks));
@@ -233,8 +240,8 @@ export class RR0Build {
233
240
  if (copies) {
234
241
  const copyConfig = {
235
242
  getOutputPath,
236
- sourcePatterns: copies.map(this.options.inDir),
237
- options: { ignore: ["node_modules/**", "out/**"].map(this.options.inDir) }
243
+ sourcePatterns: Array.from(new Set(copies)),
244
+ options: { ignore: ["node_modules/**", "out/**"] }
238
245
  };
239
246
  ssg.add(new CopyStep(copyConfig));
240
247
  }
@@ -31,7 +31,7 @@ describe("Build", () => {
31
31
  "tech/**/*.html",
32
32
  "udb/*.html",
33
33
  "js/**/*.html"
34
- ];
34
+ ].map(testFilePath);
35
35
  const copiesArg = args.copies;
36
36
  const copies = copiesArg ? copiesArg : [
37
37
  "favicon.ico", "manifest.json", "opensearch.xml", "apple-touch-icon.png", "apple-touch-icon_400x400.png", "screenshot1.jpg",
@@ -47,16 +47,12 @@ describe("Build", () => {
47
47
  "time/DualRangeComponent.mjs",
48
48
  "index/index.js", "lang/form.js", "lang/form.css", "lang/speech.js", "lang/speech.css",
49
49
  "croyance/divin/theisme/mono/livre/islam/coran/index.js"
50
- ];
50
+ ].map(testFilePath);
51
51
  const outDir = "out";
52
52
  const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY;
53
53
  if (!googleMapsApiKey) {
54
54
  throw Error("GOOGLE_MAPS_API_KEY is required");
55
55
  }
56
- const timeOptions = {
57
- root: "time",
58
- files: []
59
- };
60
56
  const timeFormat = {
61
57
  year: "numeric",
62
58
  month: "long",
@@ -66,13 +62,13 @@ describe("Build", () => {
66
62
  minute: "2-digit"
67
63
  };
68
64
  async function getTimeFiles() {
69
- const minusYearFiles = await glob("time/-?/?/?/?/index.html");
70
- const year1Files = await glob("time/?/index.html");
71
- const year2Files = await glob("time/?/?/index.html");
72
- const year3Files = await glob("time/?/?/?/index.html");
73
- const year4Files = await glob("time/?/?/?/?/index.html");
74
- const monthFiles = await glob("time/?/?/?/?/??/index.html");
75
- const dayFiles = await glob("time/?/?/?/?/??/??/index.html");
65
+ const minusYearFiles = await glob(testFilePath("time/-?/?/?/?/index.html"));
66
+ const year1Files = await glob(testFilePath("time/?/index.html"));
67
+ const year2Files = await glob(testFilePath("time/?/?/index.html"));
68
+ const year3Files = await glob(testFilePath("time/?/?/?/index.html"));
69
+ const year4Files = await glob(testFilePath("time/?/?/?/?/index.html"));
70
+ const monthFiles = await glob(testFilePath("time/?/?/?/?/??/index.html"));
71
+ const dayFiles = await glob(testFilePath("time/?/?/?/?/??/??/index.html"));
76
72
  return year1Files.concat(year2Files).concat(year3Files).concat(year4Files).concat(minusYearFiles).concat(monthFiles).concat(dayFiles).sort();
77
73
  }
78
74
  const directoryPages = [
@@ -82,30 +78,33 @@ describe("Build", () => {
82
78
  ].map(testFilePath);
83
79
  getTimeFiles().then(async (timeFiles) => {
84
80
  const directoryOptions = {
85
- root: "people/index.html",
86
- scientists: "people/scientifiques.html",
87
- ufologists: "people/ufologues.html",
88
- ufoWitnesses: "people/witness/index.html",
89
- astronomers: "people/astronomes.html",
90
- contactees: "people/contactes.html",
91
- pilots: "people/pilotes.html",
92
- military: "people/militaires.html",
93
- softwareEngineers: "tech/info/Personnes.html",
94
- politicians: "people/politicians.html",
95
- rulers: "people/dirigeants.html"
81
+ root: testFilePath("people/index.html"),
82
+ scientists: testFilePath("people/scientifiques.html"),
83
+ ufologists: testFilePath("people/ufologues.html"),
84
+ ufoWitnesses: testFilePath("people/witness/index.html"),
85
+ astronomers: testFilePath("people/astronomes.html"),
86
+ contactees: testFilePath("people/contactes.html"),
87
+ pilots: testFilePath("people/pilotes.html"),
88
+ military: testFilePath("people/militaires.html"),
89
+ softwareEngineers: testFilePath("tech/info/Personnes.html"),
90
+ politicians: testFilePath("people/politicians.html"),
91
+ rulers: testFilePath("people/dirigeants.html")
96
92
  };
97
- const sourceRegistryFileName = "source/index.json";
93
+ const sourceRegistryFileName = testFilePath("source/index.json");
98
94
  const siteBaseUrl = "https://rr0.org/";
99
95
  const mail = "rr0@rr0.org";
96
+ const timeOptions = {
97
+ root: testFilePath("time"),
98
+ files: timeFiles
99
+ };
100
100
  const build = new RR0Build({
101
101
  contentRoots, copies, outDir, locale: "fr", googleMapsApiKey, mail, timeOptions,
102
- siteBaseUrl, timeFormat, timeFiles, directoryPages,
103
- ufoCaseDirectoryFile: "science/crypto/ufo/enquete/dossier/index.html",
104
- ufoCasesExclusions: ["science/crypto/ufo/enquete/dossier/canular"],
102
+ siteBaseUrl, timeFormat, directoryPages,
103
+ ufoCaseDirectoryFile: testFilePath("science/crypto/ufo/enquete/dossier/index.html"),
104
+ ufoCasesExclusions: ["science/crypto/ufo/enquete/dossier/canular"].map(testFilePath),
105
105
  sourceRegistryFileName,
106
- directoryExcluded: ["people/Astronomers_fichiers", "people/witness", "people/author"],
107
- directoryOptions,
108
- inDir: testFilePath
106
+ directoryExcluded: ["people/Astronomers_fichiers", "people/witness", "people/author"].map(testFilePath),
107
+ directoryOptions
109
108
  });
110
109
  await build.run(args);
111
110
  });
@@ -1,6 +1,6 @@
1
1
  import { ContentStep, ContentStepConfig, ContentStepResult, OutputFunc } from "ssg-api";
2
2
  import { HtmlRR0Context } from "./RR0Context.js";
3
- import { TimeContext } from "@rr0/time";
3
+ import { TimeService } from "./time/index.js";
4
4
  export interface ContentVisitor {
5
5
  visit(context: HtmlRR0Context): Promise<void>;
6
6
  }
@@ -8,16 +8,20 @@ export interface FileVisitor {
8
8
  visit(context: HtmlRR0Context, processFile: boolean): Promise<void>;
9
9
  contentStepEnd(): Promise<void>;
10
10
  }
11
+ export interface RR0ContentStepOptions {
12
+ contentConfigs: ContentStepConfig[];
13
+ outputFunc: OutputFunc;
14
+ fileVisitors?: FileVisitor[];
15
+ contentVisitors?: ContentVisitor[];
16
+ force: boolean;
17
+ name: string;
18
+ toProcess: Set<string>;
19
+ }
11
20
  export declare class RR0ContentStep extends ContentStep<HtmlRR0Context> {
12
- protected fileVisitors: FileVisitor[];
13
- protected contentVisitors: ContentVisitor[];
14
- protected force: boolean;
15
- protected toProcess: Set<string>;
16
- constructor(contentConfigs: ContentStepConfig[], outputFunc: OutputFunc, fileVisitors: FileVisitor[], contentVisitors: ContentVisitor[], force: boolean, name: string, toProcess: Set<string>);
17
- static setTimeFromPath(context: HtmlRR0Context, filePath: string): TimeContext | undefined;
21
+ protected options: RR0ContentStepOptions;
22
+ protected timeService: TimeService;
23
+ constructor(options: RR0ContentStepOptions, timeService: TimeService);
18
24
  protected processFile(context: HtmlRR0Context, filePath: string, contentsConfig: ContentStepConfig): Promise<string | undefined>;
19
- protected setContextFromFile(context: HtmlRR0Context, filePath: string): void;
20
- protected setTimeFromPath(context: HtmlRR0Context, filePath: string): void;
21
25
  protected shouldProcessFile(context: HtmlRR0Context, contentsConfig: ContentStepConfig): Promise<boolean>;
22
26
  protected shouldProcessContent(context: HtmlRR0Context, contentsConfig: ContentStepConfig): Promise<boolean>;
23
27
  protected postExecute(result: ContentStepResult): Promise<ContentStepResult>;
@@ -1,54 +1,34 @@
1
1
  import { ContentStep } from "ssg-api";
2
- import { Time } from "./time/index.js";
3
2
  export class RR0ContentStep extends ContentStep {
4
- constructor(contentConfigs, outputFunc, fileVisitors = [], contentVisitors = [], force, name, toProcess) {
5
- super(contentConfigs, outputFunc, name);
6
- this.fileVisitors = fileVisitors;
7
- this.contentVisitors = contentVisitors;
8
- this.force = force;
9
- this.toProcess = toProcess;
10
- }
11
- static setTimeFromPath(context, filePath) {
12
- const time = context.time;
13
- time.reset();
14
- const newTimeContext = Time.contextFromFileName(context, filePath);
15
- if (newTimeContext) {
16
- time.setYear(newTimeContext.getYear());
17
- time.setMonth(newTimeContext.getMonth());
18
- time.setDayOfMonth(newTimeContext.getDayOfMonth());
19
- // context.time.from = context.time
20
- }
21
- return newTimeContext;
3
+ constructor(options, timeService) {
4
+ super(options.contentConfigs, options.outputFunc, options.name);
5
+ this.options = options;
6
+ this.timeService = timeService;
7
+ this.options.fileVisitors = options.fileVisitors || [];
8
+ this.options.contentVisitors = options.contentVisitors || [];
22
9
  }
23
10
  async processFile(context, filePath, contentsConfig) {
24
- this.setContextFromFile(context, filePath);
11
+ this.timeService.setContextFromFile(context, filePath);
25
12
  return super.processFile(context, filePath, contentsConfig);
26
13
  }
27
- setContextFromFile(context, filePath) {
28
- this.setTimeFromPath(context, filePath);
29
- }
30
- setTimeFromPath(context, filePath) {
31
- context.time.reset(); // Don't use time context from previous page.
32
- RR0ContentStep.setTimeFromPath(context, filePath);
33
- }
34
14
  async shouldProcessFile(context, contentsConfig) {
35
15
  const fileHasChanged = await super.shouldProcessFile(context, contentsConfig);
36
- const fileIsForced = this.toProcess.has(context.file.name);
37
- const processFile = this.force || fileIsForced || fileHasChanged;
16
+ const fileIsForced = this.options.toProcess.has(context.file.name);
17
+ const processFile = this.options.force || fileIsForced || fileHasChanged;
38
18
  if (processFile) {
39
- this.toProcess.add(context.file.name);
19
+ this.options.toProcess.add(context.file.name);
40
20
  }
41
- for (const fileVisitor of this.fileVisitors) {
21
+ for (const fileVisitor of this.options.fileVisitors) {
42
22
  await fileVisitor.visit(context, processFile);
43
23
  }
44
24
  return processFile;
45
25
  }
46
26
  async shouldProcessContent(context, contentsConfig) {
47
- const fileIsForced = this.toProcess.has(context.file.name);
27
+ const fileIsForced = this.options.toProcess.has(context.file.name);
48
28
  const showProcess = await super.shouldProcessContent(context, contentsConfig);
49
- const should = this.force || fileIsForced || showProcess;
29
+ const should = this.options.force || fileIsForced || showProcess;
50
30
  if (should) {
51
- for (const contentVisitor of this.contentVisitors) {
31
+ for (const contentVisitor of this.options.contentVisitors) {
52
32
  await contentVisitor.visit(context);
53
33
  }
54
34
  }
@@ -56,7 +36,7 @@ export class RR0ContentStep extends ContentStep {
56
36
  }
57
37
  async postExecute(result) {
58
38
  await super.postExecute(result);
59
- for (const fileVisitor of this.fileVisitors) {
39
+ for (const fileVisitor of this.options.fileVisitors) {
60
40
  await fileVisitor.contentStepEnd();
61
41
  }
62
42
  return result;
@@ -1,16 +1,16 @@
1
1
  import { ConsoleLogger } from "ssg-api";
2
2
  import { CLI } from "../util/cli/CLI.js";
3
3
  import path from "path";
4
- import { PeopleFactory, PeopleService } from "../people";
4
+ import { PeopleService } from "../people";
5
5
  import { AllDataService, RR0EventFactory, TypedDataFactory } from "@rr0/data";
6
6
  import { BookService } from "./BookService";
7
7
  import { TimeUrlBuilder } from "../time";
8
- import { testFilePath } from "../test";
8
+ import { rr0TestUtil, testFilePath } from "../test";
9
9
  const logger = new ConsoleLogger("rr0-books");
10
10
  const args = new CLI().getArgs();
11
11
  const fileName = args.import;
12
12
  const dry = args.dry === "true";
13
- const peopleFactory = new PeopleFactory(new RR0EventFactory());
13
+ const peopleFactory = rr0TestUtil.peopleFactory;
14
14
  const eventFactory = new RR0EventFactory();
15
15
  const bookFactory = new TypedDataFactory(eventFactory, "book");
16
16
  const dataService = new AllDataService([bookFactory, peopleFactory]);
@@ -26,7 +26,8 @@ const timeOptions = {
26
26
  };
27
27
  const timeUrlBuilder = new TimeUrlBuilder({ rootDir: timeOptions.root });
28
28
  let files = [];
29
- const books = new BookService(logger, dry, new PeopleService(dataService, peopleFactory, files), timeUrlBuilder, config);
29
+ const peopleService = new PeopleService(dataService, peopleFactory, files, rr0TestUtil.time.getService());
30
+ const books = new BookService(logger, dry, peopleService, timeUrlBuilder, config);
30
31
  books.import(fileName).then((result) => {
31
32
  logger.log("Wrote", result.length, "books");
32
33
  });
@@ -12,7 +12,7 @@ describe("People", () => {
12
12
  path.join(peopleRoot, "h/HynekJosefAllen"),
13
13
  path.join(peopleRoot, "v/VonBraunWerner")
14
14
  ];
15
- const service = new PeopleService(new AllDataService([]), rr0TestUtil.peopleFactory, peopleFiles);
15
+ const service = new PeopleService(new AllDataService([]), rr0TestUtil.peopleFactory, peopleFiles, rr0TestUtil.time.getService());
16
16
  test("age", async () => {
17
17
  const [hynek] = await service.getFromDir("HynekJosefAllen");
18
18
  expect(hynek.isDeceased()).toBe(false);
@@ -14,7 +14,7 @@ describe("PeopleFactory", () => {
14
14
  path.join(peopleRoot, "h/HynekJosefAllen"),
15
15
  path.join(peopleRoot, "v/VonBraunWerner")
16
16
  ];
17
- const factory = new PeopleService(dataService, rr0TestUtil.peopleFactory, peopleFiles);
17
+ const factory = new PeopleService(dataService, rr0TestUtil.peopleFactory, peopleFiles, rr0TestUtil.time.getService());
18
18
  test("build people with one first name", () => {
19
19
  expect(factory.createFromFullName("Jérôme Beau")).toEqual(new People(["Jérôme"], "Beau", [], [], [], false, undefined, undefined, undefined, "people/b/BeauJerome"));
20
20
  });
@@ -8,7 +8,7 @@ describe("ClassDomReplaceCommand", () => {
8
8
  test("replaces", async () => {
9
9
  const peopleRoot = "src/people";
10
10
  const peopleFiles = [path.join(peopleRoot, "b/BeauJerome")];
11
- const peopleService = new PeopleService(rr0TestUtil.dataService, rr0TestUtil.peopleFactory, peopleFiles);
11
+ const peopleService = new PeopleService(rr0TestUtil.dataService, rr0TestUtil.peopleFactory, peopleFiles, rr0TestUtil.time.getService());
12
12
  const command = new ClassDomReplaceCommand(new PeopleReplacerFactory(peopleService), "people");
13
13
  const context = rr0TestUtil.time.newHtmlContext("1/9/9/0/08/index.html", `<span class="people">Jérôme Beau</span>`);
14
14
  await command.execute(context);
@@ -22,7 +22,7 @@ describe("PeopleReplacer", () => {
22
22
  }
23
23
  test("ignore brackets", async () => {
24
24
  const dataService = new AllDataService([peopleFactory]);
25
- const replacer = new PeopleReplacer(new PeopleService(dataService, peopleFactory, peopleFiles));
25
+ const replacer = new PeopleReplacer(new PeopleService(dataService, peopleFactory, peopleFiles, rr0TestUtil.time.getService()));
26
26
  const context = rr0TestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
27
27
  {
28
28
  const lastnameFirstElement = createPeopleElement(context, "Hynek, Josef Allen (Northwestern University, Evanston, Illinois)");
@@ -37,7 +37,7 @@ describe("PeopleReplacer", () => {
37
37
  });
38
38
  test("replace people tags", async () => {
39
39
  const dataService = new AllDataService([peopleFactory]);
40
- const replacer = new PeopleReplacer(new PeopleService(dataService, peopleFactory, peopleFiles));
40
+ const replacer = new PeopleReplacer(new PeopleService(dataService, peopleFactory, peopleFiles, rr0TestUtil.time.getService()));
41
41
  const context = rr0TestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
42
42
  {
43
43
  const peopleWithTitle = createPeopleElement(context, "Ronald Reagan", "Ronald Wilson Reagan");
@@ -4,10 +4,12 @@ import { CountryCode } from "../org/index.js";
4
4
  import { Occupation } from "./Occupation.js";
5
5
  import { PeopleFactory } from "./PeopleFactory.js";
6
6
  import { AbstractDataService, AllDataService } from "@rr0/data";
7
+ import { TimeService } from "../time";
7
8
  export declare class PeopleService extends AbstractDataService<People> {
8
9
  protected peopleFactory: PeopleFactory;
10
+ protected timeService: TimeService;
9
11
  readonly cache: Map<string, People>;
10
- constructor(dataService: AllDataService, peopleFactory: PeopleFactory, files: string[]);
12
+ constructor(dataService: AllDataService, peopleFactory: PeopleFactory, files: string[], timeService: TimeService);
11
13
  createFromFullName(fullName: string): People;
12
14
  getAll(): Promise<People[]>;
13
15
  getFromDirs(dirNames: string[]): Promise<People[]>;
@@ -1,12 +1,12 @@
1
1
  import { People } from "./People.js";
2
2
  import path from "path";
3
- import { Time } from "../time/Time.js";
4
3
  import { Gender } from "@rr0/common";
5
4
  import { AbstractDataFactory, AbstractDataService } from "@rr0/data";
6
5
  export class PeopleService extends AbstractDataService {
7
- constructor(dataService, peopleFactory, files) {
6
+ constructor(dataService, peopleFactory, files, timeService) {
8
7
  super(dataService, peopleFactory, files);
9
8
  this.peopleFactory = peopleFactory;
9
+ this.timeService = timeService;
10
10
  this.cache = new Map();
11
11
  }
12
12
  createFromFullName(fullName) {
@@ -79,12 +79,12 @@ export class PeopleService extends AbstractDataService {
79
79
  }
80
80
  let birthTimeStr = people.birthTime;
81
81
  if (birthTimeStr) {
82
- const birthTime = people.birthTime = Time.dateFromIso(birthTimeStr);
82
+ const birthTime = people.birthTime = this.timeService.dateFromIso(birthTimeStr);
83
83
  birthTimeStr = birthTime.getFullYear().toString();
84
84
  }
85
85
  let deathTimeStr = people.deathTime;
86
86
  if (deathTimeStr) {
87
- const deathTime = people.deathTime = Time.dateFromIso(deathTimeStr);
87
+ const deathTime = people.deathTime = this.timeService.dateFromIso(deathTimeStr);
88
88
  deathTimeStr = deathTime.getFullYear().toString();
89
89
  }
90
90
  if (people.isDeceased()) {
@@ -2,12 +2,13 @@ import { HttpSource } from "../time/datasource/HttpSource.js";
2
2
  import { SourceRegistry } from "./SourceRegistry.js";
3
3
  import { AllDataService } from "@rr0/data";
4
4
  import { Source } from "@rr0/data/dist/source";
5
+ import { TimeService } from "../time";
5
6
  /**
6
7
  * Create Source objects and register them.
7
8
  */
8
9
  export declare class PersistentSourceRegistry extends SourceRegistry {
9
10
  protected fileName: string;
10
- constructor(dataService: AllDataService, http: HttpSource, baseUrl: string, fileName: string, options: Intl.DateTimeFormatOptions);
11
+ constructor(dataService: AllDataService, http: HttpSource, baseUrl: string, fileName: string, options: Intl.DateTimeFormatOptions, time: TimeService);
11
12
  protected get(href: string): Promise<Source>;
12
13
  protected register(href: string, source: Source): Promise<void>;
13
14
  }
@@ -4,8 +4,8 @@ import { FileContents } from "@javarome/fileutil";
4
4
  * Create Source objects and register them.
5
5
  */
6
6
  export class PersistentSourceRegistry extends SourceRegistry {
7
- constructor(dataService, http, baseUrl, fileName, options) {
8
- super(dataService, http, baseUrl, options);
7
+ constructor(dataService, http, baseUrl, fileName, options, time) {
8
+ super(dataService, http, baseUrl, options, time);
9
9
  this.fileName = fileName;
10
10
  try {
11
11
  const registryFileContents = FileContents.read(fileName, "utf-8").contents;
@@ -1,5 +1,6 @@
1
1
  import { HtmlRR0Context } from "../RR0Context.js";
2
2
  import { HttpSource } from "../time/datasource/HttpSource.js";
3
+ import { TimeService } from "../time";
3
4
  import { AllDataService } from "@rr0/data";
4
5
  import { Source } from "@rr0/data/dist/source";
5
6
  /**
@@ -10,7 +11,8 @@ export declare class SourceFactory {
10
11
  protected http: HttpSource;
11
12
  protected baseUrl: string;
12
13
  protected options: Intl.DateTimeFormatOptions;
13
- constructor(dataService: AllDataService, http: HttpSource, baseUrl: string, options: Intl.DateTimeFormatOptions);
14
+ protected time: TimeService;
15
+ constructor(dataService: AllDataService, http: HttpSource, baseUrl: string, options: Intl.DateTimeFormatOptions, time: TimeService);
14
16
  /**
15
17
  * Create a Source object from an anchor's URL.
16
18
  *
@@ -1,17 +1,17 @@
1
1
  import path from "path";
2
2
  import { TimeContext } from "@rr0/time";
3
3
  import { JSDOM } from "jsdom";
4
- import { Time } from "../time";
5
4
  import { FileContents } from "@javarome/fileutil";
6
5
  /**
7
6
  * Create Source objects.
8
7
  */
9
8
  export class SourceFactory {
10
- constructor(dataService, http, baseUrl, options) {
9
+ constructor(dataService, http, baseUrl, options, time) {
11
10
  this.dataService = dataService;
12
11
  this.http = http;
13
12
  this.baseUrl = baseUrl;
14
13
  this.options = options;
14
+ this.time = time;
15
15
  }
16
16
  /**
17
17
  * Create a Source object from an anchor's URL.
@@ -65,7 +65,7 @@ export class SourceFactory {
65
65
  }
66
66
  const publication = source.publication;
67
67
  if (publication && !publication.time) {
68
- publication.time = Time.contextFromFileName(context, href);
68
+ publication.time = this.time.contextFromFileName(context, href);
69
69
  }
70
70
  if (hash) {
71
71
  source.index = hash;
@@ -1,5 +1,5 @@
1
1
  import { HtmlRR0Context } from "../RR0Context.js";
2
- import { HttpSource } from "../time/index.js";
2
+ import { HttpSource, TimeService } from "../time/index.js";
3
3
  import { SourceFactory } from "./SourceFactory.js";
4
4
  import { AllDataService } from "@rr0/data";
5
5
  import { Source } from "@rr0/data/dist/source";
@@ -8,7 +8,7 @@ import { Source } from "@rr0/data/dist/source";
8
8
  */
9
9
  export declare class SourceRegistry extends SourceFactory {
10
10
  registry: {};
11
- constructor(dataService: AllDataService, http: HttpSource, baseUrl: string, options: Intl.DateTimeFormatOptions);
11
+ constructor(dataService: AllDataService, http: HttpSource, baseUrl: string, options: Intl.DateTimeFormatOptions, time: TimeService);
12
12
  /**
13
13
  * Create a Source object from an anchor's URL.
14
14
  *
@@ -3,8 +3,8 @@ import { SourceFactory } from "./SourceFactory.js";
3
3
  * Create Source objects and register them.
4
4
  */
5
5
  export class SourceRegistry extends SourceFactory {
6
- constructor(dataService, http, baseUrl, options) {
7
- super(dataService, http, baseUrl, options);
6
+ constructor(dataService, http, baseUrl, options, time) {
7
+ super(dataService, http, baseUrl, options, time);
8
8
  this.registry = {};
9
9
  }
10
10
  /**
@@ -1,6 +1,5 @@
1
1
  import path from "path";
2
2
  import { RR0ContextImpl } from "../RR0Context.js";
3
- import { Time } from "../time/index.js";
4
3
  import { HtmlFileContents } from "ssg-api";
5
4
  import { OrganizationFactory } from "../org/index.js";
6
5
  import { CaseFactory } from "../science/index.js";
@@ -65,7 +64,7 @@ export class RR0TestUtil {
65
64
  const lang = currentFile.lang;
66
65
  context.file = new HtmlFileContents(currentFile.name, currentFile.encoding, currentFile.contents, currentFile.lastModified, lang, { author: [] }, {}, title);
67
66
  const htmlContext = context;
68
- const timeContext = Time.contextFromFileName(htmlContext, inputFileName);
67
+ const timeContext = this.time.getService().contextFromFileName(htmlContext, inputFileName);
69
68
  Object.assign(htmlContext.time, timeContext);
70
69
  return htmlContext;
71
70
  }
@@ -1,17 +1,2 @@
1
- import { TimeTextBuilder } from "./text/TimeTextBuilder.js";
2
- import { TimeContext } from "@rr0/time";
3
- import { HtmlRR0Context } from "../RR0Context.js";
4
1
  export declare class Time {
5
- static readonly timePathRegex: RegExp;
6
- /**
7
- * Instantiate a Date object matching an ISO date ("1972-08-12 16:34" for instance).
8
- *
9
- * Approximated dates like "~1972" will be converted to exact dates ("1972").
10
- *
11
- * @param isoDate
12
- */
13
- static dateFromIso(isoDate: string): Date;
14
- static parseFileName(fileName: string): RegExpExecArray | null;
15
- static titleFromFile(context: HtmlRR0Context, fileName: string, timeTextBuilder: TimeTextBuilder): string | undefined;
16
- static contextFromFileName(context: HtmlRR0Context, fileName?: string): TimeContext | undefined;
17
2
  }
package/dist/time/Time.js CHANGED
@@ -1,59 +1,2 @@
1
- import { RR0ContextImpl } from "../RR0Context.js";
2
- import { StringUtil } from "../util/string/StringUtil.js";
3
1
  export class Time {
4
- /**
5
- * Instantiate a Date object matching an ISO date ("1972-08-12 16:34" for instance).
6
- *
7
- * Approximated dates like "~1972" will be converted to exact dates ("1972").
8
- *
9
- * @param isoDate
10
- */
11
- static dateFromIso(isoDate) {
12
- isoDate = isoDate.replace("~", "");
13
- if (isoDate.charAt(0) === "-") {
14
- isoDate = "-" + "0".repeat(7 - isoDate.length) + isoDate.substring(1);
15
- }
16
- return new Date(isoDate);
17
- }
18
- static parseFileName(fileName) {
19
- return Time.timePathRegex.exec(fileName);
20
- }
21
- static titleFromFile(context, fileName, timeTextBuilder) {
22
- let title;
23
- const timeContext = Time.contextFromFileName(context, fileName);
24
- if (timeContext) {
25
- const pageContext = new RR0ContextImpl(context.locale, timeContext, context.config, context.people, context.file);
26
- title = timeTextBuilder.build(pageContext);
27
- title = StringUtil.capitalizeFirstLetter(title);
28
- }
29
- return title;
30
- }
31
- static contextFromFileName(context, fileName = context.file.name) {
32
- let timeContext;
33
- let elems;
34
- if (fileName.endsWith("index.html")) {
35
- while ((elems = fileName.split("/")).length < 6) {
36
- fileName = elems.slice(0, elems.length - 1).join("/") + "/0/index.html";
37
- }
38
- }
39
- const timeExec = Time.parseFileName(fileName);
40
- if (timeExec && timeExec.length > 5) {
41
- const pageContext = context.clone();
42
- timeContext = pageContext.time;
43
- const m = parseInt(timeExec[2], 10);
44
- const c = parseInt(timeExec[3], 10);
45
- const d = parseInt(timeExec[4], 10);
46
- const u = parseInt(timeExec[5], 10);
47
- const year = (timeExec[1] ? -1 : 1) * (m * 1000 + c * 100 + d * 10 + u);
48
- timeContext.setYear(year);
49
- const monthStr = timeExec[6];
50
- timeContext.setMonth(monthStr ? parseInt(monthStr, 10) : undefined);
51
- const dayStr = timeExec[7];
52
- timeContext.setDayOfMonth(dayStr ? parseInt(dayStr, 10) : undefined);
53
- timeContext.setHour(undefined);
54
- timeContext.setMinutes(undefined);
55
- }
56
- return timeContext;
57
- }
58
2
  }
59
- Time.timePathRegex = /time\/(-)?(\d)\/(\d)\/(\d)\/(\d)\/?(\d{2})?\/?(\d{2})?\/?(index(_[a-z]{2})?.html)?/;
@@ -13,7 +13,7 @@ describe("TimeEventRenderer", () => {
13
13
  const dataService = new AllDataService([]);
14
14
  const baseUrl = "https://rr0.org";
15
15
  const http = new HttpSource();
16
- const sourceFactory = new SourceFactory(dataService, http, baseUrl, rr0TestUtil.intlOptions);
16
+ const sourceFactory = new SourceFactory(dataService, http, baseUrl, rr0TestUtil.intlOptions, rr0TestUtil.time.getService());
17
17
  const renderer = new CaseSummaryRenderer(new NoteRenderer(new NoteFileCounter()), sourceFactory, new SourceRenderer(rr0TestUtil.time.timeTextBuilder), rr0TestUtil.time.timeElementFactory);
18
18
  test("render event", async () => {
19
19
  const context = rr0TestUtil.time.newHtmlContext("1/9/7/0/03/index.html");
@@ -1,4 +1,3 @@
1
- import { Time } from "./Time.js";
2
1
  import { LinkType } from "ssg-api";
3
2
  export class TimeLinkDefaultHandler {
4
3
  constructor(service, timeTextBuilder) {
@@ -8,9 +7,10 @@ export class TimeLinkDefaultHandler {
8
7
  contents(context) {
9
8
  const prevLink = this.prev(context);
10
9
  if (prevLink) {
11
- let contentUrl = this.service.matchExistingTimeFile(prevLink.url.substring(1));
12
- if (contentUrl != this.service.root) {
13
- const text = Time.titleFromFile(context, contentUrl, this.timeTextBuilder);
10
+ const service = this.service;
11
+ let contentUrl = service.matchExistingTimeFile(prevLink.url.substring(1));
12
+ if (contentUrl != service.root) {
13
+ const text = service.titleFromFile(context, contentUrl, this.timeTextBuilder);
14
14
  if (text) {
15
15
  return { type: LinkType.prev, text, url: "/" + contentUrl };
16
16
  }
@@ -20,11 +20,12 @@ export class TimeLinkDefaultHandler {
20
20
  next(context) {
21
21
  let fileName = context.file.name;
22
22
  if (this.isTimeFile(fileName)) {
23
- const pos = this.service.files.indexOf(fileName);
23
+ const service = this.service;
24
+ const pos = service.files.indexOf(fileName);
24
25
  if (pos >= 0) {
25
- const nextFile = this.service.files[pos + 1];
26
+ const nextFile = service.files[pos + 1];
26
27
  if (nextFile) {
27
- const text = Time.titleFromFile(context, nextFile, this.timeTextBuilder);
28
+ const text = service.titleFromFile(context, nextFile, this.timeTextBuilder);
28
29
  return { type: LinkType.next, text, url: "/" + nextFile };
29
30
  }
30
31
  }
@@ -33,11 +34,12 @@ export class TimeLinkDefaultHandler {
33
34
  prev(context) {
34
35
  let fileName = context.file.name;
35
36
  if (this.isTimeFile(fileName)) {
36
- const pos = this.service.files.indexOf(fileName);
37
+ const service = this.service;
38
+ const pos = service.files.indexOf(fileName);
37
39
  if (pos >= 0) {
38
- const prevFile = this.service.files[pos - 1];
40
+ const prevFile = service.files[pos - 1];
39
41
  if (prevFile) {
40
- const text = Time.titleFromFile(context, prevFile, this.timeTextBuilder);
42
+ const text = service.titleFromFile(context, prevFile, this.timeTextBuilder);
41
43
  if (text) {
42
44
  return { type: LinkType.prev, text, url: "/" + prevFile };
43
45
  }
@@ -2,6 +2,8 @@ import { TimeRenderer } from "./html/TimeRenderer.js";
2
2
  import { TimeTextBuilder } from "./text/TimeTextBuilder.js";
3
3
  import { TimeUrlBuilder } from "./TimeUrlBuilder";
4
4
  import { AbstractDataService, AllDataService, RR0Event } from "@rr0/data";
5
+ import { HtmlRR0Context } from "../RR0Context";
6
+ import { TimeContext } from "@rr0/time";
5
7
  export type TimeServiceOptions = {
6
8
  readonly root: string;
7
9
  readonly files: string[];
@@ -9,12 +11,28 @@ export type TimeServiceOptions = {
9
11
  export declare class TimeService extends AbstractDataService<RR0Event> {
10
12
  readonly textBuilder: TimeTextBuilder;
11
13
  readonly urlBuilder: TimeUrlBuilder;
14
+ readonly timePathRegex: RegExp;
15
+ static readonly defaultRegex: RegExp;
12
16
  readonly renderer: TimeRenderer;
13
17
  readonly root: string;
14
- constructor(dataService: AllDataService, textBuilder: TimeTextBuilder, urlBuilder: TimeUrlBuilder, options: TimeServiceOptions);
18
+ constructor(dataService: AllDataService, textBuilder: TimeTextBuilder, urlBuilder: TimeUrlBuilder, options: TimeServiceOptions, timePathRegex?: RegExp);
15
19
  isTimeFile(filePath: string): boolean;
16
20
  /**
17
21
  * @return the found time URL or undefined if not found.
18
22
  */
19
23
  matchExistingTimeFile(url: string): string | undefined;
24
+ /**
25
+ * Instantiate a Date object matching an ISO date ("1972-08-12 16:34" for instance).
26
+ *
27
+ * Approximated dates like "~1972" will be converted to exact dates ("1972").
28
+ *
29
+ * @param isoDate
30
+ */
31
+ dateFromIso(isoDate: string): Date;
32
+ parseFileName(fileName: string): RegExpExecArray | null;
33
+ titleFromFile(context: HtmlRR0Context, fileName: string, timeTextBuilder: TimeTextBuilder): string | undefined;
34
+ contextFromFileName(context: HtmlRR0Context, fileName?: string): TimeContext | undefined;
35
+ gSetTimeFromPath(context: HtmlRR0Context, filePath: string): TimeContext | undefined;
36
+ setContextFromFile(context: HtmlRR0Context, filePath: string): void;
37
+ protected setTimeFromPath(context: HtmlRR0Context, filePath: string): void;
20
38
  }
@@ -1,10 +1,13 @@
1
1
  import { TimeRenderer } from "./html/TimeRenderer.js";
2
2
  import { AbstractDataService } from "@rr0/data";
3
+ import { RR0ContextImpl } from "../RR0Context";
4
+ import { StringUtil } from "../util";
3
5
  export class TimeService extends AbstractDataService {
4
- constructor(dataService, textBuilder, urlBuilder, options) {
6
+ constructor(dataService, textBuilder, urlBuilder, options, timePathRegex = TimeService.defaultRegex) {
5
7
  super(dataService, null, options.files);
6
8
  this.textBuilder = textBuilder;
7
9
  this.urlBuilder = urlBuilder;
10
+ this.timePathRegex = timePathRegex;
8
11
  this.root = options.root;
9
12
  this.renderer = new TimeRenderer(this, this.textBuilder);
10
13
  }
@@ -21,4 +24,78 @@ export class TimeService extends AbstractDataService {
21
24
  }
22
25
  return url === this.root ? undefined : url;
23
26
  }
27
+ /**
28
+ * Instantiate a Date object matching an ISO date ("1972-08-12 16:34" for instance).
29
+ *
30
+ * Approximated dates like "~1972" will be converted to exact dates ("1972").
31
+ *
32
+ * @param isoDate
33
+ */
34
+ dateFromIso(isoDate) {
35
+ isoDate = isoDate.replace("~", "");
36
+ if (isoDate.charAt(0) === "-") {
37
+ isoDate = "-" + "0".repeat(7 - isoDate.length) + isoDate.substring(1);
38
+ }
39
+ return new Date(isoDate);
40
+ }
41
+ parseFileName(fileName) {
42
+ return this.timePathRegex.exec(fileName);
43
+ }
44
+ titleFromFile(context, fileName, timeTextBuilder) {
45
+ let title;
46
+ const timeContext = this.contextFromFileName(context, fileName);
47
+ if (timeContext) {
48
+ const pageContext = new RR0ContextImpl(context.locale, timeContext, context.config, context.people, context.file);
49
+ title = timeTextBuilder.build(pageContext);
50
+ title = StringUtil.capitalizeFirstLetter(title);
51
+ }
52
+ return title;
53
+ }
54
+ contextFromFileName(context, fileName = context.file.name) {
55
+ let timeContext;
56
+ let elems;
57
+ if (fileName.endsWith("index.html")) {
58
+ while ((elems = fileName.split("/")).length < 6) {
59
+ fileName = elems.slice(0, elems.length - 1).join("/") + "/0/index.html";
60
+ }
61
+ }
62
+ const timeExec = this.parseFileName(fileName);
63
+ if (timeExec && timeExec.length > 5) {
64
+ const pageContext = context.clone();
65
+ timeContext = pageContext.time;
66
+ const m = parseInt(timeExec[2], 10);
67
+ const c = parseInt(timeExec[3], 10);
68
+ const d = parseInt(timeExec[4], 10);
69
+ const u = parseInt(timeExec[5], 10);
70
+ const year = (timeExec[1] ? -1 : 1) * (m * 1000 + c * 100 + d * 10 + u);
71
+ timeContext.setYear(year);
72
+ const monthStr = timeExec[6];
73
+ timeContext.setMonth(monthStr ? parseInt(monthStr, 10) : undefined);
74
+ const dayStr = timeExec[7];
75
+ timeContext.setDayOfMonth(dayStr ? parseInt(dayStr, 10) : undefined);
76
+ timeContext.setHour(undefined);
77
+ timeContext.setMinutes(undefined);
78
+ }
79
+ return timeContext;
80
+ }
81
+ gSetTimeFromPath(context, filePath) {
82
+ const time = context.time;
83
+ time.reset();
84
+ const newTimeContext = this.contextFromFileName(context, filePath);
85
+ if (newTimeContext) {
86
+ time.setYear(newTimeContext.getYear());
87
+ time.setMonth(newTimeContext.getMonth());
88
+ time.setDayOfMonth(newTimeContext.getDayOfMonth());
89
+ // context.time.from = context.time
90
+ }
91
+ return newTimeContext;
92
+ }
93
+ setContextFromFile(context, filePath) {
94
+ this.setTimeFromPath(context, filePath);
95
+ }
96
+ setTimeFromPath(context, filePath) {
97
+ context.time.reset(); // Don't use time context from previous page.
98
+ this.gSetTimeFromPath(context, filePath);
99
+ }
24
100
  }
101
+ TimeService.defaultRegex = /time\/(-)?(\d)\/(\d)\/(\d)\/(\d)\/?(\d{2})?\/?(\d{2})?\/?(index(_[a-z]{2})?.html)?/;
@@ -1,12 +1,12 @@
1
- import { Time } from "./Time.js";
2
1
  import { TimeContext } from "@rr0/time";
3
2
  import { RR0ContextImpl } from "../RR0Context.js";
4
3
  import { describe, expect, test } from "@javarome/testscript";
5
4
  import { rr0TestUtil } from "../test/index.js";
6
5
  import { FileContents } from "@javarome/fileutil";
7
6
  describe("Time", () => {
7
+ const time = rr0TestUtil.time.getService();
8
8
  test("parse", () => {
9
- const exec = Time.parseFileName("time/-0/0/1/1/index.html");
9
+ const exec = time.parseFileName("time/-0/0/1/1/index.html");
10
10
  let pos = 0;
11
11
  expect(exec[++pos]).toBe("-");
12
12
  expect(exec[++pos]).toBe("0");
@@ -23,23 +23,23 @@ describe("Time", () => {
23
23
  const context = new RR0ContextImpl("fr", timeContext, config);
24
24
  test("recognize year before 0 AD", () => {
25
25
  context.file = new FileContents("time/-0/0/1/1/index.html", "utf-8", "", new Date("2012-08-12"), { lang: "fr", variants: [] });
26
- const newTimeContext = Time.contextFromFileName(context);
26
+ const newTimeContext = time.contextFromFileName(context);
27
27
  expect(newTimeContext.getYear()).toBe(-11);
28
28
  });
29
29
  test("recognize year after 0 AD", () => {
30
30
  context.file = new FileContents("time/1/9/7/2/index.html", "utf-8", "", new Date("2012-08-12"), { lang: "fr", variants: [] });
31
- const newTimeContext = Time.contextFromFileName(context);
31
+ const newTimeContext = time.contextFromFileName(context);
32
32
  expect(newTimeContext.getYear()).toBe(1972);
33
33
  });
34
34
  test("recognize month", () => {
35
35
  context.file = new FileContents("time/1/9/7/2/08/index.html", "utf-8", "", new Date("2012-08-12"), { lang: "fr", variants: [] });
36
- const newTimeContext = Time.contextFromFileName(context);
36
+ const newTimeContext = time.contextFromFileName(context);
37
37
  expect(newTimeContext.getYear()).toBe(1972);
38
38
  expect(newTimeContext.getMonth()).toBe(8);
39
39
  });
40
40
  test("recognize day", () => {
41
41
  context.file = new FileContents("time/1/9/7/2/08/12/index.html", "utf-8", "", new Date("2012-08-12"), { lang: "fr", variants: [] });
42
- const newTimeContext = Time.contextFromFileName(context);
42
+ const newTimeContext = time.contextFromFileName(context);
43
43
  expect(newTimeContext.getYear()).toBe(1972);
44
44
  expect(newTimeContext.getMonth()).toBe(8);
45
45
  expect(newTimeContext.getDayOfMonth()).toBe(12);
@@ -1,12 +1,11 @@
1
1
  import { SsiTitleReplaceCommand } from "./SsiTitleReplaceCommand.js";
2
2
  import { rr0TestUtil } from "../test/index.js";
3
3
  import { describe, expect, test } from "@javarome/testscript";
4
- import { Time } from "./Time.js";
5
4
  describe("TitleReplaceCommand", () => {
6
5
  let timeTextBuilder = rr0TestUtil.time.timeTextBuilder;
7
6
  const timeDefaultHandler = (context) => {
8
7
  let title;
9
- title = Time.titleFromFile(context, context.file.name, timeTextBuilder);
8
+ title = rr0TestUtil.time.getService().titleFromFile(context, context.file.name, timeTextBuilder);
10
9
  return title;
11
10
  };
12
11
  describe("Time page", () => {
@@ -15,11 +15,12 @@ describe("ChronologyReplacer", () => {
15
15
  const dataService = new AllDataService([]);
16
16
  const baseUrl = "https://rr0.org";
17
17
  const http = new HttpSource();
18
- const sourceFactory = new SourceFactory(dataService, http, baseUrl, rr0TestUtil.intlOptions);
18
+ const timeTestUtil = rr0TestUtil.time;
19
+ const sourceFactory = new SourceFactory(dataService, http, baseUrl, rr0TestUtil.intlOptions, timeTestUtil.getService());
19
20
  const timeTextBuilder = new TimeTextBuilder(rr0TestUtil.intlOptions);
20
- const caseRenderer = new CaseSummaryRenderer(new NoteRenderer(new NoteFileCounter()), sourceFactory, new SourceRenderer(timeTextBuilder), rr0TestUtil.time.timeElementFactory);
21
+ const caseRenderer = new CaseSummaryRenderer(new NoteRenderer(new NoteFileCounter()), sourceFactory, new SourceRenderer(timeTextBuilder), timeTestUtil.timeElementFactory);
21
22
  chronologyReplacer = new ChronologyReplacer([urecatRR0Mapping], caseRenderer);
22
- context = rr0TestUtil.time.newHtmlContext("index.html");
23
+ context = timeTestUtil.newHtmlContext("index.html");
23
24
  context.time.setYear(undefined);
24
25
  // context.time.setMonth(3)
25
26
  });
@@ -41,8 +41,8 @@ export class DatasourceTestCase {
41
41
  const dataService = new AllDataService([]);
42
42
  const baseUrl = "https://rr0.org";
43
43
  const http = new HttpSource();
44
- const sourceFactory = new SourceFactory(dataService, http, baseUrl, this.intlOptions);
45
44
  const timeService = await rr0TestUtil.time.getService();
45
+ const sourceFactory = new SourceFactory(dataService, http, baseUrl, this.intlOptions, timeService);
46
46
  const timeElementFactory = new TimeElementFactory(new TimeRenderer(timeService, this.timeTextBuilder));
47
47
  const eventRenderer = new CaseSummaryRenderer(new NoteRenderer(new NoteFileCounter()), sourceFactory, new SourceRenderer(this.timeTextBuilder), timeElementFactory);
48
48
  const items = [];
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.1.21",
5
+ "version": "0.1.23",
6
6
  "description": "RR0 Content Management System (CMS)",
7
7
  "exports": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "@rr0/place": "^0.3.2",
35
35
  "@rr0/data": "^0.1.2",
36
36
  "@javarome/fileutil": "^0.3.6",
37
- "ssg-api": "^1.16.11",
37
+ "ssg-api": "^1.16.12",
38
38
  "canvas": "^2.11.2",
39
39
  "csv-parser": "^3.0.0",
40
40
  "glob": "^11.0.0",