@rr0/cms 0.3.81 → 0.3.82

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.
@@ -1,5 +1,6 @@
1
1
  import path from "path";
2
2
  import assert from "assert";
3
+ import { EventTime } from "@rr0/data";
3
4
  /**
4
5
  * Insert content in context file, according to data found in .json files aside of it.
5
6
  */
@@ -26,7 +27,7 @@ export class DataContentVisitor {
26
27
  async process(context, data) {
27
28
  this.processTitle(context, data);
28
29
  this.processURL(context, data);
29
- const events = data.events.sort((event1, event2) => event1.time ? event2.time ? event1.time.isBefore(event2.time) ? -1 : 1 : -1 : 1);
30
+ const events = data.events.sort((event1, event2) => event1.time ? event2.time ? EventTime.start(event1.time).isBefore(EventTime.start(event2.time)) ? -1 : 1 : -1 : 1);
30
31
  for (const event of events) {
31
32
  await this.processEvent(context, event, data);
32
33
  }
@@ -85,8 +86,9 @@ export class DataContentVisitor {
85
86
  timeParagraph(context, event) {
86
87
  const container = context.file.document.createElement("p");
87
88
  const eventContext = context.clone();
88
- const eventTime = eventContext.time.date = event.time;
89
+ const eventTime = event.time;
89
90
  assert.ok(eventTime, `Event of type "${event.type}" has no time for paragraph`);
91
+ eventContext.time.set(eventTime);
90
92
  container.dataset.time = eventTime.toString();
91
93
  const timeEl = this.timeElementFactory.create(eventContext, context, { url: true, contentOnly: false });
92
94
  return { eventP: container, timeEl };
@@ -1,5 +1,6 @@
1
1
  import path from "path";
2
2
  import { TimeContext } from "../time/TimeContext.mjs";
3
+ import { EventTime } from "@rr0/data";
3
4
  export class CaseAnchorHandler {
4
5
  constructor(caseService, timeTextBuilder) {
5
6
  this.caseService = caseService;
@@ -32,7 +33,8 @@ export class CaseAnchorHandler {
32
33
  }
33
34
  }
34
35
  else {
35
- caseContext.time = new TimeContext((_a = caseTime.year) === null || _a === void 0 ? void 0 : _a.value, (_b = caseTime.month) === null || _b === void 0 ? void 0 : _b.value, (_c = caseTime.day) === null || _c === void 0 ? void 0 : _c.value, (_d = caseTime.hour) === null || _d === void 0 ? void 0 : _d.value, (_e = caseTime.minute) === null || _e === void 0 ? void 0 : _e.value, (_f = caseTime.timeshift) === null || _f === void 0 ? void 0 : _f.toString());
36
+ const startTime = EventTime.start(caseTime);
37
+ caseContext.time = new TimeContext((_a = startTime.year) === null || _a === void 0 ? void 0 : _a.value, (_b = startTime.month) === null || _b === void 0 ? void 0 : _b.value, (_c = startTime.day) === null || _c === void 0 ? void 0 : _c.value, (_d = startTime.hour) === null || _d === void 0 ? void 0 : _d.value, (_e = startTime.minute) === null || _e === void 0 ? void 0 : _e.value, (_f = startTime.timeshift) === null || _f === void 0 ? void 0 : _f.toString());
36
38
  }
37
39
  titles.push(this.timeTextBuilder.build(caseContext));
38
40
  }
@@ -6,6 +6,7 @@ import { RR0FileUtil } from "../util/file/RR0FileUtil.js";
6
6
  import { StringUtil } from "../util/string/StringUtil.js";
7
7
  import { HtmlTag } from "../util/html/HtmlTag.js";
8
8
  import { Chapter } from "./Chapters.js";
9
+ import { EventTime } from "@rr0/data";
9
10
  /**
10
11
  * Scan directories for book information, then populates a template with collected data.
11
12
  */
@@ -77,7 +78,7 @@ export class BookDirectoryStep extends DirectoryStep {
77
78
  const details = [];
78
79
  const authors = dirBook.authors;
79
80
  const authorStr = authors ? authors.join(" & ") + ": " : "";
80
- const time = dirBook.publication.time;
81
+ const time = EventTime.start(dirBook.publication.time);
81
82
  if (time) {
82
83
  const timeDetail = time.year.value;
83
84
  details.push(HtmlTag.toString("time", timeDetail.toString()));
@@ -1,6 +1,6 @@
1
1
  import path from "path";
2
2
  import { Gender } from "@rr0/common";
3
- import { AbstractDataFactory } from "@rr0/data";
3
+ import { EventTime, AbstractDataFactory } from "@rr0/data";
4
4
  export class PeopleHtmlRenderer {
5
5
  renderLink(context, people, pseudoPeopleList, allCountries, occupations, filterOccupations = [], content) {
6
6
  var _a, _b, _c;
@@ -15,8 +15,8 @@ export class PeopleHtmlRenderer {
15
15
  if (people.hoax) {
16
16
  classList.push("canular");
17
17
  }
18
- const birthTimeStr = (_a = people.birthTime) === null || _a === void 0 ? void 0 : _a.year.toString();
19
- const deathTimeStr = (_b = people.deathTime) === null || _b === void 0 ? void 0 : _b.year.toString();
18
+ const birthTimeStr = (_a = EventTime.start(people.birthTime)) === null || _a === void 0 ? void 0 : _a.year.toString();
19
+ const deathTimeStr = (_b = EventTime.start(people.deathTime)) === null || _b === void 0 ? void 0 : _b.year.toString();
20
20
  if (people.isDeceased()) {
21
21
  classList.push("deceased");
22
22
  }
@@ -1,12 +1,11 @@
1
- import { TypedDataFactory } from "@rr0/data";
1
+ import { EventTime, TypedDataFactory } from "@rr0/data";
2
2
  import { NamedPlace } from "@rr0/place";
3
- import { Level2Date as EdtfDate } from "@rr0/time";
4
3
  export class CaseFactory extends TypedDataFactory {
5
4
  parse(dataJson) {
6
5
  const parsedCase = super.parse(dataJson);
7
6
  const time = dataJson.time;
8
7
  if (time) {
9
- Object.assign(parsedCase, { time: EdtfDate.fromString(time) });
8
+ Object.assign(parsedCase, { time: EventTime.parse(time) });
10
9
  }
11
10
  const placeName = dataJson.place;
12
11
  if (placeName) {
@@ -0,0 +1,38 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { FileContents, FileContentsLang } from "@javarome/fileutil";
3
+ import { EventTime, RR0EventFactory } from "@rr0/data";
4
+ import { Level2Date as EdtfDate, Level2Interval as EdtfInterval } from "@rr0/time";
5
+ import { CaseFactory } from "./CaseFactory.js";
6
+ describe("CaseFactory", () => {
7
+ function read(time) {
8
+ const lang = new FileContentsLang();
9
+ lang.lang = "";
10
+ lang.variants = [];
11
+ const file = new FileContents("test/science/crypto/ufo/enquete/dossier/SomeCase/case.json", "utf-8", `{
12
+ "type": "case",
13
+ "title": "Some case",
14
+ "time": "${time}",
15
+ "place": "Washington",
16
+ "events": []
17
+ }
18
+ `, new Date("2025-02-22T22:22:07.720Z"), lang);
19
+ return new CaseFactory(new RR0EventFactory()).createFromFile(file);
20
+ }
21
+ test("a date", () => {
22
+ const aCase = read("1952-07-19");
23
+ expect(aCase.time).toBeInstanceOf(EdtfDate);
24
+ expect(aCase.time.toString()).toBe("1952-07-19");
25
+ });
26
+ test("an approximate date", () => {
27
+ expect(read("~1952").time.toString()).toBe("~1952");
28
+ });
29
+ test("between x and y", () => {
30
+ const aCase = read("1952-07-19/1952-07-26");
31
+ expect(aCase.time).toBeInstanceOf(EdtfInterval);
32
+ expect(aCase.time.toString()).toBe("1952-07-19/1952-07-26");
33
+ expect(EventTime.start(aCase.time).day.value).toBe(19);
34
+ });
35
+ test("years", () => {
36
+ expect(read("1966/1993").time.toString()).toBe("1966/1993");
37
+ });
38
+ });
@@ -1,5 +1,5 @@
1
1
  import path from "path";
2
- import { AbstractDataService } from "@rr0/data";
2
+ import { EventTime, AbstractDataService } from "@rr0/data";
3
3
  import { TimeContext } from "../../../../../time/TimeContext.mjs";
4
4
  export class CaseService extends AbstractDataService {
5
5
  constructor(dataService, factory, timeElementFactory, files) {
@@ -16,7 +16,7 @@ export class CaseService extends AbstractDataService {
16
16
  const classificationLabels = context.messages.case.classification.hynek[hynek];
17
17
  details.push(classificationLabels.short);
18
18
  }
19
- const time = aCase.time;
19
+ const time = EventTime.start(aCase.time); // The year of an interval is the one it starts at
20
20
  const caseContext = context.clone();
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());
@@ -31,8 +31,9 @@ export class EventRenderer {
31
31
  }
32
32
  async render(context, event, container, options = { url: true, contentOnly: false }) {
33
33
  const eventContext = context.clone();
34
- const eventTime = eventContext.time.date = event.time;
34
+ const eventTime = event.time;
35
35
  assert.ok(eventTime, `Event of type "${event.type}" has no time`);
36
+ eventContext.time.set(eventTime);
36
37
  container.dataset.time = eventTime.toString();
37
38
  const timeEl = this.timeElementFactory.create(eventContext, context, options);
38
39
  container.append(timeEl);
@@ -161,6 +161,13 @@ export class TimeContext {
161
161
  * @return {this}
162
162
  */
163
163
  reset(): this;
164
+ /**
165
+ * Sets what this context is about: a date, or an interval when the time is only known to be within bounds.
166
+ *
167
+ * @param {EdtfDate | EdtfInterval | undefined} time
168
+ * @return {this}
169
+ */
170
+ set(time: EdtfDate | EdtfInterval | undefined): this;
164
171
  /**
165
172
  * @param {TimeContext} other
166
173
  * @return {boolean}
@@ -307,6 +307,18 @@ export class TimeContext {
307
307
  this.duration = undefined;
308
308
  return this;
309
309
  }
310
+ /**
311
+ * Sets what this context is about: a date, or an interval when the time is only known to be within bounds.
312
+ *
313
+ * @param {EdtfDate | EdtfInterval | undefined} time
314
+ * @return {this}
315
+ */
316
+ set(time) {
317
+ this.date = time instanceof EdtfInterval ? undefined : time;
318
+ this.interval = time instanceof EdtfInterval ? time : undefined;
319
+ this.duration = undefined;
320
+ return this;
321
+ }
310
322
  /**
311
323
  * @param {TimeContext} other
312
324
  * @return {boolean}
@@ -1,5 +1,6 @@
1
1
  import { TimeContext } from "./TimeContext.mjs";
2
2
  import { describe, expect, test } from "vitest";
3
+ import { Level2Date, Level2Interval } from "@rr0/time";
3
4
  describe("TimeContext", () => {
4
5
  const timeContext = new TimeContext();
5
6
  const SECOND = 1000;
@@ -95,4 +96,32 @@ describe("TimeContext", () => {
95
96
  expect(clone.getHour()).toBe(17);
96
97
  expect(clone.getMinutes()).toBe(56);
97
98
  });
99
+ describe("set", () => {
100
+ test("a date", () => {
101
+ const context = new TimeContext().set(Level2Date.fromString("1952-07-19"));
102
+ expect(context.date.toString()).toBe("1952-07-19");
103
+ expect(context.interval).toBeUndefined();
104
+ });
105
+ test("an interval", () => {
106
+ const context = new TimeContext().set(Level2Interval.fromString("1952-07-19/1952-07-26"));
107
+ expect(context.date).toBeUndefined();
108
+ expect(context.interval.toString()).toBe("1952-07-19/1952-07-26");
109
+ expect(context.from.day.value).toBe(19);
110
+ expect(context.to.day.value).toBe(26);
111
+ });
112
+ test("replaces what was set before", () => {
113
+ const context = new TimeContext();
114
+ context.updateFromStr("1952-07-19");
115
+ context.set(Level2Interval.fromString("1966/1993"));
116
+ expect(context.date).toBeUndefined();
117
+ context.set(Level2Date.fromString("1970"));
118
+ expect(context.interval).toBeUndefined();
119
+ expect(context.date.toString()).toBe("1970");
120
+ });
121
+ test("nothing", () => {
122
+ const context = new TimeContext().set(undefined);
123
+ expect(context.date).toBeUndefined();
124
+ expect(context.interval).toBeUndefined();
125
+ });
126
+ });
98
127
  });
@@ -51,7 +51,7 @@ export class TimeDirectoryStep extends DirectoryStep {
51
51
  const eventContext = context.clone();
52
52
  this.service.setContextFromFile(eventContext, event.dirName);
53
53
  if (event.time) {
54
- eventContext.time.date = event.time;
54
+ eventContext.time.set(event.time);
55
55
  }
56
56
  const ref = this.elementFactory.create(eventContext, context, { url: true, contentOnly: true });
57
57
  item.appendChild(ref);
@@ -8,7 +8,7 @@ import { SourceRenderer } from "../source/SourceRenderer.js";
8
8
  import { NoteFileCounter } from "../note/NoteFileCounter.js";
9
9
  import { NoteRenderer } from "../note/NoteRenderer.js";
10
10
  import { HautsDeSeineCityCode } from "../org/eu/fr/region/idf/92/HautsDeSeineCityCode.js";
11
- import { AllDataService } from "@rr0/data";
11
+ import { AllDataService, EventTime } from "@rr0/data";
12
12
  import { Place } from "@rr0/place";
13
13
  import { OrganizationPlace } from "../place/OrganizationPlace.js";
14
14
  import { hautsDeSeine } from "../org/eu/fr/region/idf/92/HautsDeSeine.js";
@@ -49,4 +49,45 @@ describe("TimeEventRenderer", () => {
49
49
  await renderer.render(context, c, elem);
50
50
  expect(elem.innerHTML).toBe(`<span class="time-resolved">en <time datetime="1970-03">mars 1970</time></span> À <span class="place">Nanterre (Hauts-de-Seine (Île-de-France (France)))</span>, some sighting <span class="source"><span class="peopl">Some Author</span>&nbsp;: <a href="https://somesite.com/case1">Case 1</a>, <i>Some site</i>, jeudi 13 décembre 2001</span>.`);
51
51
  });
52
+ describe("time", () => {
53
+ async function renderTime(time) {
54
+ const context = cmsTestUtil.time.newHtmlContext("1/9/7/0/03/index.html");
55
+ const c = {
56
+ events: [], type: "event", eventType: "sighting", time: EventTime.parse(time), description: "some sighting",
57
+ sources: []
58
+ };
59
+ const elem = context.file.document.createElement("li");
60
+ await renderer.render(context, c, elem);
61
+ return { html: elem.innerHTML, dataTime: elem.dataset.time };
62
+ }
63
+ test("a date", async () => {
64
+ const { html, dataTime } = await renderTime("1970-03-05");
65
+ expect(dataTime).toBe("1970-03-05");
66
+ expect(html).toContain(`<time datetime="1970-03-05"`);
67
+ expect(html).not.toContain("time-interval");
68
+ });
69
+ test("between x and y", async () => {
70
+ const { html, dataTime } = await renderTime("1970-03-05/1970-03-09");
71
+ expect(dataTime).toBe("1970-03-05/1970-03-09");
72
+ expect(html).toContain(`<span class="time-interval">`);
73
+ expect(html).toContain(`<time datetime="1970-03-05"`);
74
+ expect(html).toContain(`<time datetime="1970-03-09"`);
75
+ });
76
+ test("years", async () => {
77
+ const { html } = await renderTime("1966/1993");
78
+ expect(html).toContain(`<time datetime="1966"`);
79
+ expect(html).toContain(`<time datetime="1993"`);
80
+ });
81
+ test("from x", async () => {
82
+ const { html, dataTime } = await renderTime("1970-03/..");
83
+ expect(dataTime).toBe("1970-03/");
84
+ expect(html).toContain(`<time datetime="1970-03"`);
85
+ expect(html).toContain(`<span class="time-interval">`);
86
+ });
87
+ test("around x", async () => {
88
+ const { html, dataTime } = await renderTime("~1970-03");
89
+ expect(dataTime).toBe("~1970-03");
90
+ expect(html).toContain(`<time datetime="1970-03"`);
91
+ });
92
+ });
52
93
  });
@@ -1,5 +1,6 @@
1
1
  import { RR0HttpDatasource } from "./rr0/RR0HttpDatasource.js";
2
2
  import { HttpSource } from "./HttpSource.js";
3
+ import { EventTime } from "@rr0/data";
3
4
  /**
4
5
  * Replaces a (ul) tag from (chronology) files with case summaries from external datasources.
5
6
  */
@@ -101,7 +102,7 @@ export class ChronologyReplacer {
101
102
  const casesToMerge = sourceCases.map(sourceCase => mapping.mapper.map(context, sourceCase, fetchTime));
102
103
  const casesToAdd = [];
103
104
  for (const caseToMerge of casesToMerge) {
104
- const foundExisting = existingCases.find(existingCase => existingCase.time.isEqual(caseToMerge.time)
105
+ const foundExisting = existingCases.find(existingCase => EventTime.equals(existingCase.time, caseToMerge.time)
105
106
  && existingCase.place === caseToMerge.place);
106
107
  if (foundExisting) {
107
108
  context.logger.debug("Merging ", caseToMerge, " into ", foundExisting);
@@ -72,7 +72,7 @@ export class DatasourceTestCase {
72
72
  const datasource = this.mapping.datasource;
73
73
  const source = expectedSources[0];
74
74
  const sourceContext = context.clone();
75
- sourceContext.time.date = source.publication.time;
75
+ sourceContext.time.set(source.publication.time);
76
76
  const publicationStr = source.publication ? `, ${this.timeTextBuilder.build(sourceContext)}` : "";
77
77
  const indexStr = source.index ? `, ${source.index}` : "";
78
78
  const authorStr = datasource.authors.map(authorStr => `<span class="people">${authorStr}</span>`).join(" &amp; ");
@@ -6,6 +6,7 @@ import { Level2Date as EdtfDate } from "@rr0/time";
6
6
  import { HtmlTag } from "../../../util/html/HtmlTag.js";
7
7
  import { RR0Datasource } from "./RR0Datasource.js";
8
8
  import { TimeTextBuilder } from "../../text/TimeTextBuilder.js";
9
+ import { EventTime } from "@rr0/data";
9
10
  import { RR0CaseSummaryMapper } from "./RR0CaseSummaryMapper.js";
10
11
  import { RR0FileDatasource } from "./RR0FileDatasource.js";
11
12
  import { NamedPlace } from "@rr0/place";
@@ -37,10 +38,12 @@ describe("RR0CaseSource", () => {
37
38
  super(mapping, sourceCases);
38
39
  }
39
40
  getTime(c) {
40
- return c.time;
41
+ return EventTime.start(c.time);
41
42
  }
42
43
  sortComparator(c1, c2) {
43
- return !c1.time || c2.time && c1.time.isBefore(c2.time) ? -1 : !c2.time || c1.time.isAfter(c2.time) ? 1 : 0;
44
+ const time1 = EventTime.start(c1.time);
45
+ const time2 = EventTime.start(c2.time);
46
+ return !time1 || time2 && time1.isBefore(time2) ? -1 : !time2 || time1.isAfter(time2) ? 1 : 0;
44
47
  }
45
48
  /**
46
49
  * Specialization of sources for RR0 cases
@@ -62,7 +65,7 @@ describe("RR0CaseSource", () => {
62
65
  }
63
66
  if (publication.time) {
64
67
  const sourceContext = context.clone();
65
- sourceContext.time.date = source.publication.time;
68
+ sourceContext.time.set(source.publication.time);
66
69
  const timeStr = this.timeTextBuilder.build(sourceContext);
67
70
  sourceItems.push(timeStr);
68
71
  }
@@ -60,7 +60,7 @@ describe("SCEAUCaseSource", () => {
60
60
  }
61
61
  if (publication.time) {
62
62
  const sourceContext = context.clone();
63
- sourceContext.time.date = source.publication.time;
63
+ sourceContext.time.set(source.publication.time);
64
64
  const timeStr = this.timeTextBuilder.build(sourceContext);
65
65
  sourceItems.push(timeStr);
66
66
  }
@@ -382,6 +382,20 @@ describe("TimeReplacer", async () => {
382
382
  expect((_b = (_a = context.time.date) === null || _a === void 0 ? void 0 : _a.timeshift) === null || _b === void 0 ? void 0 : _b.value, str).toBe(-480);
383
383
  }
384
384
  });
385
+ test("a time zone that depends on the date", async () => {
386
+ var _a, _b;
387
+ for (const [str, hours] of [["1969-07-20 20:17 (PT)", -7], ["1969-12-20 20:17 PT", -8], ["2009-07-19 05:54 PT", -7]]) {
388
+ const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
389
+ await render(context, str);
390
+ expect((_b = (_a = context.time.date) === null || _a === void 0 ? void 0 : _a.timeshift) === null || _b === void 0 ? void 0 : _b.value, str).toBe(hours * 60);
391
+ }
392
+ });
393
+ test("GMT is a time zone", async () => {
394
+ var _a, _b;
395
+ const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
396
+ await render(context, "1969-07-20 20:17:40 GMT");
397
+ expect((_b = (_a = context.time.date) === null || _a === void 0 ? void 0 : _a.timeshift) === null || _b === void 0 ? void 0 : _b.value).toBe(0);
398
+ });
385
399
  test("a sidereal time is not a time zone, and is kept as text", async () => {
386
400
  var _a;
387
401
  const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
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.81",
5
+ "version": "0.3.82",
6
6
  "description": "RR0 Content Management System (CMS)",
7
7
  "exports": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -31,10 +31,10 @@
31
31
  "dependencies": {
32
32
  "@javarome/fileutil": "^0.3.9",
33
33
  "@rr0/common": "^1.1.5",
34
- "@rr0/data": "^0.3.48",
34
+ "@rr0/data": "^0.3.49",
35
35
  "@rr0/lang": "^0.1.13",
36
36
  "@rr0/place": "^0.5.4",
37
- "@rr0/time": "^2.0.0",
37
+ "@rr0/time": "^2.2.0",
38
38
  "canvas": "^3.2.1",
39
39
  "csv-parser": "^3.2.0",
40
40
  "glob": "^11.0.3",