1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-07-04 11:02:51 +00:00

Merge branch 'master' into multi-wiki-support

This commit is contained in:
Jeremy Ruston 2024-05-26 10:40:19 +01:00
commit b4564e31bd
10 changed files with 128 additions and 36 deletions

View File

@ -2188,6 +2188,8 @@ Returns an array of search paths
*/ */
$tw.getLibraryItemSearchPaths = function(libraryPath,envVar) { $tw.getLibraryItemSearchPaths = function(libraryPath,envVar) {
var pluginPaths = [path.resolve($tw.boot.corePath,libraryPath)], var pluginPaths = [path.resolve($tw.boot.corePath,libraryPath)],
env;
if(envVar) {
env = process.env[envVar]; env = process.env[envVar];
if(env) { if(env) {
env.split(path.delimiter).map(function(item) { env.split(path.delimiter).map(function(item) {
@ -2196,6 +2198,7 @@ $tw.getLibraryItemSearchPaths = function(libraryPath,envVar) {
} }
}); });
} }
}
return pluginPaths; return pluginPaths;
}; };

View File

@ -0,0 +1,23 @@
/*\
title: $:/core/modules/utils/errors.js
type: application/javascript
module-type: utils
Custom errors for TiddlyWiki.
\*/
(function(){
function TranscludeRecursionError() {
Error.apply(this,arguments);
this.signatures = Object.create(null);
};
/* Maximum permitted depth of the widget tree for recursion detection */
TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH = 1000;
TranscludeRecursionError.prototype = Object.create(Error);
exports.TranscludeRecursionError = TranscludeRecursionError;
})();

View File

@ -14,8 +14,12 @@ Utilities for working with the TiddlyWiki repository file structure
/* /*
Get an object containing all the plugins as a hashmap by title of the JSON representation of the plugin Get an object containing all the plugins as a hashmap by title of the JSON representation of the plugin
Options:
ignoreEnvironmentVariables: defaults to false
*/ */
exports.getAllPlugins = function() { exports.getAllPlugins = function(options) {
options = options || {};
var fs = require("fs"), var fs = require("fs"),
path = require("path"), path = require("path"),
tiddlers = {}; tiddlers = {};
@ -39,9 +43,9 @@ exports.getAllPlugins = function() {
} }
} }
}; };
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.pluginsPath,$tw.config.pluginsEnvVar),collectPublisherPlugins); $tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.pluginsPath,options.ignoreEnvironmentVariables ? undefined : $tw.config.pluginsEnvVar),collectPublisherPlugins);
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.themesPath,$tw.config.themesEnvVar),collectPublisherPlugins); $tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.themesPath,options.ignoreEnvironmentVariables ? undefined : $tw.config.themesEnvVar),collectPublisherPlugins);
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.languagesPath,$tw.config.languagesEnvVar),collectPlugins); $tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.languagesPath,options.ignoreEnvironmentVariables ? undefined : $tw.config.languagesEnvVar),collectPlugins);
return tiddlers; return tiddlers;
}; };

View File

@ -30,7 +30,30 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent; this.parentDomNode = parent;
this.computeAttributes(); this.computeAttributes();
this.execute(); this.execute();
try {
this.renderChildren(parent,nextSibling); this.renderChildren(parent,nextSibling);
} catch(error) {
if(error instanceof $tw.utils.TranscludeRecursionError) {
// We were infinite looping.
// We need to try and abort as much of the loop as we can, so we will keep "throwing" upward until we find a transclusion that has a different signature.
// Hopefully that will land us just outside where the loop began. That's where we want to issue an error.
// Rendering widgets beneath this point may result in a freezing browser if they explode exponentially.
var transcludeSignature = this.getVariable("transclusion");
if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) {
// For the first fifty transcludes we climb up, we simply collect signatures.
// We're assuming that those first 50 will likely include all transcludes involved in the loop.
error.signatures[transcludeSignature] = true;
} else if(!error.signatures[transcludeSignature]) {
// Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering.
this.children = [this.makeChildWidget({type: "error", attributes: {
"$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
}})];
this.renderChildren(parent,nextSibling);
return;
}
}
throw error;
}
}; };
/* /*

View File

@ -12,9 +12,6 @@ Widget base class
/*global $tw: false */ /*global $tw: false */
"use strict"; "use strict";
/* Maximum permitted depth of the widget tree for recursion detection */
var MAX_WIDGET_TREE_DEPTH = 1000;
/* /*
Create a widget object for a parse tree node Create a widget object for a parse tree node
parseTreeNode: reference to the parse tree node to be rendered parseTreeNode: reference to the parse tree node to be rendered
@ -494,10 +491,8 @@ Widget.prototype.makeChildWidgets = function(parseTreeNodes,options) {
this.children = []; this.children = [];
var self = this; var self = this;
// Check for too much recursion // Check for too much recursion
if(this.getAncestorCount() > MAX_WIDGET_TREE_DEPTH) { if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH) {
this.children.push(this.makeChildWidget({type: "error", attributes: { throw new $tw.utils.TranscludeRecursionError();
"$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
}}));
} else { } else {
// Create set variable widgets for each variable // Create set variable widgets for each variable
$tw.utils.each(options.variables,function(value,name) { $tw.utils.each(options.variables,function(value,name) {

View File

@ -7,7 +7,8 @@ title: Output
\whitespace trim \whitespace trim
<$transclude $tiddler="Output"/> <$transclude $tiddler="Output"/>
+ +
title: ExpectedResult title: ExpectedResult
<p><span class="tc-error">Recursive transclusion error in transclude widget</span></p> <span class="tc-error">Recursive transclusion error in transclude widget</span>

View File

@ -17,7 +17,7 @@ if($tw.node) {
describe("Plugin tests", function() { describe("Plugin tests", function() {
// Get all the plugins as a hashmap by title of a JSON string with the plugin content // Get all the plugins as a hashmap by title of a JSON string with the plugin content
var tiddlers = $tw.utils.getAllPlugins(); var tiddlers = $tw.utils.getAllPlugins({ignoreEnvironmentVariables: true});
// console.log(JSON.stringify(Object.keys(tiddlers),null,4)); // console.log(JSON.stringify(Object.keys(tiddlers),null,4));
describe("every plugin should have the required standard fields", function() { describe("every plugin should have the required standard fields", function() {
var titles = Object.keys(tiddlers); var titles = Object.keys(tiddlers);

View File

@ -160,6 +160,47 @@ describe("Widget module", function() {
expect(wrapper.innerHTML).toBe("<span class=\"tc-error\">Recursive transclusion error in transclude widget</span>"); expect(wrapper.innerHTML).toBe("<span class=\"tc-error\">Recursive transclusion error in transclude widget</span>");
}); });
it("should handle single-tiddler recursion with branching nodes", function() {
var wiki = new $tw.Wiki();
// Add a tiddler
wiki.addTiddlers([
{title: "TiddlerOne", text: "<$tiddler tiddler='TiddlerOne'><$transclude /> <$transclude /></$tiddler>"},
]);
// Test parse tree
var parseTreeNode = {type: "widget", children: [
{type: "transclude", attributes: {
"tiddler": {type: "string", value: "TiddlerOne"}
}}
]};
// Construct the widget node
var widgetNode = createWidgetNode(parseTreeNode,wiki);
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// Test the rendering
expect(wrapper.innerHTML).toBe("<span class=\"tc-error\">Recursive transclusion error in transclude widget</span> <span class=\"tc-error\">Recursive transclusion error in transclude widget</span>");
});
it("should handle many-tiddler recursion with branching nodes", function() {
var wiki = new $tw.Wiki();
// Add a tiddler
wiki.addTiddlers([
{title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/> <$transclude tiddler='TiddlerTwo'/>"},
{title: "TiddlerTwo", text: "<$transclude tiddler='TiddlerOne'/>"}
]);
// Test parse tree
var parseTreeNode = {type: "widget", children: [
{type: "transclude", attributes: {
"tiddler": {type: "string", value: "TiddlerOne"}
}}
]};
// Construct the widget node
var widgetNode = createWidgetNode(parseTreeNode,wiki);
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// Test the rendering
expect(wrapper.innerHTML).toBe("<span class=\"tc-error\">Recursive transclusion error in transclude widget</span>");
});
it("should deal with SVG elements", function() { it("should deal with SVG elements", function() {
var wiki = new $tw.Wiki(); var wiki = new $tw.Wiki();
// Construct the widget node // Construct the widget node

View File

@ -30,6 +30,7 @@ name: 具可读性的插件条目的名称
parent-plugin: 对于一个插件,指定其为哪个插件的子插件 parent-plugin: 对于一个插件,指定其为哪个插件的子插件
plugin-priority: 插件条目的优先级数值 plugin-priority: 插件条目的优先级数值
plugin-type: 插件条目的类型 plugin-type: 插件条目的类型
stability: 插件的开发状态:已弃用、实验性、稳定或旧版
released: TiddlyWiki 的发布日期 released: TiddlyWiki 的发布日期
revision: 条目存放于服务器中的修订版本 revision: 条目存放于服务器中的修订版本
source: 条目的网址 source: 条目的网址

View File

@ -30,6 +30,7 @@ name: 具可讀性的套件條目的名稱
parent-plugin: 對於一個插件,指定其為哪個插件的子插件 parent-plugin: 對於一個插件,指定其為哪個插件的子插件
plugin-priority: 套件條目的優先級數值 plugin-priority: 套件條目的優先級數值
plugin-type: 套件條目的類型 plugin-type: 套件條目的類型
stability: 插件的開發狀態:已棄用、實驗性、穩定或舊版
released: TiddlyWiki 的釋出日期 released: TiddlyWiki 的釋出日期
revision: 條目存放於伺服器中的修訂版本 revision: 條目存放於伺服器中的修訂版本
source: 條目的網址 source: 條目的網址