mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-07-22 19:48:54 +00:00
Serializer should preserve created and modified fields
This commit is contained in:
@@ -163,10 +163,23 @@ describe("markdown YAML frontmatter", function() {
|
||||
// Malformed YAML still parses something; we just ensure body is set
|
||||
expect(result[0].text).toBeDefined();
|
||||
});
|
||||
it("ignores created and modified per collision policy", function() {
|
||||
var result = ds("---\ntitle: T\ncreated: 2026-01-01\nmodified: 2026-02-02\n---\n\nb",{});
|
||||
it("parses ISO-8601 created and modified into TW native format", function() {
|
||||
var result = ds("---\ntitle: T\ncreated: 2025-01-02T03:04:05.006Z\nmodified: 2026-02-03T04:05:06Z\n---\n\nb",{});
|
||||
expect(result[0].created).toBe("20250102030405006");
|
||||
expect(result[0].modified).toBe("20260203040506000");
|
||||
});
|
||||
it("accepts a bare YYYY-MM-DD date for created/modified", function() {
|
||||
var result = ds("---\ntitle: T\ncreated: 2025-03-15\n---\n\nb",{});
|
||||
expect(result[0].created).toBe("20250315000000000");
|
||||
});
|
||||
it("passes through TW native timestamps for created/modified", function() {
|
||||
var result = ds("---\ntitle: T\ncreated: \"20250101000000000\"\nmodified: \"20260101000000\"\n---\n\nb",{});
|
||||
expect(result[0].created).toBe("20250101000000000");
|
||||
expect(result[0].modified).toBe("20260101000000000");
|
||||
});
|
||||
it("drops unparseable created/modified values", function() {
|
||||
var result = ds("---\ntitle: T\ncreated: not-a-date\n---\n\nb",{});
|
||||
expect(result[0].created).toBeUndefined();
|
||||
expect(result[0].modified).toBeUndefined();
|
||||
});
|
||||
it("merges existing tags with frontmatter tags", function() {
|
||||
var result = ds("---\ntags: [b, c]\n---\n\nbody",{tags: "a"});
|
||||
@@ -204,22 +217,34 @@ describe("markdown YAML frontmatter", function() {
|
||||
expect(out).toContain("- multi word tag");
|
||||
expect(out).toContain("- simple");
|
||||
});
|
||||
it("skips text, created, modified, bag, revision", function() {
|
||||
it("skips text, bag, revision", function() {
|
||||
var t = new $tw.Tiddler({
|
||||
title: "X",
|
||||
text: "body",
|
||||
created: "20260101000000000",
|
||||
modified: "20260101000000000",
|
||||
bag: "default",
|
||||
revision: "1"
|
||||
});
|
||||
var out = ser(t);
|
||||
expect(out).not.toContain("created:");
|
||||
expect(out).not.toContain("modified:");
|
||||
expect(out).not.toContain("bag:");
|
||||
expect(out).not.toContain("revision:");
|
||||
expect(out).not.toContain("text:");
|
||||
});
|
||||
it("emits created and modified as ISO-8601 strings", function() {
|
||||
var t = new $tw.Tiddler({
|
||||
title: "X",
|
||||
text: "b",
|
||||
created: "20250102030405006",
|
||||
modified: "20260203040506000"
|
||||
});
|
||||
var out = ser(t);
|
||||
expect(out).toContain('created: "2025-01-02T03:04:05.006Z"');
|
||||
expect(out).toContain('modified: "2026-02-03T04:05:06.000Z"');
|
||||
});
|
||||
it("drops unparseable created/modified values", function() {
|
||||
var t = new $tw.Tiddler({title: "X", text: "b", created: "garbage"});
|
||||
var out = ser(t);
|
||||
expect(out).not.toContain("created:");
|
||||
});
|
||||
it("skips type when it equals text/x-markdown", function() {
|
||||
var t = new $tw.Tiddler({title: "X", type: "text/x-markdown", text: "b"});
|
||||
expect(ser(t)).not.toContain("type:");
|
||||
@@ -262,6 +287,15 @@ describe("markdown YAML frontmatter", function() {
|
||||
expect(reparsed.rating).toBe("7");
|
||||
expect(reparsed.text).toBe("This is the body.");
|
||||
});
|
||||
it("preserves created and modified across deserialize → serialize", function() {
|
||||
var input = "---\ntitle: T\ncreated: 2025-01-02T03:04:05.006Z\nmodified: 2026-02-03T04:05:06.007Z\n---\n\nbody";
|
||||
var fields = ds(input,{})[0];
|
||||
var t = new $tw.Tiddler(fields);
|
||||
var out = ser(t);
|
||||
var reparsed = ds(out,{})[0];
|
||||
expect(reparsed.created).toBe("20250102030405006");
|
||||
expect(reparsed.modified).toBe("20260203040506007");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -11,6 +11,11 @@ field with a registered `stringify` method) are converted to TiddlyWiki
|
||||
bracketed lists. Non-string, non-array values are stored as their JSON
|
||||
representation.
|
||||
|
||||
`created` and `modified` in the frontmatter are accepted in either
|
||||
TiddlyWiki's native `YYYYMMDDHHMMSSmmm` UTC format or any ISO-8601
|
||||
string that `Date()` can parse; both are normalised to TW's native
|
||||
format. Values that cannot be parsed are dropped.
|
||||
|
||||
\*/
|
||||
"use strict";
|
||||
|
||||
@@ -60,7 +65,10 @@ function deserialize(text,fields) {
|
||||
value = frontmatter[key];
|
||||
// Apply field collision policy
|
||||
if(key === "created" || key === "modified") {
|
||||
// Defer to TiddlyWiki's own timestamps; ignore YAML values
|
||||
var normalised = normaliseDate(value);
|
||||
if(normalised !== null) {
|
||||
result[key] = normalised;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(key === "tags" && result[key]) {
|
||||
@@ -113,6 +121,35 @@ function fieldValueToString(key,value) {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
/*
|
||||
Normalise a frontmatter date value to TiddlyWiki's YYYYMMDDHHMMSSmmm UTC
|
||||
format. Accepts TW native strings (14 or 17 digits, optional leading "-"
|
||||
for negative years) and anything `Date()` can parse (ISO 8601, RFC 2822,
|
||||
Date objects). Returns null if the value cannot be interpreted as a date.
|
||||
*/
|
||||
function normaliseDate(value) {
|
||||
if(value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
if(typeof value === "string") {
|
||||
if(/^-?\d{14}$/.test(value)) {
|
||||
return value + "000";
|
||||
}
|
||||
if(/^-?\d{17}$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
var d = new Date(value);
|
||||
if(!isNaN(d.getTime())) {
|
||||
return $tw.utils.stringifyDate(d);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if(value instanceof Date && !isNaN(value.getTime())) {
|
||||
return $tw.utils.stringifyDate(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
Merge incoming tag value with existing tags string.
|
||||
The incoming value may be a string (TW bracketed list) or an array (from YAML).
|
||||
|
||||
@@ -12,7 +12,8 @@ Markdown file body whose first lines are a YAML frontmatter block
|
||||
Field handling:
|
||||
- `title` is always emitted (frontmatter wins over filename when reloaded).
|
||||
- `text` is the body; not emitted in the frontmatter.
|
||||
- `created`, `modified` are skipped (TiddlyWiki manages timestamps via filesystem).
|
||||
- `created`, `modified` are emitted as ISO-8601 strings (symmetric with
|
||||
the deserializer, which accepts either ISO-8601 or TW's native format).
|
||||
- `type` is skipped when it equals `text/x-markdown` (the default for `.md` files).
|
||||
- `bag`, `revision` are skipped (sync metadata, not authored content).
|
||||
- List fields (those with a registered `stringify` method) are emitted as YAML arrays.
|
||||
@@ -26,8 +27,6 @@ var yaml = require("$:/plugins/tiddlywiki/markdown/yaml.js");
|
||||
// Field names to skip when emitting frontmatter
|
||||
var SKIP_FIELDS = {
|
||||
text: true,
|
||||
created: true,
|
||||
modified: true,
|
||||
bag: true,
|
||||
revision: true
|
||||
};
|
||||
@@ -50,6 +49,13 @@ function serialize(tiddler) {
|
||||
if(name === "type" && value === "text/x-markdown") {
|
||||
return;
|
||||
}
|
||||
if(name === "created" || name === "modified") {
|
||||
var iso = toIsoDate(value);
|
||||
if(iso) {
|
||||
frontmatter[name] = iso;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// List fields → YAML arrays
|
||||
if($tw.Tiddler.fieldModules[name] && $tw.Tiddler.fieldModules[name].stringify) {
|
||||
var items;
|
||||
@@ -74,6 +80,27 @@ function serialize(tiddler) {
|
||||
return "---\n" + yaml.dump(frontmatter) + "\n---\n\n" + body;
|
||||
}
|
||||
|
||||
/*
|
||||
Convert a TiddlyWiki date field value to an ISO-8601 string. Accepts a
|
||||
native `YYYYMMDDHHMMSSmmm` string or a Date. Returns null if the value
|
||||
cannot be parsed.
|
||||
*/
|
||||
function toIsoDate(value) {
|
||||
if(!value) {
|
||||
return null;
|
||||
}
|
||||
var d;
|
||||
if($tw.utils.isDate(value)) {
|
||||
d = value;
|
||||
} else {
|
||||
d = $tw.utils.parseDate(String(value));
|
||||
}
|
||||
if(d && !isNaN(d.getTime())) {
|
||||
return d.toISOString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Register under both types — text/markdown is what the "New Markdown" button
|
||||
// sets; text/x-markdown is what the deserializer uses after content-type
|
||||
// resolution for .md files loaded from disk.
|
||||
|
||||
Reference in New Issue
Block a user