@rr0/cms 0.3.80 → 0.3.81
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.
- package/dist/time/TimeContext.test.mjs +1 -1
- package/dist/time/html/HtmlDatetime.d.ts +36 -0
- package/dist/time/html/HtmlDatetime.js +102 -0
- package/dist/time/html/HtmlDatetime.test.d.ts +1 -0
- package/dist/time/html/HtmlDatetime.test.js +80 -0
- package/dist/time/html/TimeElementFactory.js +1 -2
- package/dist/time/html/TimeRenderer.js +1 -1
- package/dist/time/html/TimeReplacer.d.ts +6 -1
- package/dist/time/html/TimeReplacer.js +8 -3
- package/dist/time/html/TimeReplacer.test.js +18 -4
- package/dist/time/html/TimeStringCompleter.d.ts +6 -0
- package/dist/time/html/TimeStringCompleter.js +21 -4
- package/package.json +3 -3
|
@@ -60,7 +60,7 @@ describe("TimeContext", () => {
|
|
|
60
60
|
expect(timeContext.getMinutes()).toBe(55);
|
|
61
61
|
});
|
|
62
62
|
test("updateFromStr with a time zone containing a P is a date, not a duration", () => {
|
|
63
|
-
for (const str of ["1957-02-27 21:45PST", "1991-09-18 00:38:42PDT"
|
|
63
|
+
for (const str of ["1957-02-27 21:45PST", "1991-09-18 00:38:42PDT"]) {
|
|
64
64
|
const context = new TimeContext();
|
|
65
65
|
expect(context.updateFromStr(str)).toBe(true);
|
|
66
66
|
expect(context.duration).toBeUndefined();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Level2Date, Level2Duration } from "@rr0/time";
|
|
2
|
+
/**
|
|
3
|
+
* The `datetime` attribute of a <time> and the EDTF it comes from.
|
|
4
|
+
*
|
|
5
|
+
* HTML knows its own formats only: a year, month, date, local or global date and time ("2004-06-11T09:12:33.123+01:00"),
|
|
6
|
+
* and durations in days, hours, minutes and seconds ("P1DT2H30M"). EDTF is richer: qualified values ("2004-~06", "~P2H"),
|
|
7
|
+
* a time zone written "+01", years and months in durations. So `datetime` holds what HTML can express, and what it cannot
|
|
8
|
+
* is kept as it was written in `data-edtf`, which is only set when it differs from `datetime`.
|
|
9
|
+
*/
|
|
10
|
+
export declare class HtmlDatetime {
|
|
11
|
+
/**
|
|
12
|
+
* Sets the attributes of a <time> element for a time.
|
|
13
|
+
*
|
|
14
|
+
* @param el The element to set the attributes of.
|
|
15
|
+
* @param time The time the element is about.
|
|
16
|
+
*/
|
|
17
|
+
static apply(el: HTMLElement, time: Level2Date | Level2Duration): void;
|
|
18
|
+
/**
|
|
19
|
+
* @return The `datetime` value that HTML can read for a time, or undefined if HTML has no equivalent.
|
|
20
|
+
*/
|
|
21
|
+
static of(time: Level2Date | Level2Duration): string | undefined;
|
|
22
|
+
protected static ofDate(date: Level2Date): string | undefined;
|
|
23
|
+
protected static ofDuration(duration: Level2Duration): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* @return The numeric value of a component, if it has one (not a range of values, as for unspecified digits) within bounds.
|
|
26
|
+
*/
|
|
27
|
+
protected static value(comp: {
|
|
28
|
+
value: unknown;
|
|
29
|
+
} | undefined, min: number, max?: number): number | undefined;
|
|
30
|
+
protected static pad(n: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* @param minutes The time zone offset, in minutes east of UTC.
|
|
33
|
+
* @return The time zone offset as HTML writes it ("Z", "+01:00").
|
|
34
|
+
*/
|
|
35
|
+
protected static offset(minutes: number): string;
|
|
36
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Level2Duration } from "@rr0/time";
|
|
2
|
+
/**
|
|
3
|
+
* The `datetime` attribute of a <time> and the EDTF it comes from.
|
|
4
|
+
*
|
|
5
|
+
* HTML knows its own formats only: a year, month, date, local or global date and time ("2004-06-11T09:12:33.123+01:00"),
|
|
6
|
+
* and durations in days, hours, minutes and seconds ("P1DT2H30M"). EDTF is richer: qualified values ("2004-~06", "~P2H"),
|
|
7
|
+
* a time zone written "+01", years and months in durations. So `datetime` holds what HTML can express, and what it cannot
|
|
8
|
+
* is kept as it was written in `data-edtf`, which is only set when it differs from `datetime`.
|
|
9
|
+
*/
|
|
10
|
+
export class HtmlDatetime {
|
|
11
|
+
/**
|
|
12
|
+
* Sets the attributes of a <time> element for a time.
|
|
13
|
+
*
|
|
14
|
+
* @param el The element to set the attributes of.
|
|
15
|
+
* @param time The time the element is about.
|
|
16
|
+
*/
|
|
17
|
+
static apply(el, time) {
|
|
18
|
+
const datetime = HtmlDatetime.of(time);
|
|
19
|
+
if (datetime !== undefined) {
|
|
20
|
+
el.dateTime = datetime;
|
|
21
|
+
}
|
|
22
|
+
const edtf = time.toString();
|
|
23
|
+
if (edtf !== datetime) {
|
|
24
|
+
el.dataset.edtf = edtf;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @return The `datetime` value that HTML can read for a time, or undefined if HTML has no equivalent.
|
|
29
|
+
*/
|
|
30
|
+
static of(time) {
|
|
31
|
+
return time instanceof Level2Duration ? HtmlDatetime.ofDuration(time) : HtmlDatetime.ofDate(time);
|
|
32
|
+
}
|
|
33
|
+
static ofDate(date) {
|
|
34
|
+
var _a;
|
|
35
|
+
const year = HtmlDatetime.value(date.year, 1);
|
|
36
|
+
if (year === undefined) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
let str = String(year).padStart(4, "0");
|
|
40
|
+
const month = HtmlDatetime.value(date.month, 1, 12); // Seasons (21 to 24) are no month
|
|
41
|
+
if (month === undefined) {
|
|
42
|
+
return str;
|
|
43
|
+
}
|
|
44
|
+
str += "-" + HtmlDatetime.pad(month);
|
|
45
|
+
const day = HtmlDatetime.value(date.day, 1, 31);
|
|
46
|
+
if (day === undefined) {
|
|
47
|
+
return str;
|
|
48
|
+
}
|
|
49
|
+
str += "-" + HtmlDatetime.pad(day);
|
|
50
|
+
const hour = HtmlDatetime.value(date.hour, 0, 23);
|
|
51
|
+
const minute = HtmlDatetime.value(date.minute, 0, 59);
|
|
52
|
+
if (hour === undefined || minute === undefined) { // HTML has no time without minutes
|
|
53
|
+
return str;
|
|
54
|
+
}
|
|
55
|
+
str += "T" + HtmlDatetime.pad(hour) + ":" + HtmlDatetime.pad(minute);
|
|
56
|
+
const second = HtmlDatetime.value(date.second, 0, 59);
|
|
57
|
+
if (second !== undefined) {
|
|
58
|
+
str += ":" + HtmlDatetime.pad(second);
|
|
59
|
+
const millisecond = HtmlDatetime.value(date.millisecond, 0, 999); // @rr0/time 1.1.0
|
|
60
|
+
if (millisecond !== undefined) {
|
|
61
|
+
str += "." + String(millisecond).padStart(3, "0");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const timeshift = (_a = date.timeshift) === null || _a === void 0 ? void 0 : _a.value;
|
|
65
|
+
if (typeof timeshift === "number" && Number.isInteger(timeshift)) {
|
|
66
|
+
str += HtmlDatetime.offset(timeshift);
|
|
67
|
+
}
|
|
68
|
+
return str;
|
|
69
|
+
}
|
|
70
|
+
static ofDuration(duration) {
|
|
71
|
+
const spec = duration.toSpec();
|
|
72
|
+
if (spec.years || spec.months) { // HTML durations have no years nor months, whose length depends on the date
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const numbers = [spec.days, spec.hours, spec.minutes, spec.seconds].map(comp => HtmlDatetime.value(comp, 0));
|
|
76
|
+
const [days, hours, minutes, seconds] = numbers;
|
|
77
|
+
const time = (hours ? hours + "H" : "") + (minutes ? minutes + "M" : "") + (seconds ? seconds + "S" : "");
|
|
78
|
+
const str = (days ? days + "D" : "") + (time ? "T" + time : "");
|
|
79
|
+
return str ? "P" + str : undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @return The numeric value of a component, if it has one (not a range of values, as for unspecified digits) within bounds.
|
|
83
|
+
*/
|
|
84
|
+
static value(comp, min, max = Number.MAX_SAFE_INTEGER) {
|
|
85
|
+
const value = comp === null || comp === void 0 ? void 0 : comp.value;
|
|
86
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max ? value : undefined;
|
|
87
|
+
}
|
|
88
|
+
static pad(n) {
|
|
89
|
+
return String(n).padStart(2, "0");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @param minutes The time zone offset, in minutes east of UTC.
|
|
93
|
+
* @return The time zone offset as HTML writes it ("Z", "+01:00").
|
|
94
|
+
*/
|
|
95
|
+
static offset(minutes) {
|
|
96
|
+
if (minutes === 0) {
|
|
97
|
+
return "Z";
|
|
98
|
+
}
|
|
99
|
+
const abs = Math.abs(minutes);
|
|
100
|
+
return (minutes < 0 ? "-" : "+") + HtmlDatetime.pad(Math.floor(abs / 60)) + ":" + HtmlDatetime.pad(abs % 60);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { Level2Date, Level2Duration } from "@rr0/time";
|
|
3
|
+
import { HtmlDatetime } from "./HtmlDatetime.js";
|
|
4
|
+
import { cmsTestUtil } from "../../test/CMSTestUtil.js";
|
|
5
|
+
describe("HtmlDatetime", () => {
|
|
6
|
+
describe("date", () => {
|
|
7
|
+
test.each([
|
|
8
|
+
["2004", "2004"],
|
|
9
|
+
["0033", "0033"],
|
|
10
|
+
["2004-06", "2004-06"],
|
|
11
|
+
["2004-06-11", "2004-06-11"],
|
|
12
|
+
["2004-06-11T09:12", "2004-06-11T09:12"],
|
|
13
|
+
["2004-06-11T09:12:33", "2004-06-11T09:12:33"],
|
|
14
|
+
["2004-06-11 09:12", "2004-06-11T09:12"],
|
|
15
|
+
["2004-06-11T09:12:33Z", "2004-06-11T09:12:33Z"],
|
|
16
|
+
["2004-06-11T09:12:33+01", "2004-06-11T09:12:33+01:00"],
|
|
17
|
+
["2004-06-11T09:12-05", "2004-06-11T09:12-05:00"],
|
|
18
|
+
["1947-06-24 21:45PST", "1947-06-24T21:45-08:00"]
|
|
19
|
+
])("%s is %s", (edtf, expected) => {
|
|
20
|
+
expect(HtmlDatetime.of(Level2Date.fromString(edtf))).toBe(expected);
|
|
21
|
+
});
|
|
22
|
+
test("qualification is not part of the HTML value", () => {
|
|
23
|
+
expect(HtmlDatetime.of(Level2Date.fromString("2004-~06"))).toBe("2004-06");
|
|
24
|
+
expect(HtmlDatetime.of(Level2Date.fromString("2004-06~"))).toBe("2004-06");
|
|
25
|
+
expect(HtmlDatetime.of(Level2Date.fromString("?2004-06-11"))).toBe("2004-06-11");
|
|
26
|
+
});
|
|
27
|
+
test("an hour without minutes has no HTML equivalent, the date remains", () => {
|
|
28
|
+
expect(HtmlDatetime.of(new Level2Date({ year: 2004, month: 6, day: 11, hour: 9 }))).toBe("2004-06-11");
|
|
29
|
+
});
|
|
30
|
+
test("a season is no month", () => {
|
|
31
|
+
expect(HtmlDatetime.of(Level2Date.fromString("2004-21"))).toBe("2004");
|
|
32
|
+
});
|
|
33
|
+
test("a year before 1 has no HTML equivalent", () => {
|
|
34
|
+
expect(HtmlDatetime.of(Level2Date.fromString("-0033"))).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("duration", () => {
|
|
38
|
+
test.each([
|
|
39
|
+
["P2D", "P2D"],
|
|
40
|
+
["P20H", "PT20H"],
|
|
41
|
+
["P90S", "PT1M30S"],
|
|
42
|
+
["P2D10H23M45S", "P2DT10H23M45S"],
|
|
43
|
+
["PT30M", "PT30M"]
|
|
44
|
+
])("%s is %s", (edtf, expected) => {
|
|
45
|
+
expect(HtmlDatetime.of(Level2Duration.fromString(edtf))).toBe(expected);
|
|
46
|
+
});
|
|
47
|
+
test("months are written MM in the former notation, and have no HTML equivalent", () => {
|
|
48
|
+
expect(HtmlDatetime.of(Level2Duration.fromString("P2MM"))).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
test("years have no HTML equivalent", () => {
|
|
51
|
+
expect(HtmlDatetime.of(Level2Duration.fromString("P1Y"))).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
test("qualification is not part of the HTML value", () => {
|
|
54
|
+
expect(HtmlDatetime.of(Level2Duration.fromString("~P20H"))).toBe("PT20H");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("element", () => {
|
|
58
|
+
const newTimeEl = () => cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "").file.document.createElement("time");
|
|
59
|
+
test("data-edtf is not set when HTML reads the same value", () => {
|
|
60
|
+
const timeEl = newTimeEl();
|
|
61
|
+
HtmlDatetime.apply(timeEl, Level2Date.fromString("2004-06-11"));
|
|
62
|
+
expect(timeEl.getAttribute("datetime")).toBe("2004-06-11");
|
|
63
|
+
expect(timeEl.hasAttribute("data-edtf")).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
test("data-edtf holds the EDTF when HTML reads it differently", () => {
|
|
66
|
+
const timeEl = newTimeEl();
|
|
67
|
+
const date = Level2Date.fromString("2004-06-11T09:12+01");
|
|
68
|
+
HtmlDatetime.apply(timeEl, date);
|
|
69
|
+
expect(timeEl.getAttribute("datetime")).toBe("2004-06-11T09:12+01:00");
|
|
70
|
+
expect(timeEl.getAttribute("data-edtf")).toBe(date.toString());
|
|
71
|
+
});
|
|
72
|
+
test("data-edtf holds the EDTF when HTML has no equivalent, and then datetime is not set", () => {
|
|
73
|
+
const timeEl = newTimeEl();
|
|
74
|
+
const duration = Level2Duration.fromString("P2MM");
|
|
75
|
+
HtmlDatetime.apply(timeEl, duration);
|
|
76
|
+
expect(timeEl.hasAttribute("datetime")).toBe(false);
|
|
77
|
+
expect(timeEl.getAttribute("data-edtf")).toBe(duration.toString());
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -77,7 +77,6 @@ export class TimeElementFactory {
|
|
|
77
77
|
const duration = context.time.duration;
|
|
78
78
|
const items = [];
|
|
79
79
|
const messages = context.messages.context.time.duration;
|
|
80
|
-
const datetime = duration.toString();
|
|
81
80
|
const spec = duration.toSpec();
|
|
82
81
|
const days = spec.days;
|
|
83
82
|
if (days) {
|
|
@@ -105,7 +104,7 @@ export class TimeElementFactory {
|
|
|
105
104
|
if (context.time.approximate) {
|
|
106
105
|
replacementStr = messages.approximate(replacementStr);
|
|
107
106
|
}
|
|
108
|
-
replacement = TimeReplacer.resolvedTime(context,
|
|
107
|
+
replacement = TimeReplacer.resolvedTime(context, duration);
|
|
109
108
|
replacement.classList.add("duration");
|
|
110
109
|
replacement.textContent = replacementStr;
|
|
111
110
|
}
|
|
@@ -28,7 +28,7 @@ export class TimeRenderer {
|
|
|
28
28
|
const currentFileName = file.name;
|
|
29
29
|
const doc = file.document;
|
|
30
30
|
let replacement;
|
|
31
|
-
const timeEl = TimeReplacer.resolvedTime(context, date
|
|
31
|
+
const timeEl = TimeReplacer.resolvedTime(context, date);
|
|
32
32
|
if (title !== text) {
|
|
33
33
|
timeEl.title = title;
|
|
34
34
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HtmlRR0Context } from "../../RR0Context.js";
|
|
2
2
|
import { DomReplacement } from "../DomReplacement.js";
|
|
3
|
+
import { Level2Date, Level2Duration } from "@rr0/time";
|
|
3
4
|
import { TimeElementFactory } from "./TimeElementFactory.js";
|
|
4
5
|
import { TimeRenderOptions } from "./TimeRenderer.js";
|
|
5
6
|
import { TimeStringCompleter } from "./TimeStringCompleter.js";
|
|
@@ -15,7 +16,11 @@ export declare class TimeReplacer implements DomReplacement<HtmlRR0Context, HTML
|
|
|
15
16
|
static readonly inlineTags: string[];
|
|
16
17
|
protected readonly completer: TimeStringCompleter;
|
|
17
18
|
constructor(factory: TimeElementFactory);
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @param context The context to create the element in.
|
|
21
|
+
* @param time The time the element is about, which `datetime` and `data-edtf` are set from.
|
|
22
|
+
*/
|
|
23
|
+
static resolvedTime(context: HtmlRR0Context, time: Level2Date | Level2Duration): HTMLTimeElement;
|
|
19
24
|
/**
|
|
20
25
|
* @return The text that precedes an element in its sentence.
|
|
21
26
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ObjectUtils } from "@rr0/common";
|
|
2
|
+
import { HtmlDatetime } from "./HtmlDatetime.js";
|
|
2
3
|
import { TimeStringCompleter } from "./TimeStringCompleter.js";
|
|
3
4
|
/**
|
|
4
5
|
* Replaces a <time> tag.
|
|
@@ -8,9 +9,13 @@ export class TimeReplacer {
|
|
|
8
9
|
this.factory = factory;
|
|
9
10
|
this.completer = new TimeStringCompleter();
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param context The context to create the element in.
|
|
14
|
+
* @param time The time the element is about, which `datetime` and `data-edtf` are set from.
|
|
15
|
+
*/
|
|
16
|
+
static resolvedTime(context, time) {
|
|
12
17
|
const replacement = context.file.document.createElement("time");
|
|
13
|
-
replacement
|
|
18
|
+
HtmlDatetime.apply(replacement, time);
|
|
14
19
|
return replacement;
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
@@ -34,7 +39,7 @@ export class TimeReplacer {
|
|
|
34
39
|
}
|
|
35
40
|
async replacement(pageContext, origEl) {
|
|
36
41
|
let replacement;
|
|
37
|
-
if (origEl.dateTime || origEl.dataset.format === "none") { // Already done, or not to be interpreted
|
|
42
|
+
if (origEl.dateTime || origEl.dataset.edtf || origEl.dataset.format === "none") { // Already done, or not to be interpreted
|
|
38
43
|
replacement = origEl;
|
|
39
44
|
}
|
|
40
45
|
else {
|
|
@@ -153,7 +153,7 @@ describe("TimeReplacer", async () => {
|
|
|
153
153
|
original.textContent = "2003-12-24T10:22CDT";
|
|
154
154
|
const replacement = await replacer.replacement(context, original);
|
|
155
155
|
expect(replacement.outerHTML)
|
|
156
|
-
.toBe(`<span class="time-resolved"><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
|
|
156
|
+
.toBe(`<span class="time-resolved"><a href="${path.join("/", timeOptions.rootDir, "2/0/0/3/12/24/")}"><time datetime="2003-12-24T10:22-05:00" data-edtf="2003-12-24T10:22-05">mercredi 24 décembre 2003 à 10:22</time></a></span>`); // TODO: Text should have timezone info
|
|
157
157
|
expect(context.time.getYear()).toBe(2003);
|
|
158
158
|
expect(context.time.getMonth()).toBe(12);
|
|
159
159
|
expect(context.time.getDayOfMonth()).toBe(24);
|
|
@@ -262,7 +262,7 @@ describe("TimeReplacer", async () => {
|
|
|
262
262
|
const datetime = "P2D10H23M45S";
|
|
263
263
|
timeEl.textContent = datetime;
|
|
264
264
|
const replacement = await replacer.replacement(context, timeEl);
|
|
265
|
-
expect(replacement.outerHTML).toBe(`<time datetime="${datetime}" class="duration">2 jours, 10 heures, 23 minutes et 45 secondes</time>`);
|
|
265
|
+
expect(replacement.outerHTML).toBe(`<time datetime="P2DT10H23M45S" data-edtf="${datetime}" class="duration">2 jours, 10 heures, 23 minutes et 45 secondes</time>`);
|
|
266
266
|
});
|
|
267
267
|
test("with context", async () => {
|
|
268
268
|
const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
|
|
@@ -276,7 +276,7 @@ describe("TimeReplacer", async () => {
|
|
|
276
276
|
const element = context.file.document.createElement("time");
|
|
277
277
|
element.textContent = "P20M";
|
|
278
278
|
const replacement = await replacer.replacement(context, element);
|
|
279
|
-
expect(replacement.outerHTML).toBe(`<time datetime="P20M" class="duration">20 minutes</time>`);
|
|
279
|
+
expect(replacement.outerHTML).toBe(`<time datetime="PT20M" data-edtf="P20M" class="duration">20 minutes</time>`);
|
|
280
280
|
}
|
|
281
281
|
});
|
|
282
282
|
test("with approximation", async () => {
|
|
@@ -284,7 +284,7 @@ describe("TimeReplacer", async () => {
|
|
|
284
284
|
const original = context.file.document.createElement("time");
|
|
285
285
|
original.textContent = "P~2H";
|
|
286
286
|
const replacement = await replacer.replacement(context, original);
|
|
287
|
-
expect(replacement.outerHTML).toBe(`<time datetime="
|
|
287
|
+
expect(replacement.outerHTML).toBe(`<time datetime="PT2H" data-edtf="P~2H" class="duration">environ 2 heures</time>`);
|
|
288
288
|
});
|
|
289
289
|
});
|
|
290
290
|
describe("parses hour", () => {
|
|
@@ -374,6 +374,20 @@ describe("TimeReplacer", async () => {
|
|
|
374
374
|
const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
|
|
375
375
|
expect(await render(context, "1947-06-24 21:45PST")).toBe("mardi 24 juin 1947 à 21:45");
|
|
376
376
|
});
|
|
377
|
+
test("a time zone in parentheses, as pages write it, is read as a time zone", async () => {
|
|
378
|
+
var _a, _b;
|
|
379
|
+
for (const str of ["1947-06-24 21:45 (PST)", "1947-06-24 21:45 PST"]) {
|
|
380
|
+
const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
|
|
381
|
+
expect(await render(context, str)).toBe("mardi 24 juin 1947 à 21:45");
|
|
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
|
+
}
|
|
384
|
+
});
|
|
385
|
+
test("a sidereal time is not a time zone, and is kept as text", async () => {
|
|
386
|
+
var _a;
|
|
387
|
+
const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
|
|
388
|
+
expect(await render(context, "1967-05-13 15:40 (LST)")).toBe("samedi 13 mai 1967 à 15:40 (LST)");
|
|
389
|
+
expect((_a = context.time.date) === null || _a === void 0 ? void 0 : _a.timeshift).toBeUndefined();
|
|
390
|
+
});
|
|
377
391
|
test("time inside a link gets no link of its own", async () => {
|
|
378
392
|
const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
|
|
379
393
|
const doc = context.file.document;
|
|
@@ -20,6 +20,12 @@ export declare class TimeStringCompleter {
|
|
|
20
20
|
* A trailing time zone ("PST", "(CDT)", "UTC+1") is part of the value, not free text.
|
|
21
21
|
*/
|
|
22
22
|
protected static readonly timeZone: RegExp;
|
|
23
|
+
/**
|
|
24
|
+
* @param str A word following a time.
|
|
25
|
+
* @return The time zone it names, as EDTF writes it right after the time ("EST" for "(EST)"), or undefined if it is not a time zone
|
|
26
|
+
* EDTF knows ("(LST)" is a local sidereal time, whose offset is unknown, so it remains free text).
|
|
27
|
+
*/
|
|
28
|
+
protected static zone(str: string): string | undefined;
|
|
23
29
|
/**
|
|
24
30
|
* A duration ("P10M", "~P1H"), or a range of durations ("P10M/12M").
|
|
25
31
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { timeZones } from "@rr0/time";
|
|
1
2
|
import { TimeContext } from "../TimeContext.mjs";
|
|
2
3
|
/**
|
|
3
4
|
* Prepares a <time> string for EDTF parsing.
|
|
@@ -8,6 +9,16 @@ import { TimeContext } from "../TimeContext.mjs";
|
|
|
8
9
|
* first. Free text written around the value ("le soir", "vers") is set apart so that it can be kept as is.
|
|
9
10
|
*/
|
|
10
11
|
export class TimeStringCompleter {
|
|
12
|
+
/**
|
|
13
|
+
* @param str A word following a time.
|
|
14
|
+
* @return The time zone it names, as EDTF writes it right after the time ("EST" for "(EST)"), or undefined if it is not a time zone
|
|
15
|
+
* EDTF knows ("(LST)" is a local sidereal time, whose offset is unknown, so it remains free text).
|
|
16
|
+
*/
|
|
17
|
+
static zone(str) {
|
|
18
|
+
var _a;
|
|
19
|
+
const name = (_a = TimeStringCompleter.timeZone.exec(str)) === null || _a === void 0 ? void 0 : _a[1];
|
|
20
|
+
return name && (/^UTC[+-]?\d*$/.test(name) || timeZones.some(zone => zone.name === name && zone.timeshift !== "?")) ? name : undefined;
|
|
21
|
+
}
|
|
11
22
|
split(str) {
|
|
12
23
|
let prefix = "";
|
|
13
24
|
let value = str.replace(/[ \t\r\n]+/g, " ").trim(); // "1973-10-11\n 21:00/22:00"
|
|
@@ -23,9 +34,15 @@ export class TimeStringCompleter {
|
|
|
23
34
|
// A bare number followed by a space may be a quantity ("1 h", "30 mn après", "8 jours plus tard"), so a day of the
|
|
24
35
|
// month needs a T before its text ("18Tla nuit").
|
|
25
36
|
const quantity = post && /^\d{1,2}$/.test(post[1]) && !post[2].includes("T");
|
|
26
|
-
if (post && !quantity
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
if (post && !quantity) {
|
|
38
|
+
const zone = TimeStringCompleter.zone(post[3].trim());
|
|
39
|
+
if (zone) {
|
|
40
|
+
value = post[1] + zone; // EDTF reads "21:45EST", not "21:45 EST" nor "21:45 (EST)"
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
value = post[1];
|
|
44
|
+
suffix = (post[2].includes("T") ? " " : post[2]) + post[3];
|
|
45
|
+
}
|
|
29
46
|
}
|
|
30
47
|
return { prefix, value, suffix };
|
|
31
48
|
}
|
|
@@ -121,7 +138,7 @@ export class TimeStringCompleter {
|
|
|
121
138
|
/**
|
|
122
139
|
* A trailing time zone ("PST", "(CDT)", "UTC+1") is part of the value, not free text.
|
|
123
140
|
*/
|
|
124
|
-
TimeStringCompleter.timeZone = /^\(?(
|
|
141
|
+
TimeStringCompleter.timeZone = /^\(?([A-Z]{1,5}|UTC[+-]?\d*)\)?$/;
|
|
125
142
|
/**
|
|
126
143
|
* A duration ("P10M", "~P1H"), or a range of durations ("P10M/12M").
|
|
127
144
|
*/
|
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.
|
|
5
|
+
"version": "0.3.81",
|
|
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.
|
|
34
|
+
"@rr0/data": "^0.3.48",
|
|
35
35
|
"@rr0/lang": "^0.1.13",
|
|
36
36
|
"@rr0/place": "^0.5.4",
|
|
37
|
-
"@rr0/time": "^
|
|
37
|
+
"@rr0/time": "^2.0.0",
|
|
38
38
|
"canvas": "^3.2.1",
|
|
39
39
|
"csv-parser": "^3.2.0",
|
|
40
40
|
"glob": "^11.0.3",
|