@rr0/cms 0.3.14 → 0.3.15

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 (41) hide show
  1. package/dist/OpenGraphCommand.d.ts +3 -2
  2. package/dist/OpenGraphCommand.js +3 -2
  3. package/dist/OpenGraphCommand.test.js +1 -1
  4. package/dist/RR0Build.d.ts +10 -5
  5. package/dist/RR0Build.js +35 -46
  6. package/dist/RR0Build.test.js +19 -2
  7. package/dist/anchor/AnchorReplaceCommandTest.js +1 -2
  8. package/dist/book/BookService.test.js +2 -2
  9. package/dist/people/author/AuthorReplaceCommand.d.ts +3 -3
  10. package/dist/people/author/AuthorReplaceCommand.js +3 -3
  11. package/dist/people/author/AuthorReplaceCommandTest.js +4 -4
  12. package/dist/science/crypto/ufo/enquete/dossier/CaseDirectoryStep.test.js +2 -2
  13. package/dist/science/crypto/ufo/enquete/dossier/CaseService.js +1 -1
  14. package/dist/time/EventRenderer.d.ts +2 -1
  15. package/dist/time/EventRenderer.js +2 -2
  16. package/dist/time/TimeLinkDefaultHandler.d.ts +3 -1
  17. package/dist/time/TimeLinkDefaultHandler.js +6 -5
  18. package/dist/time/TimeOptions.d.ts +4 -0
  19. package/dist/time/TimeOptions.js +1 -0
  20. package/dist/time/TimeService.d.ts +3 -24
  21. package/dist/time/TimeService.js +2 -33
  22. package/dist/time/TimeTagReplaceCommand.test.js +3 -3
  23. package/dist/time/TimeTestUtil.d.ts +6 -4
  24. package/dist/time/TimeTestUtil.js +10 -8
  25. package/dist/time/TimeUrlBuilder.d.ts +7 -3
  26. package/dist/time/TimeUrlBuilder.js +14 -0
  27. package/dist/time/TimeUrlBuilder.test.js +2 -1
  28. package/dist/time/datasource/ChronologyReplacer.js +1 -1
  29. package/dist/time/datasource/ChronologyReplacerFactory.d.ts +3 -3
  30. package/dist/time/datasource/ChronologyReplacerFactory.js +3 -3
  31. package/dist/time/datasource/DatasourceTestCase.js +1 -1
  32. package/dist/time/datasource/rr0/RR0HttpDatasource.js +1 -1
  33. package/dist/time/html/TimeElementFactory.js +3 -3
  34. package/dist/time/html/TimeRenderer.d.ts +4 -3
  35. package/dist/time/html/TimeRenderer.js +12 -9
  36. package/dist/time/html/TimeReplacer.test.js +15 -14
  37. package/dist/time/html/TimeReplacerFactory.d.ts +3 -1
  38. package/dist/time/html/TimeReplacerFactory.js +3 -2
  39. package/dist/time/index.d.ts +1 -0
  40. package/dist/time/index.js +1 -0
  41. package/package.json +1 -1
@@ -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 { TimeService } from "./time/index.js";
4
+ import { TimeService, TimeTextBuilder } from "./time/index.js";
5
5
  /**
6
6
  * Create a preview image for each page sharing.
7
7
  */
@@ -10,10 +10,11 @@ export declare class OpenGraphCommand implements ReplaceCommand<HtmlRR0Context>
10
10
  protected timeFiles: string[];
11
11
  protected baseUrl: string;
12
12
  protected timeService: TimeService;
13
+ readonly timeTextBuilder: TimeTextBuilder;
13
14
  protected width: number;
14
15
  protected height: number;
15
16
  protected num: number;
16
- constructor(outDir: string, timeFiles: string[], baseUrl: string, timeService: TimeService, width?: number, height?: number);
17
+ constructor(outDir: string, timeFiles: string[], baseUrl: string, timeService: TimeService, timeTextBuilder: TimeTextBuilder, width?: number, height?: number);
17
18
  execute(context: HtmlRR0Context): Promise<void>;
18
19
  getInfoStr(context: HtmlRR0Context): string;
19
20
  contentStepEnd(): Promise<void>;
@@ -6,11 +6,12 @@ import assert from "assert";
6
6
  * Create a preview image for each page sharing.
7
7
  */
8
8
  export class OpenGraphCommand {
9
- constructor(outDir, timeFiles, baseUrl, timeService, width = 1200, height = 600) {
9
+ constructor(outDir, timeFiles, baseUrl, timeService, timeTextBuilder, width = 1200, height = 600) {
10
10
  this.outDir = outDir;
11
11
  this.timeFiles = timeFiles;
12
12
  this.baseUrl = baseUrl;
13
13
  this.timeService = timeService;
14
+ this.timeTextBuilder = timeTextBuilder;
14
15
  this.width = width;
15
16
  this.height = height;
16
17
  this.num = 0;
@@ -55,7 +56,7 @@ export class OpenGraphCommand {
55
56
  context.time.setDayOfMonth(timeContext.getDayOfMonth());
56
57
  context.time.setHour(undefined);
57
58
  context.time.setMinutes(undefined);
58
- timeStr = this.timeService.textBuilder.build(context);
59
+ timeStr = this.timeTextBuilder.build(context);
59
60
  }
60
61
  }
61
62
  const copyrightStr = context.file.meta.copyright || "RR0.org";
@@ -6,7 +6,7 @@ describe("OpenGraphCommand", () => {
6
6
  test("time page", () => {
7
7
  const timeFile = rr0TestUtil.time.filePath("0/0/6/5/index.html");
8
8
  const context = rr0TestUtil.newHtmlContext(timeFile, "");
9
- const command = new OpenGraphCommand(outDir, [context.file.name], "https://rr0.org", rr0TestUtil.time.getService());
9
+ const command = new OpenGraphCommand(outDir, [context.file.name], "https://rr0.org", rr0TestUtil.time.getService(), rr0TestUtil.time.timeTextBuilder);
10
10
  expect(command.getInfoStr(context)).toBe("Chronologie, RR0.org");
11
11
  });
12
12
  });
@@ -1,13 +1,15 @@
1
- import { RR0CaseMapping, TimeService, TimeServiceOptions, TimeTextBuilder } from "./time";
1
+ import { RR0CaseMapping, TimeRenderer, TimeService, TimeTextBuilder, TimeUrlBuilder } from "./time";
2
2
  import { CaseFactory } from "./science/index.js";
3
3
  import { CityService, DepartmentService, OrganizationService } from "./org/index.js";
4
- import { RR0ContextImpl } from "./RR0Context.js";
4
+ import { HtmlRR0Context, RR0ContextImpl } from "./RR0Context.js";
5
5
  import { FileWriteConfig } from "ssg-api";
6
6
  import { PeopleDirectoryStepOptions } from "./people";
7
7
  import { AllDataService, PeopleFactory } from "@rr0/data";
8
8
  import { GooglePlaceService } from "@rr0/place";
9
9
  import { CountryService } from "./org/country/CountryService";
10
10
  import { BuildContext } from "./BuildContext";
11
+ import { ReplaceCommand } from "ssg-api/dist/src/step/content/replace";
12
+ import { TimeOptions } from "./time/TimeOptions";
11
13
  export interface RR0BuildOptions {
12
14
  contentRoots: string[];
13
15
  copies: string[];
@@ -15,7 +17,7 @@ export interface RR0BuildOptions {
15
17
  locale: string;
16
18
  googleMapsApiKey: string;
17
19
  mail: string;
18
- timeOptions: TimeServiceOptions;
20
+ timeOptions: TimeOptions;
19
21
  siteBaseUrl: string;
20
22
  timeFormat: Intl.DateTimeFormatOptions;
21
23
  directoryPages: string[];
@@ -25,6 +27,7 @@ export interface RR0BuildOptions {
25
27
  directoryExcluded: string[];
26
28
  directoryOptions: PeopleDirectoryStepOptions;
27
29
  mappings: RR0CaseMapping<any>[];
30
+ contentReplacers: ReplaceCommand<HtmlRR0Context>[];
28
31
  }
29
32
  export interface RR0BuildArgs {
30
33
  /**
@@ -59,14 +62,16 @@ export declare class RR0Build implements BuildContext {
59
62
  readonly context: RR0ContextImpl;
60
63
  readonly placeService: GooglePlaceService;
61
64
  readonly orgService: OrganizationService<any>;
62
- readonly timeService: TimeService;
63
65
  readonly caseFactory: CaseFactory;
64
66
  readonly dataService: AllDataService;
65
67
  readonly peopleFactory: PeopleFactory;
66
- readonly timeTextBuilder: TimeTextBuilder;
67
68
  readonly cityService: CityService;
68
69
  readonly departmentService: DepartmentService;
69
70
  readonly countryService: CountryService;
71
+ readonly timeTextBuilder: TimeTextBuilder;
72
+ readonly timeService: TimeService;
73
+ readonly timeRenderer: TimeRenderer;
74
+ readonly timeUrlBuilder: TimeUrlBuilder;
70
75
  constructor(options: RR0BuildOptions);
71
76
  run(args: RR0BuildArgs): Promise<void>;
72
77
  }
package/dist/RR0Build.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import { CaseSummaryRenderer, ChronologyReplacerFactory, CsvMapper, EventReplacer, EventReplacerFactory, HttpSource, RR0Mapping, SsiTitleReplaceCommand, TimeElementFactory, TimeLinkDefaultHandler, TimeReplacer, TimeReplacerFactory, TimeService, TimeTextBuilder, TimeUrlBuilder } from "./time";
3
+ import { CaseSummaryRenderer, ChronologyReplacerFactory, CsvMapper, EventReplacer, EventReplacerFactory, HttpSource, SsiTitleReplaceCommand, TimeElementFactory, TimeLinkDefaultHandler, TimeRenderer, TimeReplacer, TimeReplacerFactory, TimeService, TimeTextBuilder, TimeUrlBuilder } from "./time";
4
4
  import { CaseDirectoryStep, CaseFactory, CaseService } from "./science/index.js";
5
5
  import { PlaceReplacerFactory } from "./place/index.js";
6
6
  import { cities, CityService, CmsOrganizationFactory, countries, departments, DepartmentService, OrganizationService, regions, RegionService } from "./org/index.js";
7
7
  import { RR0ContextImpl } from "./RR0Context.js";
8
8
  import { HtmlTable } from "./util/index.js";
9
- import { AngularExpressionReplaceCommand, ClassDomReplaceCommand, CopyStep, DomReplaceCommand, HtAccessToNetlifyConfigReplaceCommand, HtmlFileContents, Ssg, SsiEchoVarReplaceCommand, SsiIfReplaceCommand, SsiIncludeReplaceCommand, SsiLastModifiedReplaceCommand, SsiSetVarReplaceCommand, StringEchoVarReplaceCommand } from "ssg-api";
10
- import { LanguageReplaceCommand } from "./lang/index.js";
9
+ import { ClassDomReplaceCommand, CopyStep, DomReplaceCommand, HtAccessToNetlifyConfigReplaceCommand, HtmlFileContents, Ssg, SsiIncludeReplaceCommand } from "ssg-api";
11
10
  import { AuthorReplaceCommand, PeopleDirectoryStepFactory, PeopleReplacerFactory, WitnessReplacerFactory } from "./people";
12
11
  import { PersistentSourceRegistry, SourceFileCounter, SourceIndexStep, SourceRenderer, SourceReplacer, SourceReplacerFactory } from "./source";
13
12
  import { NoteFileCounter, NoteRenderer, NoteReplacer, NoteReplacerFactory } from "./note/index.js";
@@ -16,16 +15,13 @@ import { MetaLinkReplaceCommand } from "./MetaLinkReplaceCommand.js";
16
15
  import { OutlineReplaceCommand } from "./outline/index.js";
17
16
  import { ImageCommand } from "./ImageCommand.js";
18
17
  import { SearchIndexStep, SearchVisitor } from "./search/index.js";
19
- import { BaseReplaceCommand } from "./BaseReplaceCommand.js";
20
18
  import { OpenGraphCommand } from "./OpenGraphCommand.js";
21
- import { DescriptionReplaceCommand } from "./DescriptionReplaceCommand.js";
22
19
  import { BookContentVisitor, BookDirectoryStep } from "./book/index.js";
23
20
  import { IndexedReplacerFactory } from "./index/IndexedReplacerFactory.js";
24
21
  import { APIFactory, CodeReplacerFactory } from "./tech/index.js";
25
22
  import { RR0ContentStep } from "./RR0ContentStep.js";
26
23
  import { UnitReplaceCommand } from "./value/index.js";
27
24
  import { DefaultContentVisitor } from "./DefaultContentVisitor.js";
28
- import { rr0DefaultCopyright } from "./RR0DefaultCopyright.js";
29
25
  import { TimeContext } from "@rr0/time";
30
26
  import { writeFile } from "@javarome/fileutil";
31
27
  import { AllDataService, EventDataFactory, PeopleFactory, PeopleService, RR0EventFactory, TypedDataFactory } from "@rr0/data";
@@ -68,8 +64,7 @@ export class RR0Build {
68
64
  this.cityService = cityService;
69
65
  const timeTextBuilder = this.timeTextBuilder = new TimeTextBuilder(options.timeFormat);
70
66
  const timeOptions = options.timeOptions;
71
- const timeRoot = timeOptions.root;
72
- const timeUrlBuilder = new TimeUrlBuilder({ rootDir: timeRoot });
67
+ const timeUrlBuilder = this.timeUrlBuilder = new TimeUrlBuilder(timeOptions);
73
68
  const sightingFactory = new EventDataFactory(eventFactory, "sighting", ["index"]);
74
69
  const caseFactory = this.caseFactory = new CaseFactory(eventFactory);
75
70
  const peopleFactory = this.peopleFactory = new PeopleFactory(eventFactory);
@@ -81,14 +76,15 @@ export class RR0Build {
81
76
  dataService.getFromDir("", ["people", "case"]).then(data => {
82
77
  console.debug(data);
83
78
  });
84
- this.timeService = new TimeService(dataService, timeTextBuilder, timeUrlBuilder, timeOptions);
79
+ this.timeRenderer = new TimeRenderer(timeUrlBuilder, timeTextBuilder);
80
+ this.timeService = new TimeService(dataService, timeOptions);
85
81
  }
86
82
  async run(args) {
87
83
  const context = this.context;
88
84
  const timeFiles = this.options.timeOptions.files;
89
85
  context.setVar("timeFilesCount", timeFiles.length);
90
- const timeService = this.timeService;
91
- const timeElementFactory = new TimeElementFactory(timeService.renderer);
86
+ const timeRenderer = this.timeRenderer;
87
+ const timeElementFactory = new TimeElementFactory(timeRenderer);
92
88
  const timeReplacer = new TimeReplacer(timeElementFactory);
93
89
  const caseFiles = await this.caseFactory.getFiles();
94
90
  let dataService = this.dataService;
@@ -112,9 +108,10 @@ export class RR0Build {
112
108
  const peopleSteps = await peopleDirectoryFactory.create(directoryOptions);
113
109
  // Publish case.json files so that vraiufo.com will find them
114
110
  const copies = this.options.copies;
115
- copies.push(...(ufoCasesStep.config.rootDirs).map(dir => path.join(dir, "case.json")));
111
+ const ufoCasesRootDirs = ufoCasesStep.config.rootDirs;
112
+ copies.push(...ufoCasesRootDirs.map(dir => path.join(dir, "case.json")));
116
113
  const outDir = this.options.outDir;
117
- await writeFile(path.join(outDir, "casesDirs.json"), JSON.stringify(ufoCasesStep.config.rootDirs), "utf-8");
114
+ await writeFile(path.join(outDir, "casesDirs.json"), JSON.stringify(ufoCasesRootDirs), "utf-8");
118
115
  const dirsContainingPeopleJson = peopleSteps.reduce((rootDirs, peopleStep) => {
119
116
  rootDirs.push(...peopleStep.config.rootDirs);
120
117
  return rootDirs;
@@ -128,52 +125,26 @@ export class RR0Build {
128
125
  const baseUrl = this.options.siteBaseUrl;
129
126
  const timeFormat = this.options.timeFormat;
130
127
  const sourceRegistryFileName = this.options.sourceRegistryFileName;
128
+ const timeService = this.timeService;
131
129
  const sourceFactory = new PersistentSourceRegistry(dataService, http, baseUrl, sourceRegistryFileName, timeFormat, timeService);
132
130
  const noteCounter = new NoteFileCounter();
133
131
  const noteRenderer = new NoteRenderer(noteCounter);
134
132
  const caseRenderer = new CaseSummaryRenderer(noteRenderer, sourceFactory, sourceRenderer, timeElementFactory);
135
- const mappings = this.options.mappings || [new RR0Mapping({ read: ["fetch"], write: ["backup"] })];
133
+ const mappings = this.options.mappings || [];
136
134
  mappings.forEach(mapping => mapping.init(this));
137
- const databaseAggregationCommand = new DomReplaceCommand(".contents ul", new ChronologyReplacerFactory(timeService, mappings, caseRenderer));
135
+ const timeUrlBuilder = this.timeUrlBuilder;
136
+ const databaseAggregationCommand = new DomReplaceCommand(".contents ul", new ChronologyReplacerFactory(timeUrlBuilder, mappings, caseRenderer));
138
137
  const timeDefaultHandler = (context) => {
139
138
  let title;
140
139
  title = timeService.titleFromFile(context, context.file.name, timeTextBuilder);
141
140
  return title;
142
141
  };
143
- const pageReplaceCommands = [
144
- new BaseReplaceCommand("/"),
145
- new LanguageReplaceCommand(),
146
- new SsiEchoVarReplaceCommand("copyright", [rr0DefaultCopyright]),
147
- new StringEchoVarReplaceCommand(),
148
- new AngularExpressionReplaceCommand(),
149
- new SsiIfReplaceCommand(),
150
- new SsiSetVarReplaceCommand("title", (_match, ...args) => `<title>${args[0]}</title>`),
151
- new SsiSetVarReplaceCommand("url", (_match, ...args) => `<meta name="url" content="${args[0]}"/>`),
152
- new SsiLastModifiedReplaceCommand(timeFormat),
153
- new SsiTitleReplaceCommand([timeDefaultHandler]),
154
- new DescriptionReplaceCommand("UFO data for french-reading people", "abstract"),
155
- new AuthorReplaceCommand(timeService)
156
- ];
157
142
  const sourceCounter = new SourceFileCounter();
158
143
  const sourceReplacer = new SourceReplacer(sourceRenderer, sourceFactory, sourceCounter);
159
144
  const sourceReplacerFactory = new SourceReplacerFactory(sourceReplacer);
160
145
  const noteReplacer = new NoteReplacer(noteRenderer);
161
146
  const noteReplacerFactory = new NoteReplacerFactory(noteReplacer);
162
147
  const eventReplacer = new EventReplacer(caseRenderer, dataService);
163
- const contentsReplaceCommand = [
164
- new ClassDomReplaceCommand(new EventReplacerFactory(eventReplacer), "event"),
165
- new ClassDomReplaceCommand(sourceReplacerFactory, "source"),
166
- new DomReplaceCommand("time", new TimeReplacerFactory(timeReplacer)),
167
- new DomReplaceCommand("code", new CodeReplacerFactory()),
168
- new ClassDomReplaceCommand(new PeopleReplacerFactory(peopleService, peopleRenderer), "people"),
169
- new ClassDomReplaceCommand(new PlaceReplacerFactory(), "place"),
170
- new ClassDomReplaceCommand(new WitnessReplacerFactory(), "temoin", "temoin1", "temoin2", "temoin3"),
171
- new ClassDomReplaceCommand(noteReplacerFactory, "note"),
172
- new ClassDomReplaceCommand(new IndexedReplacerFactory(), "indexed"),
173
- new UnitReplaceCommand(),
174
- new MetaLinkReplaceCommand(new TimeLinkDefaultHandler(timeService, timeTextBuilder)),
175
- databaseAggregationCommand
176
- ];
177
148
  const ssg = new Ssg(config);
178
149
  const getOutputPath = (context) => path.join(outDir, context.file.name);
179
150
  const force = args.force === "true";
@@ -204,8 +175,7 @@ export class RR0Build {
204
175
  replacements: [new class extends SsiIncludeReplaceCommand {
205
176
  filePath(context, fileNameArg) {
206
177
  const dirName = path.dirname(context.file.name);
207
- return fileNameArg.startsWith("/") ?
208
- path.join(process.cwd(), fileNameArg) : path.join(dirName, fileNameArg);
178
+ return fileNameArg.startsWith("/") ? path.join(process.cwd(), fileNameArg) : path.join(dirName, fileNameArg);
209
179
  }
210
180
  }([csvTransformer])],
211
181
  getOutputPath
@@ -222,13 +192,32 @@ export class RR0Build {
222
192
  if (args.books) {
223
193
  contentVisitors.push(new BookContentVisitor(bookMeta, bookLinks));
224
194
  }
195
+ const pageReplaceCommands = [
196
+ ...this.options.contentReplacers,
197
+ new SsiTitleReplaceCommand([timeDefaultHandler]),
198
+ new AuthorReplaceCommand(timeRenderer)
199
+ ];
200
+ const contentsReplaceCommand = [
201
+ new ClassDomReplaceCommand(new EventReplacerFactory(eventReplacer), "event"),
202
+ new ClassDomReplaceCommand(sourceReplacerFactory, "source"),
203
+ new DomReplaceCommand("time", new TimeReplacerFactory(timeReplacer, timeUrlBuilder)),
204
+ new DomReplaceCommand("code", new CodeReplacerFactory()),
205
+ new ClassDomReplaceCommand(new PeopleReplacerFactory(peopleService, peopleRenderer), "people"),
206
+ new ClassDomReplaceCommand(new PlaceReplacerFactory(), "place"),
207
+ new ClassDomReplaceCommand(new WitnessReplacerFactory(), "temoin", "temoin1", "temoin2", "temoin3"),
208
+ new ClassDomReplaceCommand(noteReplacerFactory, "note"),
209
+ new ClassDomReplaceCommand(new IndexedReplacerFactory(), "indexed"),
210
+ new UnitReplaceCommand(),
211
+ new MetaLinkReplaceCommand(new TimeLinkDefaultHandler(timeService, timeUrlBuilder, timeTextBuilder)),
212
+ databaseAggregationCommand
213
+ ];
225
214
  const contentReplacements = [
226
215
  ...pageReplaceCommands,
227
216
  ...contentsReplaceCommand,
228
217
  new OutlineReplaceCommand(),
229
218
  new AnchorReplaceCommand(baseUrl, [new CaseAnchorHandler(caseService, timeTextBuilder), new DataAnchorHandler(dataService)]),
230
219
  new ImageCommand(outDir, 275, 500),
231
- new OpenGraphCommand(outDir, timeFiles, baseUrl, timeService)
220
+ new OpenGraphCommand(outDir, timeFiles, baseUrl, timeService, timeTextBuilder)
232
221
  ];
233
222
  ssg.add(new RR0ContentStep({
234
223
  contentConfigs: [{ roots: contentRoots, replacements: contentReplacements, getOutputPath }],
@@ -5,6 +5,11 @@ import { BaseOvniFranceRR0Mapping, FuforaRR0Mapping, NuforcRR0Mapping, RR0Mappin
5
5
  import { testFilePath } from "./test";
6
6
  import * as process from "node:process";
7
7
  import { GeipanRR0Mapping } from "./org/eu/fr/cnes/geipan/geipan/GeipanRR0Mapping";
8
+ import { BaseReplaceCommand } from "./BaseReplaceCommand";
9
+ import { LanguageReplaceCommand } from "./lang";
10
+ import { AngularExpressionReplaceCommand, SsiEchoVarReplaceCommand, SsiIfReplaceCommand, SsiLastModifiedReplaceCommand, SsiSetVarReplaceCommand, StringEchoVarReplaceCommand } from "ssg-api";
11
+ import { rr0DefaultCopyright } from "./RR0DefaultCopyright";
12
+ import { DescriptionReplaceCommand } from "./DescriptionReplaceCommand";
8
13
  describe("Build", () => {
9
14
  console.time("ssg");
10
15
  const args = {
@@ -92,7 +97,7 @@ describe("Build", () => {
92
97
  const sourceRegistryFileName = testFilePath("source/index.json");
93
98
  const siteBaseUrl = "https://rr0.org/";
94
99
  const mail = "rr0@rr0.org";
95
- const timeOptions = { root: testFilePath("time"), files: timeFiles };
100
+ const timeOptions = { rootDir: testFilePath("time"), files: timeFiles };
96
101
  // const actions: ChronologyReplacerActions = {read: ["backup", "fetch"], write: ["backup", "pages"]}
97
102
  // const actions: ChronologyReplacerActions = {read: [], write: ["backup"]}
98
103
  const actions = { read: ["fetch"], write: ["backup"] };
@@ -116,7 +121,19 @@ describe("Build", () => {
116
121
  sourceRegistryFileName,
117
122
  directoryExcluded: ["people/Astronomers_fichiers", "people/witness", "people/author"].map(testFilePath),
118
123
  directoryOptions,
119
- mappings
124
+ mappings,
125
+ contentReplacers: [
126
+ new BaseReplaceCommand("/"),
127
+ new LanguageReplaceCommand(),
128
+ new SsiEchoVarReplaceCommand("copyright", [rr0DefaultCopyright]),
129
+ new StringEchoVarReplaceCommand(),
130
+ new AngularExpressionReplaceCommand(),
131
+ new SsiIfReplaceCommand(),
132
+ new SsiSetVarReplaceCommand("title", (_match, ...args) => `<title>${args[0]}</title>`),
133
+ new SsiSetVarReplaceCommand("url", (_match, ...args) => `<meta name="url" content="${args[0]}"/>`),
134
+ new SsiLastModifiedReplaceCommand(timeFormat),
135
+ new DescriptionReplaceCommand("UFO data for french-reading people", "abstract")
136
+ ]
120
137
  });
121
138
  await build.run(args);
122
139
  });
@@ -8,9 +8,8 @@ import path from "path";
8
8
  describe("AnchorReplaceCommand", () => {
9
9
  test("replace anchor tag", async () => {
10
10
  const dataService = rr0TestUtil.dataService;
11
- const timeService = rr0TestUtil.time.getService();
12
11
  const timeTextBuilder = new TimeTextBuilder(rr0TestUtil.intlOptions);
13
- const timeRenderer = new TimeRenderer(timeService, timeTextBuilder);
12
+ const timeRenderer = new TimeRenderer(rr0TestUtil.time.urlBuilder, timeTextBuilder);
14
13
  const timeElementFactory = new TimeElementFactory(timeRenderer);
15
14
  const roswellUrl = "/src/science/crypto/ufo/enquete/dossier/Roswell";
16
15
  const caseFiles = [path.join(roswellUrl, "index.html")];
@@ -20,10 +20,10 @@ const config = {
20
20
  }
21
21
  };
22
22
  const timeOptions = {
23
- root: testFilePath("time"),
23
+ rootDir: testFilePath("time"),
24
24
  files: []
25
25
  };
26
- const timeUrlBuilder = new TimeUrlBuilder({ rootDir: timeOptions.root });
26
+ const timeUrlBuilder = new TimeUrlBuilder(timeOptions);
27
27
  let files = [];
28
28
  const peopleService = new PeopleService(dataService, peopleFactory, { files, rootDir: testFilePath("people") });
29
29
  const books = new BookService(logger, dry, peopleService, timeUrlBuilder, config);
@@ -1,12 +1,12 @@
1
1
  import { RegexReplacer, SsiEchoVarReplaceCommand } from "ssg-api";
2
2
  import { HtmlRR0Context } from "../../RR0Context.js";
3
- import { TimeService } from "../../time/TimeService.js";
3
+ import { TimeRenderer } from "../../time";
4
4
  /**
5
5
  * Replaces "<!--#echo var="author" -->" and "<!--#echo var="copyright" -->"
6
6
  * by the page's <meta name="author">s' content.
7
7
  */
8
8
  export declare class AuthorReplaceCommand extends SsiEchoVarReplaceCommand {
9
- protected timeService: TimeService;
10
- constructor(timeService: TimeService);
9
+ protected timeRenderer: TimeRenderer;
10
+ constructor(timeRenderer: TimeRenderer);
11
11
  protected createReplacer(context: HtmlRR0Context): Promise<RegexReplacer>;
12
12
  }
@@ -4,9 +4,9 @@ import { SsiEchoVarReplaceCommand } from "ssg-api";
4
4
  * by the page's <meta name="author">s' content.
5
5
  */
6
6
  export class AuthorReplaceCommand extends SsiEchoVarReplaceCommand {
7
- constructor(timeService) {
7
+ constructor(timeRenderer) {
8
8
  super("author");
9
- this.timeService = timeService;
9
+ this.timeRenderer = timeRenderer;
10
10
  }
11
11
  async createReplacer(context) {
12
12
  return {
@@ -19,7 +19,7 @@ export class AuthorReplaceCommand extends SsiEchoVarReplaceCommand {
19
19
  authorsHtml += authorsHtml ? ": " + copyright : copyright;
20
20
  }
21
21
  if (authorsHtml && context.time.getYear()) {
22
- const { result, replacement } = this.timeService.renderer.renderContent(context, undefined, { url: true });
22
+ const { result, replacement } = this.timeRenderer.renderContent(context, undefined, { url: true, contentOnly: false });
23
23
  result.append(replacement);
24
24
  authorsHtml += ", " + result.outerHTML;
25
25
  }
@@ -8,7 +8,7 @@ describe("AuthorReplaceCommand", async () => {
8
8
  const timeService = await rr0TestUtil.time.getService();
9
9
  test("no author", async () => {
10
10
  const timeFile = rr0TestUtil.time.filePath("1/9/5/4/index.html");
11
- const command = new AuthorReplaceCommand(timeService);
11
+ const command = new AuthorReplaceCommand(rr0TestUtil.time.timeRenderer);
12
12
  const context = rr0TestUtil.newHtmlContext(timeFile, `This is published by <!--#echo var="author" -->!`);
13
13
  await command.execute(context);
14
14
  expect(context.file.meta.author).toEqual([]);
@@ -16,7 +16,7 @@ describe("AuthorReplaceCommand", async () => {
16
16
  });
17
17
  test("author only", async () => {
18
18
  const timeFile = rr0TestUtil.time.filePath("1/9/5/4/10/index.html");
19
- const command = new AuthorReplaceCommand(timeService);
19
+ const command = new AuthorReplaceCommand(rr0TestUtil.time.timeRenderer);
20
20
  const context = rr0TestUtil.newHtmlContext(timeFile, `This is published by <!--#echo var="author" -->!`);
21
21
  context.file.meta.author.push("Beau, Jérôme");
22
22
  const time = relativeTimeTextBuilder.build(undefined, context);
@@ -26,7 +26,7 @@ describe("AuthorReplaceCommand", async () => {
26
26
  });
27
27
  test("copyright only", async () => {
28
28
  const timeFile = rr0TestUtil.time.filePath("1/9/5/4/10/index.html");
29
- const command = new AuthorReplaceCommand(timeService);
29
+ const command = new AuthorReplaceCommand(rr0TestUtil.time.timeRenderer);
30
30
  const context = rr0TestUtil.newHtmlContext(timeFile, `This is published by <!--#echo var="author" -->!`);
31
31
  context.file.meta.copyright = "Some publication";
32
32
  const time = relativeTimeTextBuilder.build(undefined, context);
@@ -37,7 +37,7 @@ describe("AuthorReplaceCommand", async () => {
37
37
  });
38
38
  test("author with copyright", async () => {
39
39
  const timeFile = rr0TestUtil.time.filePath("1/9/5/4/10/index.html");
40
- const command = new AuthorReplaceCommand(timeService);
40
+ const command = new AuthorReplaceCommand(rr0TestUtil.time.timeRenderer);
41
41
  const context = rr0TestUtil.newHtmlContext(timeFile, `This is published by <!--#echo var="author" -->!`);
42
42
  context.file.meta.author.push("Beau, Jérôme");
43
43
  context.file.meta.copyright = "Some publication";
@@ -22,8 +22,8 @@ describe("DirectoryStep", () => {
22
22
  const eventFactory = new RR0EventFactory();
23
23
  const dataService = new AllDataService([new TypedDataFactory(eventFactory, "case")]);
24
24
  const caseFiles = await rr0TestUtil.caseFactory.getFiles();
25
- const timeService = rr0TestUtil.time.getService();
26
- const timeElementFactory = new TimeElementFactory(timeService.renderer);
25
+ const timeRenderer = rr0TestUtil.time.timeRenderer;
26
+ const timeElementFactory = new TimeElementFactory(timeRenderer);
27
27
  const caseService = new CaseService(dataService, rr0TestUtil.caseFactory, timeElementFactory, caseFiles);
28
28
  const ufoCasesExclusions = [];
29
29
  const step = new CaseDirectoryStep(caseService, caseService.files, ufoCasesExclusions, casesDirectoryPath, outputFunc, rr0TestUtil.config);
@@ -21,7 +21,7 @@ export class CaseService extends AbstractDataService {
21
21
  if (time) {
22
22
  caseContext.time = new TimeContext((_a = time.year) === null || _a === void 0 ? void 0 : _a.value, (_b = time.month) === null || _b === void 0 ? void 0 : _b.value, (_c = time.day) === null || _c === void 0 ? void 0 : _c.value, (_d = time.hour) === null || _d === void 0 ? void 0 : _d.value, (_e = time.minute) === null || _e === void 0 ? void 0 : _e.value, (_f = time.timeshift) === null || _f === void 0 ? void 0 : _f.toString());
23
23
  const options = { year: "numeric" };
24
- const { result, replacement } = this.timeElementFactory.renderer.renderContent(caseContext, undefined, { url: true }, options);
24
+ const { result, replacement } = this.timeElementFactory.renderer.renderContent(caseContext, undefined, { url: true, contentOnly: false }, options);
25
25
  result.append(replacement);
26
26
  details.push(result.outerHTML);
27
27
  }
@@ -6,6 +6,7 @@ import { RR0Data, RR0Event } from "@rr0/data";
6
6
  import { Source } from "@rr0/data/dist/source";
7
7
  import { PlaceRenderer } from "../place/PlaceRenderer";
8
8
  import { Place } from "@rr0/place";
9
+ import { TimeRenderOptions } from "./html";
9
10
  /**
10
11
  * Render a case summary as HTML.
11
12
  */
@@ -18,7 +19,7 @@ export declare class EventRenderer<E extends RR0Event> {
18
19
  constructor(noteRenderer: NoteRenderer, sourceFactory: SourceFactory, sourceRenderer: SourceRenderer, timeElementFactory: TimeElementFactory);
19
20
  placeElement(context: HtmlRR0Context, place: Place): HTMLSpanElement;
20
21
  renderEnd(context: HtmlRR0Context, event: RR0Data, container: HTMLElement): Promise<void>;
21
- render(context: HtmlRR0Context, event: E, container: HTMLElement): Promise<void>;
22
+ render(context: HtmlRR0Context, event: E, container: HTMLElement, options?: TimeRenderOptions): Promise<void>;
22
23
  renderNotes(context: HtmlRR0Context, notes: string[], container: HTMLElement): Promise<void>;
23
24
  renderSources(context: HtmlRR0Context, sources: Source[], container: HTMLElement): Promise<void>;
24
25
  }
@@ -29,12 +29,12 @@ export class EventRenderer {
29
29
  }
30
30
  container.append(".");
31
31
  }
32
- async render(context, event, container) {
32
+ async render(context, event, container, options = { url: true, contentOnly: false }) {
33
33
  const eventContext = context.clone();
34
34
  const eventTime = eventContext.time.date = event.time;
35
35
  assert.ok(eventTime, `Event of type "${event.type}" has no time`);
36
36
  container.dataset.time = eventTime.toString();
37
- const timeEl = this.timeElementFactory.create(eventContext, context);
37
+ const timeEl = this.timeElementFactory.create(eventContext, context, options);
38
38
  container.append(timeEl);
39
39
  const place = event.place;
40
40
  if (place) {
@@ -3,10 +3,12 @@ import { HtmlRR0Context } from "../RR0Context.js";
3
3
  import { Link } from "ssg-api";
4
4
  import { TimeTextBuilder } from "./text/TimeTextBuilder.js";
5
5
  import { TimeService } from "./TimeService";
6
+ import { TimeUrlBuilder } from "./TimeUrlBuilder";
6
7
  export declare class TimeLinkDefaultHandler implements LinkHandler<HtmlRR0Context> {
7
8
  protected service: TimeService;
9
+ protected urlBuilder: TimeUrlBuilder;
8
10
  protected timeTextBuilder: TimeTextBuilder;
9
- constructor(service: TimeService, timeTextBuilder: TimeTextBuilder);
11
+ constructor(service: TimeService, urlBuilder: TimeUrlBuilder, timeTextBuilder: TimeTextBuilder);
10
12
  contents(context: HtmlRR0Context): Link | undefined;
11
13
  next(context: HtmlRR0Context): Link | undefined;
12
14
  prev(context: HtmlRR0Context): Link | undefined;
@@ -1,16 +1,17 @@
1
1
  import { LinkType } from "ssg-api";
2
2
  export class TimeLinkDefaultHandler {
3
- constructor(service, timeTextBuilder) {
3
+ constructor(service, urlBuilder, timeTextBuilder) {
4
4
  this.service = service;
5
+ this.urlBuilder = urlBuilder;
5
6
  this.timeTextBuilder = timeTextBuilder;
6
7
  }
7
8
  contents(context) {
8
9
  const prevLink = this.prev(context);
9
10
  if (prevLink) {
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);
11
+ const urlBuilder = this.urlBuilder;
12
+ let contentUrl = urlBuilder.matchExistingTimeFile(prevLink.url.substring(1));
13
+ if (contentUrl != urlBuilder.options.rootDir) {
14
+ const text = this.service.titleFromFile(context, contentUrl, this.timeTextBuilder);
14
15
  if (text) {
15
16
  return { type: LinkType.prev, text, url: "/" + contentUrl };
16
17
  }
@@ -0,0 +1,4 @@
1
+ export type TimeOptions = {
2
+ readonly rootDir: string;
3
+ readonly files: string[];
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,34 +1,13 @@
1
- import { TimeRenderer } from "./html/TimeRenderer.js";
2
1
  import { TimeTextBuilder } from "./text/TimeTextBuilder.js";
3
- import { TimeUrlBuilder } from "./TimeUrlBuilder";
4
2
  import { AbstractDataService, AllDataService, RR0Event, RR0EventJson } from "@rr0/data";
5
3
  import { HtmlRR0Context } from "../RR0Context";
6
4
  import { TimeContext } from "@rr0/time";
7
- export type TimeServiceOptions = {
8
- readonly root: string;
9
- readonly files: string[];
10
- };
5
+ import { TimeOptions } from "./TimeOptions";
11
6
  export declare class TimeService extends AbstractDataService<RR0Event, RR0EventJson> {
12
- readonly textBuilder: TimeTextBuilder;
13
- readonly urlBuilder: TimeUrlBuilder;
7
+ protected options: TimeOptions;
14
8
  readonly timePathRegex: RegExp;
15
9
  static readonly defaultRegex: RegExp;
16
- readonly renderer: TimeRenderer;
17
- readonly root: string;
18
- constructor(dataService: AllDataService, textBuilder: TimeTextBuilder, urlBuilder: TimeUrlBuilder, options: TimeServiceOptions, timePathRegex?: RegExp);
19
- isTimeFile(filePath: string): boolean;
20
- /**
21
- * @return the found time URL or undefined if not found.
22
- */
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;
10
+ constructor(dataService: AllDataService, options: TimeOptions, timePathRegex?: RegExp);
32
11
  parseFileName(fileName: string): RegExpExecArray | null;
33
12
  titleFromFile(context: HtmlRR0Context, fileName: string, timeTextBuilder: TimeTextBuilder): string | undefined;
34
13
  contextFromFileName(context: HtmlRR0Context, fileName?: string): TimeContext | undefined;
@@ -1,42 +1,11 @@
1
- import { TimeRenderer } from "./html/TimeRenderer.js";
2
1
  import { AbstractDataService } from "@rr0/data";
3
2
  import { RR0ContextImpl } from "../RR0Context";
4
3
  import { StringUtil } from "../util";
5
4
  export class TimeService extends AbstractDataService {
6
- constructor(dataService, textBuilder, urlBuilder, options, timePathRegex = TimeService.defaultRegex) {
5
+ constructor(dataService, options, timePathRegex = TimeService.defaultRegex) {
7
6
  super(dataService, null, options.files);
8
- this.textBuilder = textBuilder;
9
- this.urlBuilder = urlBuilder;
7
+ this.options = options;
10
8
  this.timePathRegex = timePathRegex;
11
- this.root = options.root;
12
- this.renderer = new TimeRenderer(this, this.textBuilder);
13
- }
14
- isTimeFile(filePath) {
15
- return this.files.includes(filePath);
16
- }
17
- /**
18
- * @return the found time URL or undefined if not found.
19
- */
20
- matchExistingTimeFile(url) {
21
- while (url && url !== this.root && this.files.indexOf(`${url}/index.html`) < 0) {
22
- const slash = url.lastIndexOf("/");
23
- url = url.substring(0, slash);
24
- }
25
- return url === this.root ? undefined : url;
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
9
  }
41
10
  parseFileName(fileName) {
42
11
  return this.timePathRegex.exec(fileName);
@@ -5,11 +5,11 @@ import { DomReplaceCommand } from "ssg-api";
5
5
  import { TimeReplacer } from "./html/TimeReplacer.js";
6
6
  import path from "path";
7
7
  describe("HtmlTagReplaceCommand", async () => {
8
- const timeRoot = rr0TestUtil.time.timeOptions.root;
9
- await rr0TestUtil.time.getService({ root: path.join(rr0TestUtil.rootDir, timeRoot), files: ["time/2/0/0/4/index.html"] });
8
+ const timeRoot = rr0TestUtil.time.timeOptions.rootDir;
9
+ const timeService = await rr0TestUtil.time.getService({ rootDir: path.join(rr0TestUtil.rootDir, timeRoot), files: ["time/2/0/0/4/index.html"] });
10
10
  test("replace time tag", async () => {
11
11
  const replacer = new TimeReplacer(rr0TestUtil.time.timeElementFactory);
12
- const command = new DomReplaceCommand("time", new TimeReplacerFactory(replacer));
12
+ const command = new DomReplaceCommand("time", new TimeReplacerFactory(replacer, rr0TestUtil.time.urlBuilder));
13
13
  const context = rr0TestUtil.time.newHtmlContext("1/9/9/0/08/index.html", `<time>2004</time> <a href="/science/crypto/ufo/enquete/dossier/Roswell">Roswell</a>`);
14
14
  await command.execute(context);
15
15
  expect(context.file.contents).toBe(`<html><head><meta name="generator" content="ssg-api"></head><body><span class="time-resolved">en <a href="${path.join("/", timeRoot, "2/0/0/4/")}"><time datetime="2004">2004</time></a></span> <a href="/science/crypto/ufo/enquete/dossier/Roswell">Roswell</a></body></html>`);
@@ -1,16 +1,18 @@
1
- import { TimeElementFactory, TimeService, TimeServiceOptions, TimeTextBuilder, TimeUrlBuilder } from "../time/index.js";
1
+ import { TimeElementFactory, TimeRenderer, TimeService, TimeTextBuilder, TimeUrlBuilder } from "../time/index.js";
2
2
  import { RR0TestUtil } from "../test";
3
3
  import { HtmlRR0Context } from "../RR0Context";
4
+ import { TimeOptions } from "./TimeOptions";
4
5
  export declare class TimeTestUtil {
5
6
  readonly timeTextBuilder: TimeTextBuilder;
6
7
  timeElementFactory: TimeElementFactory;
7
- timeOptions: TimeServiceOptions;
8
+ timeOptions: TimeOptions;
8
9
  urlBuilder: TimeUrlBuilder;
9
10
  protected timeService: TimeService;
10
- fullRoot: string;
11
+ readonly fullRoot: string;
12
+ readonly timeRenderer: TimeRenderer;
11
13
  constructor(rr0TestUtil: RR0TestUtil);
12
14
  newHtmlContext(inputFileName: string, contents?: string, locale?: string): HtmlRR0Context;
13
15
  filePath(inputFileName: string): string;
14
16
  url(inputFileName: string): string;
15
- getService(options?: TimeServiceOptions): TimeService;
17
+ getService(options?: TimeOptions): TimeService;
16
18
  }
@@ -1,28 +1,30 @@
1
1
  import path from "path";
2
- import { TimeElementFactory, TimeService, TimeTextBuilder, TimeUrlBuilder } from "../time/index.js";
2
+ import { TimeElementFactory, TimeRenderer, TimeService, TimeTextBuilder, TimeUrlBuilder } from "../time/index.js";
3
3
  import { rr0TestUtil } from "../test";
4
4
  export class TimeTestUtil {
5
5
  constructor(rr0TestUtil) {
6
- this.timeOptions = { root: "time", files: [] };
7
- this.urlBuilder = new TimeUrlBuilder({ rootDir: this.timeOptions.root });
6
+ this.timeOptions = { rootDir: "time", files: [] };
7
+ this.urlBuilder = new TimeUrlBuilder(this.timeOptions);
8
8
  this.timeTextBuilder = new TimeTextBuilder(rr0TestUtil.intlOptions);
9
- this.fullRoot = path.join(rr0TestUtil.rootDir, this.timeOptions.root);
9
+ this.fullRoot = path.join(rr0TestUtil.rootDir, this.timeOptions.rootDir);
10
+ this.timeRenderer = new TimeRenderer(this.urlBuilder, this.timeTextBuilder);
10
11
  }
11
12
  newHtmlContext(inputFileName, contents, locale = "fr") {
12
13
  return rr0TestUtil.newHtmlContext(this.filePath(inputFileName), contents, locale);
13
14
  }
14
15
  filePath(inputFileName) {
15
- return path.join(this.timeOptions.root, inputFileName);
16
+ return path.join(this.timeOptions.rootDir, inputFileName);
16
17
  }
17
18
  url(inputFileName) {
18
19
  return path.join("/", this.filePath(inputFileName));
19
20
  }
20
21
  getService(options = this.timeOptions) {
21
22
  if (!this.timeService
22
- || this.timeService.root !== options.root
23
+ || this.timeOptions.rootDir !== options.rootDir
23
24
  || JSON.stringify(this.timeService.files) !== JSON.stringify(options.files)) {
24
- this.timeService = new TimeService(rr0TestUtil.dataService, this.timeTextBuilder, this.urlBuilder, options);
25
- this.timeElementFactory = new TimeElementFactory(this.timeService.renderer);
25
+ this.timeOptions = options;
26
+ this.timeService = new TimeService(rr0TestUtil.dataService, options);
27
+ this.timeElementFactory = new TimeElementFactory(this.timeRenderer);
26
28
  }
27
29
  return this.timeService;
28
30
  }
@@ -1,11 +1,15 @@
1
1
  import { Level2Date as EdtfDate, TimeContext } from "@rr0/time";
2
- export type TimeUrlBuilderOptions = {
3
- rootDir: string;
4
- };
2
+ import { TimeOptions } from "./TimeOptions";
3
+ export type TimeUrlBuilderOptions = TimeOptions;
5
4
  export declare class TimeUrlBuilder {
6
5
  readonly options: TimeUrlBuilderOptions;
7
6
  constructor(options: TimeUrlBuilderOptions);
8
7
  fromContext(time: TimeContext): string;
9
8
  fromEdtf(time: EdtfDate): string;
10
9
  fromYYMMDD(year: number, month: number, day: number): string;
10
+ isTimeFile(filePath: string): boolean;
11
+ /**
12
+ * @return the found time URL or undefined if not found.
13
+ */
14
+ matchExistingTimeFile(url: string): string | undefined;
11
15
  }
@@ -23,4 +23,18 @@ export class TimeUrlBuilder {
23
23
  }
24
24
  return url;
25
25
  }
26
+ isTimeFile(filePath) {
27
+ return this.options.files.includes(filePath);
28
+ }
29
+ /**
30
+ * @return the found time URL or undefined if not found.
31
+ */
32
+ matchExistingTimeFile(url) {
33
+ const rootDir = this.options.rootDir;
34
+ while (url && url !== rootDir && !this.isTimeFile(`${url}/index.html`)) {
35
+ const slash = url.lastIndexOf("/");
36
+ url = url.substring(0, slash);
37
+ }
38
+ return url === rootDir ? undefined : url;
39
+ }
26
40
  }
@@ -7,7 +7,8 @@ import path from "path";
7
7
  describe("TimeUrlBuilder", () => {
8
8
  const config = rr0TestUtil.config;
9
9
  const rootDir = rr0TestUtil.time.fullRoot;
10
- const timeUrlBuilder = new TimeUrlBuilder({ rootDir });
10
+ const timeOptions = { rootDir, files: [] };
11
+ const timeUrlBuilder = new TimeUrlBuilder(timeOptions);
11
12
  test("builds year", () => {
12
13
  {
13
14
  const context = new RR0ContextImpl("fr", new TimeContext(), config);
@@ -44,7 +44,7 @@ export class ChronologyReplacer {
44
44
  for (const c of allCases) {
45
45
  const outDoc = context.file.document;
46
46
  const eventEl = outDoc.createElement("li");
47
- await this.renderer.render(context, c, eventEl);
47
+ await this.renderer.render(context, c, eventEl, { url: true, contentOnly: true });
48
48
  element.append(eventEl);
49
49
  }
50
50
  }
@@ -2,12 +2,12 @@ import { DomReplacer, ReplacerFactory } from "ssg-api";
2
2
  import { ChronologyReplacer } from "./ChronologyReplacer.js";
3
3
  import { HtmlRR0Context } from "../../RR0Context.js";
4
4
  import { CaseSummaryRenderer } from "../CaseSummaryRenderer.js";
5
- import { TimeService } from "../TimeService.js";
6
5
  import { RR0CaseMapping } from "./rr0/RR0CaseMapping.js";
6
+ import { TimeUrlBuilder } from "../TimeUrlBuilder";
7
7
  export declare class ChronologyReplacerFactory implements ReplacerFactory<DomReplacer> {
8
- protected timeService: TimeService;
8
+ protected timeUrlBuilder: TimeUrlBuilder;
9
9
  protected readonly replacer: ChronologyReplacer;
10
- constructor(timeService: TimeService, datasources: RR0CaseMapping<any>[], caseRenderer: CaseSummaryRenderer);
10
+ constructor(timeUrlBuilder: TimeUrlBuilder, datasources: RR0CaseMapping<any>[], caseRenderer: CaseSummaryRenderer);
11
11
  /**
12
12
  * Creates a contextual replacer for time tags.
13
13
  *
@@ -1,7 +1,7 @@
1
1
  import { ChronologyReplacer } from "./ChronologyReplacer.js";
2
2
  export class ChronologyReplacerFactory {
3
- constructor(timeService, datasources, caseRenderer) {
4
- this.timeService = timeService;
3
+ constructor(timeUrlBuilder, datasources, caseRenderer) {
4
+ this.timeUrlBuilder = timeUrlBuilder;
5
5
  this.replacer = new ChronologyReplacer(datasources, caseRenderer);
6
6
  }
7
7
  /**
@@ -12,7 +12,7 @@ export class ChronologyReplacerFactory {
12
12
  async create(context) {
13
13
  return {
14
14
  replace: async (ul) => {
15
- const isTimeFile = this.timeService.isTimeFile(context.file.name);
15
+ const isTimeFile = this.timeUrlBuilder.isTimeFile(context.file.name);
16
16
  if (isTimeFile) {
17
17
  ul = await this.replacer.replacement(context, ul);
18
18
  }
@@ -45,7 +45,7 @@ export class DatasourceTestCase {
45
45
  const http = new HttpSource();
46
46
  const timeService = await rr0TestUtil.time.getService();
47
47
  const sourceFactory = new SourceFactory(dataService, http, baseUrl, this.intlOptions, timeService);
48
- const timeElementFactory = new TimeElementFactory(new TimeRenderer(timeService, this.timeTextBuilder));
48
+ const timeElementFactory = new TimeElementFactory(new TimeRenderer(rr0TestUtil.time.urlBuilder, this.timeTextBuilder));
49
49
  const eventRenderer = new CaseSummaryRenderer(new NoteRenderer(new NoteFileCounter()), sourceFactory, new SourceRenderer(this.timeTextBuilder), timeElementFactory);
50
50
  const items = [];
51
51
  for (const c of cases) {
@@ -88,7 +88,7 @@ export class RR0HttpDatasource extends RR0Datasource {
88
88
  time = EdtfDate.fromString(timeStr);
89
89
  }
90
90
  catch (e) {
91
- console.warn("Could not parse source time", e);
91
+ // console.warn("Could not parse source time", e)
92
92
  }
93
93
  if (time) {
94
94
  pubItems.pop();
@@ -6,7 +6,7 @@ export class TimeElementFactory {
6
6
  constructor(renderer) {
7
7
  this.renderer = renderer;
8
8
  }
9
- create(context, previousContext, options = { url: true }) {
9
+ create(context, previousContext, options = { url: true, contentOnly: false }) {
10
10
  let replacement;
11
11
  const time = context.time;
12
12
  const interval = time.interval;
@@ -52,7 +52,7 @@ export class TimeElementFactory {
52
52
  result.append(startingReplacement, replacement);
53
53
  return result;
54
54
  }
55
- valueReplacement(context, previousContext, options = { url: true }) {
55
+ valueReplacement(context, previousContext, options = { url: true, contentOnly: false }) {
56
56
  let replacement;
57
57
  if (context.time.duration) {
58
58
  replacement = this.durationReplacement(context);
@@ -100,7 +100,7 @@ export class TimeElementFactory {
100
100
  }
101
101
  return replacement;
102
102
  }
103
- dateTimeReplacement(context, previousContext, options = { url: true }) {
103
+ dateTimeReplacement(context, previousContext, options = { url: true, contentOnly: false }) {
104
104
  let replacement = undefined;
105
105
  if (context.time.isDefined()) {
106
106
  replacement = this.renderer.render(context, previousContext, options);
@@ -1,15 +1,16 @@
1
1
  import { HtmlRR0Context, RR0Context } from "../../RR0Context.js";
2
2
  import { TimeTextBuilder } from "../text/TimeTextBuilder.js";
3
3
  import { RelativeTimeTextBuilder } from "../text/RelativeTimeTextBuilder.js";
4
- import { TimeService } from "../TimeService";
4
+ import { TimeUrlBuilder } from "../TimeUrlBuilder";
5
5
  export interface TimeRenderOptions {
6
6
  url: boolean;
7
+ contentOnly: boolean;
7
8
  }
8
9
  export declare class TimeRenderer {
9
- readonly service: TimeService;
10
+ readonly urlBuilder: TimeUrlBuilder;
10
11
  protected textBuilder: TimeTextBuilder;
11
12
  protected readonly relativeTextBuilder: RelativeTimeTextBuilder;
12
- constructor(service: TimeService, textBuilder: TimeTextBuilder);
13
+ constructor(urlBuilder: TimeUrlBuilder, textBuilder: TimeTextBuilder);
13
14
  render(context: HtmlRR0Context, previousContext?: RR0Context, options?: TimeRenderOptions): HTMLElement;
14
15
  renderContent(context: HtmlRR0Context, previousContext: RR0Context, options: TimeRenderOptions, renderOptions?: Intl.DateTimeFormatOptions): {
15
16
  result: HTMLElement;
@@ -2,22 +2,25 @@ import { RelativeTimeTextBuilder } from "../text/RelativeTimeTextBuilder.js";
2
2
  import { UrlUtil } from "../../util/url/UrlUtil.js";
3
3
  import { TimeReplacer } from "./TimeReplacer.js";
4
4
  export class TimeRenderer {
5
- constructor(service, textBuilder) {
6
- this.service = service;
5
+ constructor(urlBuilder, textBuilder) {
6
+ this.urlBuilder = urlBuilder;
7
7
  this.textBuilder = textBuilder;
8
8
  this.relativeTextBuilder = new RelativeTimeTextBuilder(textBuilder);
9
9
  }
10
- render(context, previousContext, options = { url: true }) {
10
+ render(context, previousContext, options = { url: true, contentOnly: false }) {
11
11
  const { result, replacement } = this.renderContent(context, previousContext, options);
12
- const timeMessages = context.messages.context.time;
13
- const time = context.time;
14
- const message = time.getDayOfMonth() ? timeMessages.on : timeMessages.in;
15
- result.append(message(time.approximate), replacement);
12
+ if (!options.contentOnly) {
13
+ const timeMessages = context.messages.context.time;
14
+ const time = context.time;
15
+ const message = time.getDayOfMonth() ? timeMessages.on : timeMessages.in;
16
+ result.append(message(time.approximate));
17
+ }
18
+ result.append(replacement);
16
19
  return result;
17
20
  }
18
21
  renderContent(context, previousContext, options, renderOptions = this.textBuilder.options) {
19
22
  const time = context.time;
20
- const absoluteTimeUrl = this.service.urlBuilder.fromContext(time);
23
+ const absoluteTimeUrl = this.urlBuilder.fromContext(time);
21
24
  const title = this.textBuilder.build(context, renderOptions);
22
25
  const text = (previousContext ? this.relativeTextBuilder.build(previousContext, context) : undefined) || title;
23
26
  const file = context.file;
@@ -30,7 +33,7 @@ export class TimeRenderer {
30
33
  }
31
34
  timeEl.textContent = text;
32
35
  const dirName = currentFileName.substring(0, currentFileName.indexOf("/index"));
33
- const existingUrl = options.url && this.service.matchExistingTimeFile(absoluteTimeUrl);
36
+ const existingUrl = options.url && this.urlBuilder.matchExistingTimeFile(absoluteTimeUrl);
34
37
  if (existingUrl && existingUrl !== dirName) {
35
38
  const a = replacement = doc.createElement("a");
36
39
  a.href = UrlUtil.absolute(existingUrl);
@@ -6,9 +6,9 @@ import { TimeElementFactory } from "./TimeElementFactory.js";
6
6
  import { TimeTextBuilder } from "../text/TimeTextBuilder.js";
7
7
  import path from "path";
8
8
  describe("TimeReplacer", async () => {
9
- const timeRoot = rr0TestUtil.time.timeOptions.root;
10
- const timeService = await rr0TestUtil.time.getService({
11
- root: timeRoot,
9
+ const timeRoot = rr0TestUtil.time.timeOptions.rootDir;
10
+ const timeOptions = {
11
+ rootDir: timeRoot,
12
12
  files: [
13
13
  path.join(timeRoot, "1/9/4/7/07/02/index.html"),
14
14
  path.join(timeRoot, "2/0/0/3/index.html"),
@@ -21,13 +21,14 @@ describe("TimeReplacer", async () => {
21
21
  path.join(timeRoot, "2/0/0/6/07/14/index.html"),
22
22
  path.join(timeRoot, "2/0/0/7/06/15/index.html")
23
23
  ]
24
- });
24
+ };
25
+ const timeService = await rr0TestUtil.time.getService(timeOptions);
25
26
  const textBuilder = new TimeTextBuilder(rr0TestUtil.intlOptions);
26
- const timeRenderer = new TimeRenderer(timeService, textBuilder);
27
+ const timeRenderer = new TimeRenderer(rr0TestUtil.time.urlBuilder, textBuilder);
27
28
  const timeElementFactory = new TimeElementFactory(timeRenderer);
28
29
  const replacer = new TimeReplacer(timeElementFactory);
29
30
  function timeUrl(pathStr) {
30
- return path.join("/", timeService.root, pathStr, "index.html");
31
+ return path.join("/", timeOptions.rootDir, pathStr, "index.html");
31
32
  }
32
33
  test("parses year", async () => {
33
34
  {
@@ -35,7 +36,7 @@ describe("TimeReplacer", async () => {
35
36
  const timeEl = context.file.document.createElement("time");
36
37
  timeEl.textContent = "2003";
37
38
  const replacement = await replacer.replacement(context, timeEl);
38
- expect(replacement.outerHTML).toBe(`<span class="time-resolved">en <a href="${path.join("/", timeService.root, "2/0/0/3/")}"><time datetime="2003">2003</time></a></span>`);
39
+ expect(replacement.outerHTML).toBe(`<span class="time-resolved">en <a href="${path.join("/", timeOptions.rootDir, "2/0/0/3/")}"><time datetime="2003">2003</time></a></span>`);
39
40
  expect(context.time.getYear()).toBe(2003);
40
41
  expect(context.time.getMonth()).toBe(undefined);
41
42
  expect(context.time.getDayOfMonth()).toBe(undefined);
@@ -48,7 +49,7 @@ describe("TimeReplacer", async () => {
48
49
  const timeEl = context.file.document.createElement("time");
49
50
  timeEl.innerHTML = "2003\n ";
50
51
  const replacement = await replacer.replacement(context, timeEl);
51
- expect(replacement.outerHTML).toBe(`<span class="time-resolved">en <a href="${path.join("/", timeService.root, "2/0/0/3/")}"><time datetime="2003">2003</time></a></span>`);
52
+ expect(replacement.outerHTML).toBe(`<span class="time-resolved">en <a href="${path.join("/", timeOptions.rootDir, "2/0/0/3/")}"><time datetime="2003">2003</time></a></span>`);
52
53
  expect(context.time.getYear()).toBe(2003);
53
54
  expect(context.time.getMonth()).toBe(undefined);
54
55
  expect(context.time.getDayOfMonth()).toBe(undefined);
@@ -64,7 +65,7 @@ describe("TimeReplacer", async () => {
64
65
  original.textContent = interval;
65
66
  const replaced = await replacer.replacement(context, original);
66
67
  expect(replaced.outerHTML)
67
- .toBe(`<span class="time-interval"><span class="time-resolved">en <a href="${path.join("/", timeService.root, "2/0/0/3/")}"><time datetime="2003">2003</time></a></span> à <span class="time-resolved">en <a href="${path.join("/", timeService.root, "2/0/0/4/")}"><time datetime="2004">2004</time></a></span></span>`);
68
+ .toBe(`<span class="time-interval"><span class="time-resolved">en <a href="${path.join("/", timeOptions.rootDir, "2/0/0/3/")}"><time datetime="2003">2003</time></a></span> à <span class="time-resolved">en <a href="${path.join("/", timeOptions.rootDir, "2/0/0/4/")}"><time datetime="2004">2004</time></a></span></span>`);
68
69
  expect(context.time.getYear()).toBe(2004);
69
70
  expect(context.time.getMonth()).toBe(undefined);
70
71
  expect(context.time.getDayOfMonth()).toBe(undefined);
@@ -93,7 +94,7 @@ describe("TimeReplacer", async () => {
93
94
  original.textContent = "2003-12-24T10:22CDT";
94
95
  const replacement = await replacer.replacement(context, original);
95
96
  expect(replacement.outerHTML)
96
- .toBe(`<span class="time-resolved">le <a href="${path.join("/", timeService.root, "2/0/0/3/12/24/")}"><time datetime="2003-12-24T10:22-05">mercredi 24 décembre 2003 à 10:22</time></a></span>`); // TODO: Text should have timezone info
97
+ .toBe(`<span class="time-resolved">le <a href="${path.join("/", timeOptions.rootDir, "2/0/0/3/12/24/")}"><time datetime="2003-12-24T10:22-05">mercredi 24 décembre 2003 à 10:22</time></a></span>`); // TODO: Text should have timezone info
97
98
  expect(context.time.getYear()).toBe(2003);
98
99
  expect(context.time.getMonth()).toBe(12);
99
100
  expect(context.time.getDayOfMonth()).toBe(24);
@@ -120,7 +121,7 @@ describe("TimeReplacer", async () => {
120
121
  const original = context.file.document.createElement("time");
121
122
  original.textContent = "2004-09";
122
123
  const replacement = await replacer.replacement(context, original);
123
- expect(replacement.outerHTML).toBe(`<span class="time-resolved">en <a href="${path.join("/", timeService.root, "2/0/0/4/09/")}"><time datetime="2004-09">septembre 2004</time></a></span>`);
124
+ expect(replacement.outerHTML).toBe(`<span class="time-resolved">en <a href="${path.join("/", timeOptions.rootDir, "2/0/0/4/09/")}"><time datetime="2004-09">septembre 2004</time></a></span>`);
124
125
  expect(context.time.getYear()).toBe(2004);
125
126
  expect(context.time.getMonth()).toBe(9);
126
127
  expect(context.time.getDayOfMonth()).toBe(undefined);
@@ -133,7 +134,7 @@ describe("TimeReplacer", async () => {
133
134
  const timeEl = context.file.document.createElement("time");
134
135
  timeEl.textContent = "2005-08-23";
135
136
  const replacement = await replacer.replacement(context, timeEl);
136
- expect(replacement.outerHTML).toBe(`<span class="time-resolved">le <a href="${path.join("/", timeService.root, "2/0/0/5/08/23/")}"><time datetime="2005-08-23">mardi 23 août 2005</time></a></span>`);
137
+ expect(replacement.outerHTML).toBe(`<span class="time-resolved">le <a href="${path.join("/", timeOptions.rootDir, "2/0/0/5/08/23/")}"><time datetime="2005-08-23">mardi 23 août 2005</time></a></span>`);
137
138
  expect(context.time.getYear()).toBe(2005);
138
139
  expect(context.time.getMonth()).toBe(8);
139
140
  expect(context.time.getDayOfMonth()).toBe(23);
@@ -201,7 +202,7 @@ describe("TimeReplacer", async () => {
201
202
  const timeEl = context.file.document.createElement("time");
202
203
  timeEl.textContent = "2006-07-14 17:56";
203
204
  const replacement = await replacer.replacement(context, timeEl);
204
- expect(replacement.outerHTML).toBe(`<span class="time-resolved">le <a href="${path.join("/", timeService.root, "2/0/0/6/07/14/")}"><time datetime="2006-07-14T17:56">vendredi 14 juillet 2006 à 17:56</time></a></span>`);
205
+ expect(replacement.outerHTML).toBe(`<span class="time-resolved">le <a href="${path.join("/", timeOptions.rootDir, "2/0/0/6/07/14/")}"><time datetime="2006-07-14T17:56">vendredi 14 juillet 2006 à 17:56</time></a></span>`);
205
206
  expect(context.time.getYear()).toBe(2006);
206
207
  expect(context.time.getMonth()).toBe(7);
207
208
  expect(context.time.getDayOfMonth()).toBe(14);
@@ -211,7 +212,7 @@ describe("TimeReplacer", async () => {
211
212
  const timeEl1 = context.file.document.createElement("time");
212
213
  timeEl1.textContent = "2007-06-15 18:47";
213
214
  const replacement1 = await replacer.replacement(context, timeEl1);
214
- expect(replacement1.outerHTML).toBe(`<span class="time-resolved">le <a href="${path.join("/", timeService.root, "2/0/0/7/06/15/")}"><time datetime="2007-06-15T18:47">vendredi 15 juin 2007 à 18:47</time></a></span>`);
215
+ expect(replacement1.outerHTML).toBe(`<span class="time-resolved">le <a href="${path.join("/", timeOptions.rootDir, "2/0/0/7/06/15/")}"><time datetime="2007-06-15T18:47">vendredi 15 juin 2007 à 18:47</time></a></span>`);
215
216
  expect(context.time.getYear()).toBe(2007);
216
217
  expect(context.time.getMonth()).toBe(6);
217
218
  expect(context.time.getDayOfMonth()).toBe(15);
@@ -1,9 +1,11 @@
1
1
  import { TimeReplacer } from "./TimeReplacer.js";
2
2
  import { HtmlRR0Context } from "../../RR0Context.js";
3
3
  import { DomReplacer, ReplacerFactory } from "ssg-api";
4
+ import { TimeUrlBuilder } from "../TimeUrlBuilder";
4
5
  export declare class TimeReplacerFactory implements ReplacerFactory<DomReplacer> {
5
6
  protected readonly replacer: TimeReplacer;
6
- constructor(replacer: TimeReplacer);
7
+ protected readonly timeUrlBuilder: TimeUrlBuilder;
8
+ constructor(replacer: TimeReplacer, timeUrlBuilder: TimeUrlBuilder);
7
9
  /**
8
10
  * Creates a contextual replacer for time tags.
9
11
  *
@@ -1,6 +1,7 @@
1
1
  export class TimeReplacerFactory {
2
- constructor(replacer) {
2
+ constructor(replacer, timeUrlBuilder) {
3
3
  this.replacer = replacer;
4
+ this.timeUrlBuilder = timeUrlBuilder;
4
5
  }
5
6
  /**
6
7
  * Creates a contextual replacer for time tags.
@@ -10,7 +11,7 @@ export class TimeReplacerFactory {
10
11
  async create(context) {
11
12
  return {
12
13
  replace: (original) => {
13
- return this.replacer.replacement(this.replacer.factory.renderer.service.isTimeFile(context.file.name) ? context.clone() : context, original);
14
+ return this.replacer.replacement(this.timeUrlBuilder.isTimeFile(context.file.name) ? context.clone() : context, original);
14
15
  }
15
16
  };
16
17
  }
@@ -10,3 +10,4 @@ export * from "./TimeLinkDefaultHandler.js";
10
10
  export * from "./TimeService.js";
11
11
  export * from "./TimeUrlBuilder.js";
12
12
  export * from "./datasource/index.js";
13
+ export * from "./TimeOptions.js";
@@ -10,3 +10,4 @@ export * from "./TimeLinkDefaultHandler.js";
10
10
  export * from "./TimeService.js";
11
11
  export * from "./TimeUrlBuilder.js";
12
12
  export * from "./datasource/index.js";
13
+ export * from "./TimeOptions.js";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@rr0/cms",
3
3
  "type": "module",
4
4
  "author": "Jérôme Beau <rr0@rr0.org> (https://rr0.org)",
5
- "version": "0.3.14",
5
+ "version": "0.3.15",
6
6
  "description": "RR0 Content Management System (CMS)",
7
7
  "exports": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",