@rr0/cms 0.3.67 → 0.3.69
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/CMSGenerator.test.js +10 -1
- package/dist/time/RR0TimeMessages.d.ts +5 -0
- package/dist/time/RR0TimeMessages_en.d.ts +2 -0
- package/dist/time/RR0TimeMessages_en.js +2 -0
- package/dist/time/RR0TimeMessages_fr.d.ts +2 -0
- package/dist/time/RR0TimeMessages_fr.js +2 -0
- package/dist/time/TimeLinkDefaultHandler.d.ts +7 -0
- package/dist/time/TimeLinkDefaultHandler.js +32 -11
- package/dist/time/TimeLinkDefaultHandler.test.d.ts +1 -0
- package/dist/time/TimeLinkDefaultHandler.test.js +43 -0
- package/dist/time/html/TimeElementFactory.js +4 -1
- package/dist/time/html/TimeRenderer.d.ts +4 -0
- package/dist/time/html/TimeReplacer.d.ts +9 -0
- package/dist/time/html/TimeReplacer.js +18 -1
- package/dist/time/html/TimeReplacer.test.js +19 -0
- package/dist/time/text/RelativeTimeTextBuilder.js +6 -4
- package/dist/time/text/RelativeTimeTextBuilder.test.js +18 -0
- package/dist/time/text/tmp.test.d.ts +1 -0
- package/dist/time/text/tmp.test.js +27 -0
- package/package.json +3 -3
|
@@ -24,6 +24,15 @@ import { PlaceReplacerFactory } from "./place/PlaceReplacerFactory.js";
|
|
|
24
24
|
import { IndexedReplacerFactory } from "./index/IndexedReplacerFactory.js";
|
|
25
25
|
import { UnitReplaceCommand } from "./value/UnitReplaceCommand.js";
|
|
26
26
|
import { cmsTestUtil } from "./test/CMSTestUtil.js";
|
|
27
|
+
/**
|
|
28
|
+
* Chronological order: a period's page comes before the pages of its sub-periods ("1960" before "April 1960"),
|
|
29
|
+
* which a plain sort does not do since "04/index.html" < "index.html".
|
|
30
|
+
*/
|
|
31
|
+
function periodFirst(a, b) {
|
|
32
|
+
const dirA = a.substring(0, a.lastIndexOf("/") + 1);
|
|
33
|
+
const dirB = b.substring(0, b.lastIndexOf("/") + 1);
|
|
34
|
+
return dirA < dirB ? -1 : dirA > dirB ? 1 : 0;
|
|
35
|
+
}
|
|
27
36
|
export async function getTimeFiles() {
|
|
28
37
|
const minusYearFiles = await glob(cmsTestUtil.filePath("time/-?/?/?/?/index.html"));
|
|
29
38
|
const year1Files = await glob(cmsTestUtil.filePath("time/?/index.html"));
|
|
@@ -32,7 +41,7 @@ export async function getTimeFiles() {
|
|
|
32
41
|
const year4Files = await glob(cmsTestUtil.filePath("time/?/?/?/?/index.html"));
|
|
33
42
|
const monthFiles = await glob(cmsTestUtil.filePath("time/?/?/?/?/??/index.html"));
|
|
34
43
|
const dayFiles = await glob(cmsTestUtil.filePath("time/?/?/?/?/??/??/index.html"));
|
|
35
|
-
return year1Files.concat(year2Files).concat(year3Files).concat(year4Files).concat(minusYearFiles).concat(monthFiles).concat(dayFiles).sort();
|
|
44
|
+
return year1Files.concat(year2Files).concat(year3Files).concat(year4Files).concat(minusYearFiles).concat(monthFiles).concat(dayFiles).sort(periodFirst);
|
|
36
45
|
}
|
|
37
46
|
describe("Build", () => {
|
|
38
47
|
console.time("ssg");
|
|
@@ -27,6 +27,11 @@ export type RR0TimeMessages = {
|
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
29
|
fromTo(startReplacement: string, endReplacement: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* The word that, written right before an interval, asks for it to be rendered as `betweenAnd` ("between", "entre").
|
|
32
|
+
*/
|
|
33
|
+
between: RegExp;
|
|
34
|
+
betweenAnd(startReplacement: string, endReplacement: string): string;
|
|
30
35
|
on(approximate: boolean): string;
|
|
31
36
|
in(approximate: boolean): string;
|
|
32
37
|
starting(approximate: boolean): string;
|
|
@@ -30,5 +30,7 @@ export declare class RR0TimeMessages_en implements RR0TimeMessages {
|
|
|
30
30
|
on: (approximate: boolean) => string;
|
|
31
31
|
in: (approximate: boolean) => string;
|
|
32
32
|
fromTo: (startReplacement: string, endReplacement: string) => string;
|
|
33
|
+
between: RegExp;
|
|
34
|
+
betweenAnd: (startReplacement: string, endReplacement: string) => string;
|
|
33
35
|
starting: (approximate: boolean) => string;
|
|
34
36
|
}
|
|
@@ -33,6 +33,8 @@ export class RR0TimeMessages_en {
|
|
|
33
33
|
};
|
|
34
34
|
this.in = (approximate) => (approximate ? "around " : "");
|
|
35
35
|
this.fromTo = (startReplacement, endReplacement) => startReplacement + " to " + endReplacement;
|
|
36
|
+
this.between = /\bbetween\s*$/i;
|
|
37
|
+
this.betweenAnd = (startReplacement, endReplacement) => startReplacement + " and " + endReplacement;
|
|
36
38
|
this.starting = (approximate) => "starting " + (approximate ? "" : "the ");
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -30,5 +30,7 @@ export declare class RR0TimeMessages_fr implements RR0TimeMessages {
|
|
|
30
30
|
on: (approximate: boolean) => string;
|
|
31
31
|
in: (approximate: boolean) => string;
|
|
32
32
|
fromTo: (startReplacement: string, endReplacement: string) => string;
|
|
33
|
+
between: RegExp;
|
|
34
|
+
betweenAnd: (startReplacement: string, endReplacement: string) => string;
|
|
33
35
|
starting: (approximate: boolean) => string;
|
|
34
36
|
}
|
|
@@ -33,6 +33,8 @@ export class RR0TimeMessages_fr {
|
|
|
33
33
|
};
|
|
34
34
|
this.in = (approximate) => (approximate ? "vers " : "en ");
|
|
35
35
|
this.fromTo = (startReplacement, endReplacement) => `${startReplacement} à ${endReplacement}`;
|
|
36
|
+
this.between = /\bentre\s*$/i;
|
|
37
|
+
this.betweenAnd = (startReplacement, endReplacement) => `${startReplacement} et ${endReplacement}`;
|
|
36
38
|
this.starting = (approximate) => "à partir " + (approximate ? "de " : "du ");
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -9,9 +9,16 @@ export declare class TimeLinkDefaultHandler implements LinkHandler<HtmlRR0Contex
|
|
|
9
9
|
protected urlBuilder: TimeUrlBuilder;
|
|
10
10
|
protected timeTextBuilder: TimeTextBuilder;
|
|
11
11
|
constructor(service: TimeService, urlBuilder: TimeUrlBuilder, timeTextBuilder: TimeTextBuilder);
|
|
12
|
+
/**
|
|
13
|
+
* The period containing the page (the year of a month, the month of a day...), which lists it.
|
|
14
|
+
*/
|
|
12
15
|
contents(context: HtmlRR0Context): Link | undefined;
|
|
13
16
|
next(context: HtmlRR0Context): Link | undefined;
|
|
14
17
|
prev(context: HtmlRR0Context): Link | undefined;
|
|
15
18
|
start(context: HtmlRR0Context): Link | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* A decade, century or millennium cannot be told from a year by its path's date, so its page's own title is used.
|
|
21
|
+
*/
|
|
22
|
+
protected titleOf(context: HtmlRR0Context, fileName: string): string | undefined;
|
|
16
23
|
protected isTimeFile(fileName: string): boolean;
|
|
17
24
|
}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { LinkType } from "ssg-api";
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
3
|
export class TimeLinkDefaultHandler {
|
|
3
4
|
constructor(service, urlBuilder, timeTextBuilder) {
|
|
4
5
|
this.service = service;
|
|
5
6
|
this.urlBuilder = urlBuilder;
|
|
6
7
|
this.timeTextBuilder = timeTextBuilder;
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* The period containing the page (the year of a month, the month of a day...), which lists it.
|
|
11
|
+
*/
|
|
8
12
|
contents(context) {
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const fileName = context.file.name;
|
|
14
|
+
if (this.isTimeFile(fileName)) {
|
|
15
|
+
const pageDir = fileName.substring(0, fileName.lastIndexOf("/"));
|
|
16
|
+
const parentDir = pageDir.substring(0, pageDir.lastIndexOf("/"));
|
|
17
|
+
const contentUrl = this.urlBuilder.matchExistingTimeFile(parentDir);
|
|
18
|
+
if (contentUrl) {
|
|
19
|
+
const contentFile = contentUrl + "/index.html";
|
|
20
|
+
const text = this.titleOf(context, contentFile);
|
|
15
21
|
if (text) {
|
|
16
|
-
return { type: LinkType.
|
|
22
|
+
return { type: LinkType.contents, text, url: "/" + contentFile };
|
|
17
23
|
}
|
|
18
24
|
}
|
|
19
25
|
}
|
|
@@ -26,7 +32,7 @@ export class TimeLinkDefaultHandler {
|
|
|
26
32
|
if (pos >= 0) {
|
|
27
33
|
const nextFile = service.files[pos + 1];
|
|
28
34
|
if (nextFile) {
|
|
29
|
-
const text =
|
|
35
|
+
const text = this.titleOf(context, nextFile);
|
|
30
36
|
return { type: LinkType.next, text, url: "/" + nextFile };
|
|
31
37
|
}
|
|
32
38
|
}
|
|
@@ -40,7 +46,7 @@ export class TimeLinkDefaultHandler {
|
|
|
40
46
|
if (pos >= 0) {
|
|
41
47
|
const prevFile = service.files[pos - 1];
|
|
42
48
|
if (prevFile) {
|
|
43
|
-
const text =
|
|
49
|
+
const text = this.titleOf(context, prevFile);
|
|
44
50
|
if (text) {
|
|
45
51
|
return { type: LinkType.prev, text, url: "/" + prevFile };
|
|
46
52
|
}
|
|
@@ -51,13 +57,28 @@ export class TimeLinkDefaultHandler {
|
|
|
51
57
|
start(context) {
|
|
52
58
|
if (this.isTimeFile(context.file.name)) {
|
|
53
59
|
return {
|
|
54
|
-
type: LinkType.
|
|
60
|
+
type: LinkType.start,
|
|
55
61
|
text: "Chronologie",
|
|
56
62
|
url: "/time/"
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* A decade, century or millennium cannot be told from a year by its path's date, so its page's own title is used.
|
|
68
|
+
*/
|
|
69
|
+
titleOf(context, fileName) {
|
|
70
|
+
var _a;
|
|
71
|
+
const rootDir = this.urlBuilder.options.rootDir;
|
|
72
|
+
const digits = fileName.substring(rootDir.length + 1).split("/").length - 1;
|
|
73
|
+
if (digits < 4 && existsSync(fileName)) {
|
|
74
|
+
const title = (_a = /<title>([^<]*)<\/title>/.exec(readFileSync(fileName, "utf-8"))) === null || _a === void 0 ? void 0 : _a[1].trim();
|
|
75
|
+
if (title) {
|
|
76
|
+
return title;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return this.service.titleFromFile(context, fileName, this.timeTextBuilder);
|
|
80
|
+
}
|
|
60
81
|
isTimeFile(fileName) {
|
|
61
|
-
return fileName.startsWith("
|
|
82
|
+
return fileName.startsWith(this.urlBuilder.options.rootDir + "/");
|
|
62
83
|
}
|
|
63
84
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { LinkType } from "ssg-api";
|
|
3
|
+
import { cmsTestUtil } from "../test/CMSTestUtil.js";
|
|
4
|
+
import { TimeLinkDefaultHandler } from "./TimeLinkDefaultHandler.js";
|
|
5
|
+
describe("TimeLinkDefaultHandler", () => {
|
|
6
|
+
const rootDir = cmsTestUtil.filePath("time");
|
|
7
|
+
const files = [
|
|
8
|
+
"1/9/5/4/10/index.html",
|
|
9
|
+
"1/9/7/index.html",
|
|
10
|
+
"1/9/7/0/index.html",
|
|
11
|
+
"1/9/7/0/03/index.html",
|
|
12
|
+
"1/9/7/0/11/index.html",
|
|
13
|
+
"1/9/9/0/index.html"
|
|
14
|
+
].map(file => rootDir + "/" + file);
|
|
15
|
+
const url = (file) => "/" + rootDir + "/" + file;
|
|
16
|
+
const timeUtil = cmsTestUtil.time;
|
|
17
|
+
const service = timeUtil.getService({ rootDir, files });
|
|
18
|
+
const handler = new TimeLinkDefaultHandler(service, timeUtil.urlBuilder, timeUtil.timeTextBuilder);
|
|
19
|
+
const context = cmsTestUtil.newHtmlContext("time/1/9/7/0/11/index.html", "<html><body></body></html>");
|
|
20
|
+
test("start is the chronology", () => {
|
|
21
|
+
expect(handler.start(context)).toEqual({ type: LinkType.start, text: "Chronologie", url: "/time/" });
|
|
22
|
+
});
|
|
23
|
+
test("contents is the period containing the page, not the previous page", () => {
|
|
24
|
+
const contents = handler.contents(context);
|
|
25
|
+
expect(contents.type).toBe(LinkType.contents);
|
|
26
|
+
expect(contents.url).toBe(url("1/9/7/0/index.html"));
|
|
27
|
+
expect(contents.text).toBe("1970");
|
|
28
|
+
});
|
|
29
|
+
test("prev and next follow the files order", () => {
|
|
30
|
+
expect(handler.prev(context).url).toBe(url("1/9/7/0/03/index.html"));
|
|
31
|
+
expect(handler.next(context).url).toBe(url("1/9/9/0/index.html"));
|
|
32
|
+
});
|
|
33
|
+
test("a decade is titled by its page, not as the year its path starts with", () => {
|
|
34
|
+
const yearContext = cmsTestUtil.newHtmlContext("time/1/9/7/0/index.html", "<html><body></body></html>");
|
|
35
|
+
const contents = handler.contents(yearContext);
|
|
36
|
+
expect(contents.url).toBe(url("1/9/7/index.html"));
|
|
37
|
+
expect(contents.text).toBe("1970s");
|
|
38
|
+
});
|
|
39
|
+
test("a page below a period without page is listed by the nearest one", () => {
|
|
40
|
+
const dayContext = cmsTestUtil.newHtmlContext("time/1/9/5/4/10/01/index.html", "<html><body></body></html>");
|
|
41
|
+
expect(handler.contents(dayContext).url).toBe(url("1/9/5/4/10/index.html"));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -37,7 +37,10 @@ export class TimeElementFactory {
|
|
|
37
37
|
if (endReplacement && endReplacement.outerHTML !== startReplacement.outerHTML) {
|
|
38
38
|
replacement = fromContext.file.document.createElement("span");
|
|
39
39
|
replacement.className = "time-interval";
|
|
40
|
-
|
|
40
|
+
const timeMessages = fromContext.messages.context.time;
|
|
41
|
+
replacement.innerHTML = options.between
|
|
42
|
+
? timeMessages.betweenAnd(startReplacement.outerHTML, endReplacement.outerHTML)
|
|
43
|
+
: timeMessages.fromTo(startReplacement.outerHTML, endReplacement.outerHTML);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
return replacement;
|
|
@@ -5,6 +5,10 @@ import { TimeUrlBuilder } from "../TimeUrlBuilder.js";
|
|
|
5
5
|
export interface TimeRenderOptions {
|
|
6
6
|
url: boolean;
|
|
7
7
|
contentOnly: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* The text says "between" right before this interval, so it reads "between X and Y" rather than "between X to Y".
|
|
10
|
+
*/
|
|
11
|
+
between?: boolean;
|
|
8
12
|
}
|
|
9
13
|
export declare class TimeRenderer {
|
|
10
14
|
readonly urlBuilder: TimeUrlBuilder;
|
|
@@ -6,7 +6,16 @@ import { TimeElementFactory } from "./TimeElementFactory.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class TimeReplacer implements DomReplacement<HtmlRR0Context, HTMLTimeElement> {
|
|
8
8
|
readonly factory: TimeElementFactory;
|
|
9
|
+
/**
|
|
10
|
+
* Elements that don't break a sentence, so that the text before them is the text before the <time> they contain
|
|
11
|
+
* ("between <strong><time>1989/1994</time></strong>").
|
|
12
|
+
*/
|
|
13
|
+
static readonly inlineTags: string[];
|
|
9
14
|
constructor(factory: TimeElementFactory);
|
|
10
15
|
static resolvedTime(context: HtmlRR0Context, dateTime: string): HTMLTimeElement;
|
|
16
|
+
/**
|
|
17
|
+
* @return The text that precedes an element in its sentence.
|
|
18
|
+
*/
|
|
19
|
+
static precedingText(el: Element): string;
|
|
11
20
|
replacement(context: HtmlRR0Context, origEl: HTMLTimeElement): Promise<HTMLElement>;
|
|
12
21
|
}
|
|
@@ -11,6 +11,17 @@ export class TimeReplacer {
|
|
|
11
11
|
replacement.dateTime = dateTime;
|
|
12
12
|
return replacement;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* @return The text that precedes an element in its sentence.
|
|
16
|
+
*/
|
|
17
|
+
static precedingText(el) {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
let node = el;
|
|
20
|
+
while (!node.previousSibling && node.parentElement && TimeReplacer.inlineTags.includes(node.parentElement.tagName)) {
|
|
21
|
+
node = node.parentElement;
|
|
22
|
+
}
|
|
23
|
+
return (_b = (_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.textContent) !== null && _b !== void 0 ? _b : "";
|
|
24
|
+
}
|
|
14
25
|
async replacement(context, origEl) {
|
|
15
26
|
let replacement;
|
|
16
27
|
if (origEl.dateTime) { // Already done?
|
|
@@ -20,7 +31,8 @@ export class TimeReplacer {
|
|
|
20
31
|
const previousContext = origEl.dataset.context === "none" ? undefined : context.clone();
|
|
21
32
|
const timeStr = origEl.textContent;
|
|
22
33
|
const valid = context.time.updateFromStr(timeStr);
|
|
23
|
-
|
|
34
|
+
const between = context.messages.context.time.between.test(TimeReplacer.precedingText(origEl));
|
|
35
|
+
replacement = valid && this.factory.create(context, previousContext, { url: true, contentOnly: true, between });
|
|
24
36
|
if (!replacement) {
|
|
25
37
|
replacement = origEl;
|
|
26
38
|
// replacement.setAttribute("datetime", context.time.toString())
|
|
@@ -30,3 +42,8 @@ export class TimeReplacer {
|
|
|
30
42
|
return replacement;
|
|
31
43
|
}
|
|
32
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Elements that don't break a sentence, so that the text before them is the text before the <time> they contain
|
|
47
|
+
* ("between <strong><time>1989/1994</time></strong>").
|
|
48
|
+
*/
|
|
49
|
+
TimeReplacer.inlineTags = ["STRONG", "EM", "B", "I", "U", "SPAN", "A"];
|
|
@@ -83,6 +83,25 @@ describe("TimeReplacer", async () => {
|
|
|
83
83
|
const replaced = await replacer.replacement(context, interval);
|
|
84
84
|
expect(replaced.outerHTML).toBe(`<span class="time-interval"><span class="time-resolved"><time datetime="1951">1951</time></span> à <span class="time-resolved"><time datetime="1955">1955</time></span></span>`);
|
|
85
85
|
});
|
|
86
|
+
describe("renders an interval after \"between\" as \"between X and Y\"", () => {
|
|
87
|
+
async function renderIn(locale, html) {
|
|
88
|
+
const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "", locale);
|
|
89
|
+
const p = context.file.document.createElement("p");
|
|
90
|
+
p.innerHTML = html;
|
|
91
|
+
const replaced = await replacer.replacement(context, p.querySelector("time"));
|
|
92
|
+
return replaced.textContent;
|
|
93
|
+
}
|
|
94
|
+
test("in French", async () => {
|
|
95
|
+
expect(await renderIn("fr", "entre <time>1951/1955</time>")).toBe("1951 et 1955");
|
|
96
|
+
});
|
|
97
|
+
test("in English, through an inline element", async () => {
|
|
98
|
+
expect(await renderIn("en", "anywhere between <strong><time>1951/1955</time></strong>")).toBe("1951 and 1955");
|
|
99
|
+
});
|
|
100
|
+
test("but not without the word", async () => {
|
|
101
|
+
expect(await renderIn("fr", "de <time>1951/1955</time>")).toBe("1951 à 1955");
|
|
102
|
+
expect(await renderIn("en", "from <time>1951/1955</time>")).toBe("1951 to 1955");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
86
105
|
test("parses unsupported", async () => {
|
|
87
106
|
const interval = "moi";
|
|
88
107
|
const context = cmsTestUtil.time.newHtmlContext("1/9/9/0/08/index.html", "");
|
|
@@ -64,8 +64,10 @@ export class RelativeTimeTextBuilder {
|
|
|
64
64
|
if (deltaContext.time.isDefined()) {
|
|
65
65
|
newContext = deltaContext;
|
|
66
66
|
}
|
|
67
|
+
// A relative wording ("the day after", "the month after"...) only makes sense against a previous time
|
|
68
|
+
// that is as precise: a month page (1947-07) is not "the day before" its first entry (1947-07-01).
|
|
67
69
|
const messages = newContext.messages.context.time.relative;
|
|
68
|
-
if (dayOfMonthDelta && sameYear && sameMonth) {
|
|
70
|
+
if (dayOfMonthDelta && sameYear && sameMonth && !noDay) {
|
|
69
71
|
switch (dayOfMonthDelta) {
|
|
70
72
|
case -1:
|
|
71
73
|
text = messages.day.before;
|
|
@@ -76,7 +78,7 @@ export class RelativeTimeTextBuilder {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
else {
|
|
79
|
-
if (!dayOfMonth && sameYear) {
|
|
81
|
+
if (!dayOfMonth && sameYear && !noMonth) {
|
|
80
82
|
switch (deltaDurationMonth) {
|
|
81
83
|
case -1:
|
|
82
84
|
text = messages.month.before;
|
|
@@ -86,7 +88,7 @@ export class RelativeTimeTextBuilder {
|
|
|
86
88
|
break;
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
|
-
if (!month) {
|
|
91
|
+
if (!month && !noYear) {
|
|
90
92
|
switch (yearDelta) {
|
|
91
93
|
case -1:
|
|
92
94
|
text = messages.year.before;
|
|
@@ -96,7 +98,7 @@ export class RelativeTimeTextBuilder {
|
|
|
96
98
|
break;
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
|
-
if (!minutes) {
|
|
101
|
+
if (!minutes && previousTime.getHour() !== undefined) {
|
|
100
102
|
switch (hourDelta) {
|
|
101
103
|
case -1:
|
|
102
104
|
text = messages.hour.before;
|
|
@@ -90,6 +90,24 @@ describe("RelativeTimeTextBuilder", () => {
|
|
|
90
90
|
expect(relativeTimeTextBuilder.build(previousContext, context)).toBe("the day after");
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
|
+
test("first day of a month page is not the day after the month", () => {
|
|
94
|
+
const monthContext = cmsTestUtil.time.newHtmlContext("1/9/4/7/07", "");
|
|
95
|
+
const firstDay = monthContext.clone();
|
|
96
|
+
firstDay.time.updateFromStr("1947-07-01");
|
|
97
|
+
expect(relativeTimeTextBuilder.build(monthContext, firstDay)).toBe("mardi 1");
|
|
98
|
+
const secondDay = firstDay.clone();
|
|
99
|
+
secondDay.time.updateFromStr("1947-07-02");
|
|
100
|
+
expect(relativeTimeTextBuilder.build(firstDay, secondDay)).toBe("le lendemain");
|
|
101
|
+
});
|
|
102
|
+
test("first month of a year page is not the month after the year", () => {
|
|
103
|
+
const yearContext = cmsTestUtil.time.newHtmlContext("1/9/4/7", "");
|
|
104
|
+
const january = yearContext.clone();
|
|
105
|
+
january.time.updateFromStr("1947-01");
|
|
106
|
+
expect(relativeTimeTextBuilder.build(yearContext, january)).toBe("janvier");
|
|
107
|
+
const february = january.clone();
|
|
108
|
+
february.time.updateFromStr("1947-02");
|
|
109
|
+
expect(relativeTimeTextBuilder.build(january, february)).toBe("le mois suivant");
|
|
110
|
+
});
|
|
93
111
|
describe("hour", () => {
|
|
94
112
|
test("next", () => {
|
|
95
113
|
{
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RelativeTimeTextBuilder } from "./RelativeTimeTextBuilder.js";
|
|
2
|
+
import { cmsTestUtil } from "../../test/CMSTestUtil.js";
|
|
3
|
+
import { test } from "vitest";
|
|
4
|
+
import { TimeTextBuilder } from "./TimeTextBuilder.js";
|
|
5
|
+
import { Level0Duration } from "@rr0/time";
|
|
6
|
+
test("x", () => {
|
|
7
|
+
const b = new RelativeTimeTextBuilder(new TimeTextBuilder(cmsTestUtil.intlOptions));
|
|
8
|
+
const prev = cmsTestUtil.time.newHtmlContext("1/9/4/7/07", "");
|
|
9
|
+
console.log("prev", prev.time.toString(), prev.time.getDayOfMonth());
|
|
10
|
+
const ctx = prev.clone();
|
|
11
|
+
ctx.time.updateFromStr("1947-07-01");
|
|
12
|
+
const ms = ctx.time.date.compare(prev.time.date);
|
|
13
|
+
console.log("ctx", ctx.time.toString(), ms, JSON.stringify(Level0Duration.toSpec(ms)));
|
|
14
|
+
console.log(b.build(prev, ctx));
|
|
15
|
+
const c2 = ctx.clone();
|
|
16
|
+
c2.time.updateFromStr("1947-07-02");
|
|
17
|
+
const ms2 = c2.time.date.compare(prev.time.date);
|
|
18
|
+
console.log(ms2, JSON.stringify(Level0Duration.toSpec(ms2)), b.build(prev, c2));
|
|
19
|
+
});
|
|
20
|
+
test("y", () => {
|
|
21
|
+
const b = new RelativeTimeTextBuilder(new TimeTextBuilder(cmsTestUtil.intlOptions));
|
|
22
|
+
const prev = cmsTestUtil.time.newHtmlContext("1/9/4/7", "");
|
|
23
|
+
const ctx = prev.clone();
|
|
24
|
+
ctx.time.updateFromStr("1947-01");
|
|
25
|
+
const ms = ctx.time.date.compare(prev.time.date);
|
|
26
|
+
console.log("Y", prev.time.toString(), ctx.time.toString(), ms, JSON.stringify(Level0Duration.toSpec(ms)), b.build(prev, ctx));
|
|
27
|
+
});
|
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.69",
|
|
6
6
|
"description": "RR0 Content Management System (CMS)",
|
|
7
7
|
"exports": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"test-ci": "vitest run"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@javarome/fileutil": "^0.3.
|
|
32
|
+
"@javarome/fileutil": "^0.3.9",
|
|
33
33
|
"@rr0/common": "^1.1.5",
|
|
34
34
|
"@rr0/data": "^0.3.43",
|
|
35
35
|
"@rr0/lang": "^0.1.13",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"image-size": "^2.0.2",
|
|
42
42
|
"jsdom": "^27.4.0",
|
|
43
43
|
"selenium-webdriver": "^4.39.0",
|
|
44
|
-
"ssg-api": "^1.18.
|
|
44
|
+
"ssg-api": "^1.18.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/jsdom": "^21.1.7",
|