mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-07-11 06:12:45 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ec36293df |
@@ -0,0 +1,69 @@
|
||||
# Repository Contribution Guidelines & Automation
|
||||
|
||||
This document establishes the single source of truth for repository contribution guidelines, optimized for AI context consumption, alongside a TiddlyWiki-based automation strategy to prevent source-of-truth drift.
|
||||
|
||||
---
|
||||
|
||||
## 📄 The High-Density Ruleset
|
||||
*Copy the content below into a single master tiddler titled `ProjectContributionRules` with the type `text/x-markdown`.*
|
||||
|
||||
```markdown
|
||||
# CONTRIBUTING
|
||||
|
||||
## AI & Comment Rules
|
||||
* Banned: Conversational filler, verbose explanations of standard APIs, or teaching basic programming logic.
|
||||
* Required: Prune all AI-generated comment blocks down to a single sentence maximum.
|
||||
* Focus: Comments must only state the "why" behind non-obvious engineering decisions, never the "how".
|
||||
* PR Descriptions: Write concise, bulleted summaries of what changed. Do not use AI-generated marketing/polite filler text.
|
||||
|
||||
### Code Comment Examples
|
||||
* BAD: // Use behavior:"instant" so that this animation's per-frame position updates are applied immediately and are NOT re-animated by a CSS `scroll-behavior: smooth` on the scrolling element (which would make the page lag behind and stutter).
|
||||
* GOOD: // Use 'instant' to prevent conflicts with CSS scroll-behavior: smooth.
|
||||
|
||||
## Workflow Rules
|
||||
* Branching: Base all feature branches on `main`. Use `feat/` or `fix/` prefixes.
|
||||
* Scope: Keep pull requests limited to a single logical change. Massive, multi-feature PRs will be rejected.
|
||||
* Testing: Run local tests and formatting checks before pushing.
|
||||
* Merging: All commits are squashed on merge.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ TiddlyWiki Build Integration
|
||||
To ensure this file is distributed automatically to all required locations without duplication, use TiddlyWiki's native command-line rendering tool.
|
||||
|
||||
### 1. Update `tiddlywiki.info`
|
||||
Add a dedicated `repo-configs` target to your project's `tiddlywiki.info` JSON file. This instructs TiddlyWiki to compile the master tiddler out to both human-facing and AI-facing configuration endpoints:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Project build targets",
|
||||
"plugins": [
|
||||
"tiddlywiki/markdown"
|
||||
],
|
||||
"build": {
|
||||
"repo-configs": [
|
||||
"--rendertiddler", "ProjectContributionRules", "./CONTRIBUTING.md", "text/plain",
|
||||
"--rendertiddler", "ProjectContributionRules", "./.claudecode", "text/plain"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Update `package.json`
|
||||
Integrate the rendering command into your existing Node.js project lifecycle scripts so it triggers automatically during development builds or pre-commit checks:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build:configs": "tiddlywiki --build repo-configs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Why This Works
|
||||
* **Zero Duplication:** Edits are made exclusively within `ProjectContributionRules` inside the wiki workspace.
|
||||
* **Human Visibility:** Browsing GitHub automatically surfaces `CONTRIBUTING.md` to human contributors.
|
||||
* **Claude Code Ready:** When **Claude Code** sweeps the project root, it instantly reads the identical, strict engineering boundaries outputted to `.claudecode`.
|
||||
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
|
||||
title: TiddlyWiki Team
|
||||
tags: Community/Team
|
||||
modified: 20260708095630754
|
||||
created: 20260708095630754
|
||||
leader: @BurningTreeC
|
||||
team:
|
||||
|
||||
The TiddlyDesktop development repository is at https://github.com/TiddlyWiki/TiddlyDesktop
|
||||
@@ -31,16 +31,13 @@ exports.parse = function() {
|
||||
reEnd.lastIndex = this.parser.pos;
|
||||
var match = reEnd.exec(this.parser.source),
|
||||
text,
|
||||
start = this.parser.pos,
|
||||
textEnd;
|
||||
start = this.parser.pos;
|
||||
// Process the text
|
||||
if(match) {
|
||||
text = this.parser.source.substring(this.parser.pos,match.index);
|
||||
textEnd = match.index;
|
||||
this.parser.pos = match.index + match[0].length;
|
||||
} else {
|
||||
text = this.parser.source.substr(this.parser.pos);
|
||||
textEnd = this.parser.sourceLength;
|
||||
this.parser.pos = this.parser.sourceLength;
|
||||
}
|
||||
return [{
|
||||
@@ -50,7 +47,7 @@ exports.parse = function() {
|
||||
type: "text",
|
||||
text: text,
|
||||
start: start,
|
||||
end: textEnd
|
||||
end: this.parser.pos
|
||||
}]
|
||||
}];
|
||||
};
|
||||
|
||||
@@ -32,8 +32,7 @@ exports.parse = function() {
|
||||
this.parser.pos = this.matchRegExp.lastIndex;
|
||||
// Create the link unless it is suppressed
|
||||
if(this.match[0].substr(0,1) === "~") {
|
||||
// Start after the suppressing "~" so the span matches the plain text
|
||||
return [{type: "text", text: this.match[0].substr(1), start: start + 1, end: this.parser.pos}];
|
||||
return [{type: "text", text: this.match[0].substr(1), start: start, end: this.parser.pos}];
|
||||
} else {
|
||||
return [{
|
||||
type: "element",
|
||||
|
||||
@@ -34,7 +34,7 @@ exports.parse = function() {
|
||||
// Parse the filter terminated by a line break
|
||||
var reMatch = /(.*)(?:$|\r?\n)/mg;
|
||||
reMatch.lastIndex = this.parser.pos;
|
||||
var filterStart = this.parser.pos;
|
||||
var filterStart = this.parser.source;
|
||||
var match = reMatch.exec(this.parser.source);
|
||||
this.parser.pos = reMatch.lastIndex;
|
||||
// Parse tree nodes to return
|
||||
|
||||
@@ -27,11 +27,9 @@ Parse the most recent match
|
||||
*/
|
||||
exports.parse = function() {
|
||||
// Get the details of the match
|
||||
var linkText = this.match[0],
|
||||
// Start after the suppressing "~" so the span matches the plain text
|
||||
start = this.parser.pos + 1;
|
||||
var linkText = this.match[0];
|
||||
// Move past the wikilink
|
||||
this.parser.pos = this.matchRegExp.lastIndex;
|
||||
// Return the link without unwikilink character as plain text
|
||||
return [{type: "text", text: linkText.substr(1), start: start, end: this.parser.pos}];
|
||||
return [{type: "text", text: linkText.substr(1)}];
|
||||
};
|
||||
|
||||
@@ -243,28 +243,8 @@ exports.slowInSlowOut = function(t) {
|
||||
};
|
||||
|
||||
exports.copyObjectPropertiesSafe = function(object) {
|
||||
const seen = new Set();
|
||||
|
||||
function isDOMElement(value) {
|
||||
if(!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cross-realm DOM nodes
|
||||
if(typeof value.nodeType === "number" &&
|
||||
typeof value.nodeName === "string") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cross-realm Window objects
|
||||
if(value.window === value &&
|
||||
value.document &&
|
||||
value.location) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
const seen = new Set(),
|
||||
isDOMElement = (value) => value instanceof Node || value instanceof Window;
|
||||
|
||||
function safeCopy(obj) {
|
||||
// skip circular references
|
||||
@@ -275,6 +255,10 @@ exports.copyObjectPropertiesSafe = function(object) {
|
||||
if(typeof obj !== "object" || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
// skip DOM elements
|
||||
if(isDOMElement(obj)) {
|
||||
return undefined;
|
||||
}
|
||||
// copy arrays, preserving positions
|
||||
if(Array.isArray(obj)) {
|
||||
return obj.map((item) => {
|
||||
@@ -282,11 +266,7 @@ exports.copyObjectPropertiesSafe = function(object) {
|
||||
return value === undefined ? null : value;
|
||||
});
|
||||
}
|
||||
// skip DOM elements
|
||||
if(isDOMElement(obj)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
seen.add(obj);
|
||||
const copy = {};
|
||||
let key,
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
Fast test runner that boots the test edition and runs selected test specs.
|
||||
Skips the expensive --rendertiddler step from --build index.
|
||||
|
||||
Usage:
|
||||
node test-parser-quick.js [test-files...]
|
||||
|
||||
Examples:
|
||||
node test-parser-quick.js # Run ALL specs
|
||||
node test-parser-quick.js test-wikitext-parser # Run one spec file
|
||||
node test-parser-quick.js test-wikitext-parser test-filters # Run multiple spec files
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var $tw = require("../../boot/boot.js").TiddlyWiki();
|
||||
|
||||
$tw.boot.argv = ["editions/test"];
|
||||
|
||||
// Disable startup modules that aren't needed for tests
|
||||
$tw.boot.disabledStartupModules = [
|
||||
"favicon", "password", "browser-messaging", "info",
|
||||
"render", "rootwidget", "story", "windows"
|
||||
];
|
||||
|
||||
$tw.boot.boot(function() {
|
||||
var args = process.argv.slice(2);
|
||||
var allTests = $tw.wiki.filterTiddlers("[all[tiddlers+shadows]type[application/javascript]tag[$:/tags/test-spec]]");
|
||||
|
||||
// Filter test tiddlers if arguments provided
|
||||
var testsToRun;
|
||||
if(args.length > 0) {
|
||||
testsToRun = allTests.filter(function(title) {
|
||||
return args.some(function(arg) {
|
||||
return title.toLowerCase().indexOf(arg.toLowerCase()) !== -1;
|
||||
});
|
||||
});
|
||||
if(testsToRun.length === 0) {
|
||||
console.error("No test files matched: " + args.join(", "));
|
||||
console.error("Available test files:");
|
||||
allTests.forEach(function(t) { console.error(" " + t); });
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
testsToRun = allTests;
|
||||
}
|
||||
|
||||
// Override the test filter to only include our selected tests
|
||||
var titlesSet = Object.create(null);
|
||||
testsToRun.forEach(function(t) { titlesSet[t] = true; });
|
||||
|
||||
var origFilterTiddlers = $tw.wiki.filterTiddlers.bind($tw.wiki);
|
||||
$tw.wiki.filterTiddlers = function(filterString) {
|
||||
var result = origFilterTiddlers.apply(null, arguments);
|
||||
if(filterString.indexOf("$:/tags/test-spec") !== -1) {
|
||||
return result.filter(function(t) { return titlesSet[t]; });
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
console.log("Running " + testsToRun.length + " of " + allTests.length + " test files");
|
||||
|
||||
// Use the jasmine plugin's own runTests function
|
||||
var jasmine = $tw.modules.execute("$:/plugins/tiddlywiki/jasmine/jasmine-plugin.js");
|
||||
jasmine.runTests(function(err) {
|
||||
if(err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
/*\
|
||||
title: test-parsetree-positions.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
|
||||
Regression tests for #9882: wikitext parser rules must emit accurate
|
||||
`start`/`end` source positions on their parse tree nodes. Tooling that maps
|
||||
rendered output back to the source text relies on these offsets.
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
describe("Parse tree source position tests (#9882)", function() {
|
||||
|
||||
// Create a wiki
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
// Define a parsing shortcut
|
||||
var parse = function(text) {
|
||||
return wiki.parseText("text/vnd.tiddlywiki",text).tree;
|
||||
};
|
||||
|
||||
it("should give inline code runs a text node that spans only the code, not the backticks", function() {
|
||||
// codeinline.js: `code` gives a text node "code" spanning offsets 1 to 5. The closing backtick at 5 is excluded.
|
||||
// Bug: `end` was set to `this.parser.pos`, which sits past the closing backtick, so end was 6 and the span swallowed the backtick.
|
||||
expect(parse("`code`")).toEqual(
|
||||
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 6, children: [ { type: "element", tag: "code", rule: "codeinline", start: 0, end: 6, children: [ { type: "text", text: "code", start: 1, end: 5 } ] } ] } ]
|
||||
);
|
||||
// ``a`b`` gives text "a`b" spanning 2 to 5. `end` must be the offset of the closing marker whatever its length.
|
||||
// Bug: `end` was `this.parser.pos` (7), two characters past the code, so it also swallowed the closing ``.
|
||||
expect(parse("``a`b``")).toEqual(
|
||||
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 7, children: [ { type: "element", tag: "code", rule: "codeinline", start: 0, end: 7, children: [ { type: "text", text: "a`b", start: 2, end: 5 } ] } ] } ]
|
||||
);
|
||||
});
|
||||
|
||||
it("should start the text node of a suppressed external link after the ~", function() {
|
||||
// extlink.js: ~https://example.com/ emits the plain text "https://example.com/", which spans offsets 1 to 21.
|
||||
// Bug: `start` was the offset of the ~ (0), so the span was one character too wide and began on the ~ that the text omits.
|
||||
expect(parse("~https://example.com/")).toEqual(
|
||||
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 21, children: [ { type: "text", text: "https://example.com/", start: 1, end: 21, rule: "extlink" } ] } ]
|
||||
);
|
||||
});
|
||||
|
||||
it("should give a suppressed wikilink's text node source positions", function() {
|
||||
// wikilinkprefix.js: ~SuppressedLink emits the plain text "SuppressedLink", spanning offsets 1 to 15.
|
||||
// Bug: the text node carried no `start`/`end` at all. The parser framework then defaulted `start` to the ~ offset (0).
|
||||
expect(parse("~SuppressedLink")).toEqual(
|
||||
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 15, children: [ { type: "text", text: "SuppressedLink", start: 1, end: 15, rule: "wikilinkprefix" } ] } ]
|
||||
);
|
||||
});
|
||||
|
||||
it("should record the filter's start offset for an \\import pragma", function() {
|
||||
// import.js: \import [tag[x]] records the filter value "[tag[x]]" starting at offset 8, right after "\import ".
|
||||
// Bug: `filterStart` was assigned `this.parser.source` (the whole source string) instead of `this.parser.pos`, so `start` was a string, not an offset.
|
||||
expect(parse("\\import [tag[x]]\n")).toEqual(
|
||||
[ { type: "importvariables", rule: "import", start: 0, end: 16, attributes: { filter: { type: "string", value: "[tag[x]]", start: 8, end: 16 } }, children: [] } ]
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,240 +0,0 @@
|
||||
/*\
|
||||
title: test-utils-copyObjectPropertiesSafe.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
|
||||
Tests $tw.utils.copyObjectPropertiesSafe, the root cause of #9869.
|
||||
|
||||
$eventcatcher serialises DOM events via JSON.stringify(copyObjectPropertiesSafe(event)).
|
||||
|
||||
The original bug was caused by instanceof Node/Window being realm-specific.
|
||||
When an event originated from a different browser window, DOM objects from that
|
||||
window were not detected and JSON.stringify() could throw "Illegal invocation".
|
||||
|
||||
The implementation must:
|
||||
- skip DOM nodes and Window objects from other realms
|
||||
- preserve normal event data
|
||||
- preserve CustomEvent.detail payloads
|
||||
- continue copying enumerable properties from non-DOM objects
|
||||
- preserve arrays and break circular references
|
||||
|
||||
The tests use substitutes for foreign DOM objects because headless Node does not
|
||||
provide a second browser realm. These objects model the important characteristics:
|
||||
DOM nodes have nodeType/nodeName, and Window objects have window/self/document.
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
describe("copyObjectPropertiesSafe (#9869)", function() {
|
||||
|
||||
var cops = $tw.utils.copyObjectPropertiesSafe;
|
||||
|
||||
// Simulates a DOM node from another realm.
|
||||
// The important characteristics are:
|
||||
// - nodeType/nodeName identify it as a DOM node
|
||||
// - circular parentNode references resemble real DOM trees
|
||||
function fakeElement(tagName,extra) {
|
||||
var node = $tw.utils.extend({
|
||||
nodeType: 1,
|
||||
nodeName: tagName,
|
||||
tagName: tagName
|
||||
},extra || {});
|
||||
|
||||
node.parentNode = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
// Simulates a Window object from another realm.
|
||||
function fakeWindow() {
|
||||
var win = {
|
||||
document: {},
|
||||
location: {}
|
||||
};
|
||||
|
||||
win.window = win;
|
||||
win.self = win;
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
// An event carrying foreign DOM nodes and a Window.
|
||||
function fakeEvent(type,target,extra) {
|
||||
return $tw.utils.extend({
|
||||
type: type,
|
||||
target: target,
|
||||
currentTarget: target,
|
||||
view: fakeWindow(),
|
||||
detail: 0,
|
||||
isTrusted: true
|
||||
},extra || {});
|
||||
}
|
||||
|
||||
|
||||
it("does not throw serialising any event type from a secondary window", function() {
|
||||
var events = [
|
||||
fakeEvent("focusin",fakeElement("INPUT")),
|
||||
fakeEvent("change",fakeElement("SELECT")),
|
||||
fakeEvent("click",fakeElement("BUTTON"),{
|
||||
button: 0,
|
||||
clientX: 5,
|
||||
clientY: 9,
|
||||
relatedTarget: null
|
||||
}),
|
||||
fakeEvent("mouseover",fakeElement("DIV"),{
|
||||
relatedTarget: fakeElement("SPAN")
|
||||
})
|
||||
];
|
||||
|
||||
events.forEach(function(event) {
|
||||
expect(function() {
|
||||
JSON.stringify(cops(event));
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("drops DOM nodes and Window but keeps serialisable event data", function() {
|
||||
var event = fakeEvent("click",fakeElement("BUTTON"),{
|
||||
button: 0,
|
||||
clientX: 5,
|
||||
clientY: 9
|
||||
});
|
||||
|
||||
var result = JSON.parse(JSON.stringify(cops(event)));
|
||||
|
||||
expect(result.target).toBeUndefined();
|
||||
expect(result.currentTarget).toBeUndefined();
|
||||
expect(result.view).toBeUndefined();
|
||||
|
||||
expect(result.type).toBe("click");
|
||||
expect(result.detail).toBe(0);
|
||||
expect(result.isTrusted).toBe(true);
|
||||
expect(result.button).toBe(0);
|
||||
expect(result.clientX).toBe(5);
|
||||
expect(result.clientY).toBe(9);
|
||||
});
|
||||
|
||||
|
||||
it("preserves nested objects, arrays and circular reference handling", function() {
|
||||
var event = fakeEvent("custom",fakeElement("DIV"),{
|
||||
detail: {
|
||||
nested: {
|
||||
a: 1,
|
||||
b: [2,3]
|
||||
}
|
||||
},
|
||||
path: [
|
||||
fakeElement("DIV"),
|
||||
fakeWindow(),
|
||||
42
|
||||
]
|
||||
});
|
||||
|
||||
event.self = event;
|
||||
|
||||
var result = JSON.parse(JSON.stringify(cops(event)));
|
||||
|
||||
expect(result.detail).toEqual({
|
||||
nested: {
|
||||
a: 1,
|
||||
b: [2,3]
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.path).toEqual([
|
||||
null,
|
||||
null,
|
||||
42
|
||||
]);
|
||||
|
||||
expect(result.self).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it("preserves CustomEvent.detail objects including custom object instances", function() {
|
||||
function DetailObject() {
|
||||
this.name = "example";
|
||||
this.values = [1,2,3];
|
||||
}
|
||||
|
||||
var event = fakeEvent("custom",fakeElement("DIV"),{
|
||||
detail: new DetailObject()
|
||||
});
|
||||
|
||||
var result = JSON.parse(JSON.stringify(cops(event)));
|
||||
|
||||
expect(result.detail).toEqual({
|
||||
name: "example",
|
||||
values: [1,2,3]
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("preserves CustomEvent.detail objects while dropping DOM objects inside them", function() {
|
||||
var event = fakeEvent("custom",fakeElement("DIV"),{
|
||||
detail: {
|
||||
value: 123,
|
||||
nested: {
|
||||
target: fakeElement("SPAN"),
|
||||
kept: "yes"
|
||||
},
|
||||
items: [
|
||||
fakeElement("BUTTON"),
|
||||
42
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
var result = JSON.parse(JSON.stringify(cops(event)));
|
||||
|
||||
expect(result.detail.value).toBe(123);
|
||||
|
||||
expect(result.detail.nested.target).toBeUndefined();
|
||||
expect(result.detail.nested.kept).toBe("yes");
|
||||
|
||||
expect(result.detail.items).toEqual([
|
||||
null,
|
||||
42
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it("accepts primitive, array and object arguments", function() {
|
||||
var nullProto = Object.create(null);
|
||||
nullProto.kept = true;
|
||||
|
||||
expect(cops(42)).toBe(42);
|
||||
expect(cops(null)).toBe(null);
|
||||
|
||||
expect(cops([
|
||||
1,
|
||||
"two",
|
||||
{three: 3}
|
||||
])).toEqual([
|
||||
1,
|
||||
"two",
|
||||
{three: 3}
|
||||
]);
|
||||
|
||||
expect(cops({
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
})).toEqual({
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
});
|
||||
|
||||
expect(JSON.parse(JSON.stringify(cops({
|
||||
nullProto: nullProto
|
||||
})))).toEqual({
|
||||
nullProto: {
|
||||
kept: true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
title: Community Survey 2025
|
||||
created: 20250708130030654
|
||||
modified: 20250826162904085
|
||||
title: Community Survey 2025
|
||||
|
||||
<div style.float="right" style.padding-left="1em">
|
||||
<$image source="Community Survey 2025 Image" alt="Shaping the future of TiddlyWiki with the Community Survey 2025" width="280"/>
|
||||
<$image source="Community Survey 2025" alt="Shaping the future of TiddlyWiki with the Community Survey 2025" width="280"/>
|
||||
</div>
|
||||
|
||||
The core developers work hard year by year to continuously improve ~TiddlyWiki. Part of the satisfaction is that we are not just building software for ourselves, we’re serving the needs of a wider community of users.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 54 KiB |
-21
@@ -1,21 +0,0 @@
|
||||
title: $:/changenotes/5.4.1/#9882
|
||||
created: 20260701142414000
|
||||
modified: 20260701142414000
|
||||
description: Fix invalid source positions in parse tree nodes
|
||||
release: 5.4.1
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: bugfix
|
||||
change-category: internal
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9882
|
||||
github-contributors: pmario
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
Several wikitext parser rules emitted parse tree nodes with missing or incorrect `start` and `end` source positions. Accurate positions are needed by tooling that maps rendered output back to the source text.
|
||||
|
||||
* ''Inline code'' (`` `code` ``): the text node `end` no longer runs past the closing backtick, so the span covers only the code text.
|
||||
|
||||
* ''Suppressed external links'' (`~https://example.com`): the text node now starts after the suppressing `~`, matching the plain text that is emitted.
|
||||
|
||||
* ''Suppressed wikilinks'' (`~SuppressedLink`): the text node now carries `start` and `end` positions. Previously it had none.
|
||||
|
||||
* ''Import filter'' (`\import`): the filter start position was set to the whole source string instead of the current parse position. It now uses the correct offset.
|
||||
@@ -1,13 +0,0 @@
|
||||
title: $:/changenotes/5.4.1/#9905
|
||||
created: 20260709142414000
|
||||
modified: 20260709142414000
|
||||
description: Fix errors in eventcatcher while serializing event properties
|
||||
release: 5.4.1
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: bugfix
|
||||
change-category: internal
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9905
|
||||
github-contributors: saqimtiaz
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
Fixes an issue in utils.copyObjectPropertiesSafe which did not correctly handle DOM objects from other windows resulting in an error.
|
||||
Reference in New Issue
Block a user