From ed35d91be6c6e188c80bc0994fa83acd7526d225 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 12 Oct 2013 17:05:13 +0100 Subject: [PATCH] Initial Commit Carried over from the abortive pull request #169 --- core/modules/commands/new_rendertiddler.js | 44 ++ core/modules/new_widgets/element.js | 73 ++++ core/modules/new_widgets/entity.js | 62 +++ core/modules/new_widgets/fields.js | 113 +++++ core/modules/new_widgets/link.js | 117 +++++ core/modules/new_widgets/list.js | 258 +++++++++++ core/modules/new_widgets/navigator.js | 120 ++++++ core/modules/new_widgets/reveal.js | 134 ++++++ core/modules/new_widgets/setvariable.js | 64 +++ core/modules/new_widgets/tempwidgets.js | 22 + core/modules/new_widgets/text.js | 63 +++ core/modules/new_widgets/tiddler.js | 75 ++++ core/modules/new_widgets/transclude.js | 93 ++++ core/modules/new_widgets/version.js | 62 +++ core/modules/new_widgets/view.js | 143 +++++++ core/modules/new_widgets/widget.js | 378 +++++++++++++++++ core/modules/startup.js | 33 +- core/modules/utils/fakedom.js | 42 +- core/modules/wiki.js | 87 ++++ core/templates/wikified-tiddler.tid | 2 +- core/ui/EditTemplate.tid | 2 +- core/ui/ViewTemplate/body.tid | 2 +- editions/test/tiddlers/tests/test-widget.js | 399 ++++++++++++++++++ .../editions/TiddlyWiki5_Node_Edition.tid | 2 +- nbld.sh | 28 ++ 25 files changed, 2403 insertions(+), 15 deletions(-) create mode 100755 core/modules/commands/new_rendertiddler.js create mode 100755 core/modules/new_widgets/element.js create mode 100755 core/modules/new_widgets/entity.js create mode 100755 core/modules/new_widgets/fields.js create mode 100755 core/modules/new_widgets/link.js create mode 100755 core/modules/new_widgets/list.js create mode 100755 core/modules/new_widgets/navigator.js create mode 100755 core/modules/new_widgets/reveal.js create mode 100755 core/modules/new_widgets/setvariable.js create mode 100755 core/modules/new_widgets/tempwidgets.js create mode 100755 core/modules/new_widgets/text.js create mode 100755 core/modules/new_widgets/tiddler.js create mode 100755 core/modules/new_widgets/transclude.js create mode 100755 core/modules/new_widgets/version.js create mode 100755 core/modules/new_widgets/view.js create mode 100755 core/modules/new_widgets/widget.js mode change 100644 => 100755 core/modules/startup.js mode change 100644 => 100755 core/modules/utils/fakedom.js mode change 100644 => 100755 core/modules/wiki.js create mode 100755 editions/test/tiddlers/tests/test-widget.js create mode 100755 nbld.sh diff --git a/core/modules/commands/new_rendertiddler.js b/core/modules/commands/new_rendertiddler.js new file mode 100755 index 000000000..41dc5d66d --- /dev/null +++ b/core/modules/commands/new_rendertiddler.js @@ -0,0 +1,44 @@ +/*\ +title: $:/core/modules/commands/new_rendertiddler.js +type: application/javascript +module-type: command + +Command to render a tiddler and save it to a file + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports.info = { + name: "new_rendertiddler", + synchronous: false +}; + +var Command = function(params,commander,callback) { + this.params = params; + this.commander = commander; + this.callback = callback; +}; + +Command.prototype.execute = function() { + if(this.params.length < 2) { + return "Missing filename"; + } + var self = this, + fs = require("fs"), + path = require("path"), + title = this.params[0], + filename = this.params[1], + type = this.params[2] || "text/html"; + fs.writeFile(filename,this.commander.wiki.new_renderTiddler(type,title),"utf8",function(err) { + self.callback(err); + }); + return null; +}; + +exports.Command = Command; + +})(); diff --git a/core/modules/new_widgets/element.js b/core/modules/new_widgets/element.js new file mode 100755 index 000000000..99215a3cd --- /dev/null +++ b/core/modules/new_widgets/element.js @@ -0,0 +1,73 @@ +/*\ +title: $:/core/modules/new_widgets/element.js +type: application/javascript +module-type: new_widget + +Element widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var ElementWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +ElementWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +ElementWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + var domNode = this.document.createElement(this.parseTreeNode.tag); + this.assignAttributes(domNode); + parent.insertBefore(domNode,nextSibling); + this.renderChildren(domNode,null); + this.domNodes.push(domNode); +}; + +/* +Compute the internal state of the widget +*/ +ElementWidget.prototype.execute = function() { + this.makeChildWidgets(); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +ElementWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(), + hasChangedAttributes = $tw.utils.count(changedAttributes) > 0; + if(hasChangedAttributes) { + // Update our attributes + this.assignAttributes(this.domNodes[0]); + } + var hasRefreshed = this.refreshChildren(changedTiddlers); + return hasRefreshed || hasChangedAttributes; +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +ElementWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.element = ElementWidget; + +})(); diff --git a/core/modules/new_widgets/entity.js b/core/modules/new_widgets/entity.js new file mode 100755 index 000000000..3b750fb90 --- /dev/null +++ b/core/modules/new_widgets/entity.js @@ -0,0 +1,62 @@ +/*\ +title: $:/core/modules/new_widgets/entity.js +type: application/javascript +module-type: new_widget + +HTML entity widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var EntityWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +EntityWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +EntityWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.execute(); + var textNode = this.document.createTextNode($tw.utils.entityDecode(this.parseTreeNode.entity)); + parent.insertBefore(textNode,nextSibling); + this.domNodes.push(textNode); +}; + +/* +Compute the internal state of the widget +*/ +EntityWidget.prototype.execute = function() { +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +EntityWidget.prototype.refresh = function(changedTiddlers) { + return false; +}; + +/* +Remove any DOM nodes created by this widget +*/ +EntityWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.entity = EntityWidget; + +})(); diff --git a/core/modules/new_widgets/fields.js b/core/modules/new_widgets/fields.js new file mode 100755 index 000000000..e4bd21a3d --- /dev/null +++ b/core/modules/new_widgets/fields.js @@ -0,0 +1,113 @@ +/*\ +title: $:/core/modules/new_widgets/fields.js +type: application/javascript +module-type: new_widget + +View widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var FieldsWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +FieldsWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +FieldsWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + var textNode = this.document.createTextNode(this.text); + parent.insertBefore(textNode,nextSibling); + this.domNodes.push(textNode); +}; + +/* +Compute the internal state of the widget +*/ +FieldsWidget.prototype.execute = function() { + // Get parameters from our attributes + this.tiddlerTitle = this.getAttribute("tiddler",this.getVariable("tiddlerTitle")); + this.template = this.getAttribute("template"); + this.exclude = this.getAttribute("exclude"); + this.stripTitlePrefix = this.getAttribute("stripTitlePrefix","no") === "yes"; + // Get the value to display + var tiddler = this.wiki.getTiddler(this.tiddlerTitle); + // Get the exclusion list + var exclude; + if(this.exclude) { + exclude = this.exclude.split(" "); + } else { + exclude = ["text"]; + } + // Compose the template + var text = []; + if(this.template && tiddler) { + var fields = []; + for(var fieldName in tiddler.fields) { + if(exclude.indexOf(fieldName) === -1) { + fields.push(fieldName); + } + } + fields.sort(); + for(var f=0; f 0) { + templateTree = this.parseTreeNode.children; + } else { + // Default template is a link to the title + templateTree = [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ + {type: "text", text: title} + ]}]; + } + } + templateTree = [{type: "tiddler", attributes: {title: {type: "string", value: title}}, children: templateTree}] + } + // Return the list item + return {type: "listitem", itemTitle: title, children: templateTree}; +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +ListWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + // Completely refresh if any of our attributes have changed + if(changedAttributes.filter || changedAttributes.preserveCurrentTiddler) { + this.refreshSelf(); + return true; + } else { + // Handle any changes to the list + return this.handleListChanges(changedTiddlers); + } +}; + +/* +Process any changes to the list +*/ +ListWidget.prototype.handleListChanges = function(changedTiddlers) { + // Get the new list + var prevList = this.list; + this.list = this.getTiddlerList(); + // Check for an empty list + if(this.list.length === 0) { + // Check if it was empty before + if(prevList.length === 0) { + // If so, just refresh the empty message + return this.refreshChildren(changedTiddlers); + } else { + // Replace the previous content with the empty message + var nextSibling = this.findNextSibling(); + this.removeChildDomNodes(); + this.makeChildWidgets(this.getEmptyMessage()); + this.renderChildren(this.parentDomNode,nextSibling); + return true; + } + } else { + // If the list was empty then we need to remove the empty message + if(prevList.length === 0) { + this.removeChildDomNodes(); + this.children = []; + } + // Cycle through the list, inserting and removing list items as needed + var hasRefreshed = false; + for(var t=0; t=t; n--) { + this.removeListItem(n); + hasRefreshed = true; + } + // Refresh the item we're reusing + var refreshed = this.children[t].refresh(changedTiddlers); + hasRefreshed = hasRefreshed || refreshed; + } + } + // Remove any left over items + for(t=this.children.length-1; t>=this.list.length; t--) { + this.removeListItem(t); + hasRefreshed = true; + } + return hasRefreshed; + } +}; + +/* +Find the list item with a given title, starting from a specified position +*/ +ListWidget.prototype.findListItem = function(startIndex,title) { + while(startIndex < this.children.length) { + if(this.children[startIndex].parseTreeNode.itemTitle === title) { + return startIndex; + } + startIndex++; + } + return undefined; +}; + +/* +Insert a new list item at the specified index +*/ +ListWidget.prototype.insertListItem = function(index,title) { + var newItem = this.makeChildWidget(this.makeItemTemplate(title)); + newItem.parentDomNode = this.parentDomNode; // Hack to enable findNextSibling() to work + this.children.splice(index,0,newItem); + var nextSibling = newItem.findNextSibling(); + newItem.render(this.parentDomNode,nextSibling); + return true; +}; + +/* +Remvoe the specified list item +*/ +ListWidget.prototype.removeListItem = function(index) { + // Remove the DOM nodes owned by this item + this.children[index].removeChildDomNodes(); + // Remove the child widget + this.children.splice(index,1); +}; + +exports.list = ListWidget; + +var ListItemWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +ListItemWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +ListItemWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +ListItemWidget.prototype.execute = function() { + // Set the current list item title + this.setVariable("listItem",this.parseTreeNode.itemTitle); + // Construct the child widgets + this.makeChildWidgets(); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +ListItemWidget.prototype.refresh = function(changedTiddlers) { + return this.refreshChildren(changedTiddlers); +}; + +exports.listitem = ListItemWidget; + +})(); diff --git a/core/modules/new_widgets/navigator.js b/core/modules/new_widgets/navigator.js new file mode 100755 index 000000000..cc7122c5e --- /dev/null +++ b/core/modules/new_widgets/navigator.js @@ -0,0 +1,120 @@ +/*\ +title: $:/core/modules/new_widgets/navigator.js +type: application/javascript +module-type: new_widget + +Navigator widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var NavigatorWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); + this.addEventListeners([ + {type: "tw-navigate", handler: "handleNavigateEvent"}, + {type: "tw-edit-tiddler", handler: "handleEditTiddlerEvent"}, + {type: "tw-delete-tiddler", handler: "handleDeleteTiddlerEvent"}, + {type: "tw-save-tiddler", handler: "handleSaveTiddlerEvent"}, + {type: "tw-cancel-tiddler", handler: "handleCancelTiddlerEvent"}, + {type: "tw-close-tiddler", handler: "handleCloseTiddlerEvent"}, + {type: "tw-close-all-tiddlers", handler: "handleCloseAllTiddlersEvent"}, + {type: "tw-new-tiddler", handler: "handleNewTiddlerEvent"} + ]); +}; + +/* +Inherit from the base widget class +*/ +NavigatorWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +NavigatorWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +NavigatorWidget.prototype.execute = function() { + // Get our parameters + this.storyTitle = this.getAttribute("story"); + this.historyTitle = this.getAttribute("history"); + // Construct the child widgets + this.makeChildWidgets(); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +NavigatorWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.story || changedAttributes.history) { + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +NavigatorWidget.prototype.getStoryList = function() { + this.storyList = this.wiki.getTiddlerList(this.storyTitle); +}; + +NavigatorWidget.prototype.saveStoryList = function() { + var storyTiddler = this.wiki.getTiddler(this.storyTitle); + this.wiki.addTiddler(new $tw.Tiddler({ + title: this.storyTitle + },storyTiddler,{list: this.storyList})); +}; + +NavigatorWidget.prototype.findTitleInStory = function(title,defaultIndex) { + for(var t=0; t 0) { + this.initialise(parseTreeNode,options); + } +}; + +/* +Initialise widget properties. These steps are pulled out of the constructor so that we can reuse them in subclasses +*/ +Widget.prototype.initialise = function(parseTreeNode,options) { + options = options || {}; + // Save widget info + this.parseTreeNode = parseTreeNode; + this.wiki = options.wiki; + this.variables = options.variables || {}; + this.parentWidget = options.parentWidget; + this.document = options.document; + this.attributes = {}; + this.children = []; + this.domNodes = []; + this.eventListeners = {}; + // Hashmap of the widget classes + if(!this.widgetClasses) { + Widget.prototype.widgetClasses = $tw.modules.applyMethods("new_widget"); + } +}; + +/* +Render this widget into the DOM +*/ +Widget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +Widget.prototype.execute = function() { + this.makeChildWidgets(); +}; + +/* +Get the prevailing value of a context variable +name: name of variable +params: array of {name:, value:} for each parameter +*/ +Widget.prototype.getVariable = function(name,params) { + // Search up the widget tree for the variable name + var node = this; + while(node && !$tw.utils.hop(node.variables,name)) { + node = node.parentWidget; + } + if(!node) { + return undefined; + } + // Get the value + var value = node.variables[name].value; + // Substitute any parameters specified in the definition + var defParams = node.variables[name].params; + if(defParams) { + var nextAnonParameter = 0, // Next candidate anonymous parameter in macro call + paramInfo, paramValue; + // Step through each of the parameters in the macro definition + for(var p=0; p 0) { + return this.domNodes[0]; + } + // Otherwise, recursively call our children + for(var t=0; t \ No newline at end of file +<$transclude /> \ No newline at end of file diff --git a/core/ui/EditTemplate.tid b/core/ui/EditTemplate.tid index d6fe4a8e7..1f3c2041f 100644 --- a/core/ui/EditTemplate.tid +++ b/core/ui/EditTemplate.tid @@ -15,7 +15,7 @@ modifier: JeremyRuston <$transclude title="$:/core/ui/EditorHint"/> <$button type="set" set="$:/ShowEditPreview" setTo="no">hide preview
-<$view field="text" format="wikified"/> +<$transclude />
<$edit field="text"/> diff --git a/core/ui/ViewTemplate/body.tid b/core/ui/ViewTemplate/body.tid index cda91f5e2..4d07820d1 100644 --- a/core/ui/ViewTemplate/body.tid +++ b/core/ui/ViewTemplate/body.tid @@ -2,5 +2,5 @@ title: $:/core/ui/ViewTemplate/body tags: $:/tags/ViewTemplate
-<$view field="text" format="wikified"/> +<$transclude />
\ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js new file mode 100755 index 000000000..bf3068962 --- /dev/null +++ b/editions/test/tiddlers/tests/test-widget.js @@ -0,0 +1,399 @@ +/*\ +title: test-widget.js +type: application/javascript +tags: [[$:/tags/test-spec]] + +Tests the wikitext rendering pipeline end-to-end. We also need tests that individually test parsers, rendertreenodes etc., but this gets us started. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +describe("Widget module", function() { + + var widget = require("$:/core/modules/new_widgets/widget.js"); + + function createWidgetNode(parseTreeNode,wiki,variables) { + return new widget.widget(parseTreeNode,{ + wiki: wiki, + variables: variables || {}, + document: $tw.document + }); + } + + function parseText(text,wiki) { + var parser = wiki.new_parseText("text/vnd.tiddlywiki",text); + return parser ? {type: "widget", children: parser.tree} : undefined; + } + + function renderWidgetNode(widgetNode) { + $tw.document.setSequenceNumber(0); + var wrapper = $tw.document.createElement("div"); + widgetNode.render(wrapper,null); +// console.log(require("util").inspect(wrapper,{depth: 8})); + return wrapper; + } + + function refreshWidgetNode(widgetNode,wrapper,changes) { + var changedTiddlers = {}; + if(changes) { + $tw.utils.each(changes,function(title) { + changedTiddlers[title] = true; + }); + } + widgetNode.refresh(changedTiddlers,wrapper,null); +// console.log(require("util").inspect(wrapper,{depth: 8})); + } + + it("should deal with text nodes and HTML elements", function() { + var wiki = new $tw.Wiki(); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "text", text: "A text node"}, + {type: "element", tag: "div", attributes: { + "class": {type: "string", value: "myClass"}, + "title": {type: "string", value: "myTitle"} + }, children: [ + {type: "text", text: " and the content of a DIV"}, + {type: "element", tag: "div", children: [ + {type: "text", text: " and an inner DIV"}, + ]}, + {type: "text", text: " and back in the outer DIV"} + ]} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + describe("should render", function() { + // Test the rendering + expect(wrapper.innerHTML).toBe("A text node
\n and the content of a DIV
\n and an inner DIV
and back in the outer DIV
"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[1].sequenceNumber).toBe(2); + expect(wrapper.children[1].children[0].sequenceNumber).toBe(3); + expect(wrapper.children[1].children[1].sequenceNumber).toBe(4); + expect(wrapper.children[1].children[1].children[0].sequenceNumber).toBe(5); + expect(wrapper.children[1].children[2].sequenceNumber).toBe(6); + }); + }); + + it("should deal with transclude widgets and indirect attributes", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "the quick brown fox"} + ]); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "text", text: "A text node"}, + {type: "element", tag: "div", attributes: { + "class": {type: "string", value: "myClass"}, + "title": {type: "indirect", textReference: "TiddlerOne"} + }, children: [ + {type: "text", text: " and the content of a DIV"}, + {type: "element", tag: "div", children: [ + {type: "text", text: " and an inner DIV"}, + ]}, + {type: "text", text: " and back in the outer DIV"}, + {type: "transclude", attributes: { + "title": {type: "string", value: "TiddlerOne"} + }} + ]}, + {type: "transclude", attributes: { + "title": {type: "string", value: "TiddlerOne"} + }} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + describe("should render", function() { + // Test the rendering + expect(wrapper.innerHTML).toBe("A text node
\n and the content of a DIV
\n and an inner DIV
and back in the outer DIVthe quick brown fox
the quick brown fox"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[1].sequenceNumber).toBe(2); + expect(wrapper.children[1].children[0].sequenceNumber).toBe(3); + expect(wrapper.children[1].children[1].sequenceNumber).toBe(4); + expect(wrapper.children[1].children[1].children[0].sequenceNumber).toBe(5); + expect(wrapper.children[1].children[2].sequenceNumber).toBe(6); + expect(wrapper.children[1].children[3].sequenceNumber).toBe(7); + expect(wrapper.children[2].sequenceNumber).toBe(8); + }); + // Change the transcluded tiddler + wiki.addTiddler({title: "TiddlerOne", text: "jumps over the lazy dog"}); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerOne"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("A text node
\n and the content of a DIV
\n and an inner DIV
and back in the outer DIVjumps over the lazy dog
jumps over the lazy dog"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[1].sequenceNumber).toBe(2); + expect(wrapper.children[1].children[0].sequenceNumber).toBe(3); + expect(wrapper.children[1].children[1].sequenceNumber).toBe(4); + expect(wrapper.children[1].children[1].children[0].sequenceNumber).toBe(5); + expect(wrapper.children[1].children[2].sequenceNumber).toBe(6); + expect(wrapper.children[1].children[3].sequenceNumber).toBe(9); + expect(wrapper.children[2].sequenceNumber).toBe(10); + }); + }); + + it("should detect recursion of the transclude macro", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "<$transclude title='TiddlerOne'/>\n"} + ]); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "transclude", attributes: { + "title": {type: "string", value: "TiddlerOne"} + }} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + describe("should detect the recursion", function() { + // Test the rendering + expect(wrapper.innerHTML).toBe("Tiddler recursion error in transclude widget\n"); + }); + + }); + + it("should parse and render transclusions", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "Jolly Old World"}, + {title: "TiddlerTwo", text: "<$transclude title={{TiddlerThree}}/>"}, + {title: "TiddlerThree", text: "TiddlerOne"} + ]); + // Construct the widget node + var text = "My <$transclude title='TiddlerTwo'/> is Jolly" + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nMy Jolly Old World is Jolly

"); + }); + + it("should render the view widget", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "Jolly Old World"} + ]); + // Construct the widget node + var text = "<$view title='TiddlerOne'/>"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nJolly Old World

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(2); + // Change the transcluded tiddler + wiki.addTiddler({title: "TiddlerOne", text: "World-wide Jelly"}); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerOne"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nWorld-wide Jelly

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(3); + }); + }); + + it("should deal with the setvariable widget", function() { + var wiki = new $tw.Wiki(); + // Add some tiddlers + wiki.addTiddlers([ + {title: "TiddlerOne", text: "Jolly Old World"}, + {title: "TiddlerTwo", text: "<$transclude title={{TiddlerThree}}/>"}, + {title: "TiddlerThree", text: "TiddlerOne"}, + {title: "TiddlerFour", text: "TiddlerTwo"} + ]); + // Construct the widget node + var text = "My <$setvariable name='tiddlerTitle' value={{TiddlerFour}}><$transclude title={{!!title}}/> is Jolly" + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nMy Jolly Old World is Jolly

"); + // Change the transcluded tiddler + wiki.addTiddler({title: "TiddlerFour", text: "TiddlerOne"}); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerFour"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nMy Jolly Old World is Jolly

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(2); + expect(wrapper.children[0].children[1].sequenceNumber).toBe(5); + expect(wrapper.children[0].children[2].sequenceNumber).toBe(4); + }); + }); + + it("should deal with attributes specified as macro invocations", function() { + var wiki = new $tw.Wiki(); + // Construct the widget node + var text = "
>>Content
"; + var variables = { + myMacro: { + value: "My something $one$, $two$ or other $three$", + params: [ + {name: "one", "default": "paramOne"}, + {name: "two"}, + {name: "three", "default": "paramTwo"} + ] + } + }; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki,variables); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\n

\nContent

"); + }); + + it("should deal with the list widget", function() { + var wiki = new $tw.Wiki(); + // Add some tiddlers + wiki.addTiddlers([ + {title: "TiddlerOne", text: "Jolly Old World"}, + {title: "TiddlerTwo", text: "Worldly Old Jelly"}, + {title: "TiddlerThree", text: "Golly Gosh"}, + {title: "TiddlerFour", text: "Lemon Squash"} + ]); + // Construct the widget node + var text = "<$list><$view field='title'/>"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

"); + // Add another tiddler + wiki.addTiddler({title: "TiddlerFive", text: "Jalapeno Peppers"}); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerFive"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(6); + expect(wrapper.children[0].children[1].sequenceNumber).toBe(2); + expect(wrapper.children[0].children[2].sequenceNumber).toBe(3); + expect(wrapper.children[0].children[3].sequenceNumber).toBe(4); + expect(wrapper.children[0].children[4].sequenceNumber).toBe(5); + }); + // Remove a tiddler + wiki.deleteTiddler("TiddlerThree"); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerThree"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerTwo

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(6); + expect(wrapper.children[0].children[1].sequenceNumber).toBe(2); + expect(wrapper.children[0].children[2].sequenceNumber).toBe(3); + expect(wrapper.children[0].children[3].sequenceNumber).toBe(5); + }); + // Add it back a tiddler + wiki.addTiddler({title: "TiddlerThree", text: "Something"}); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerThree"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(6); + expect(wrapper.children[0].children[1].sequenceNumber).toBe(2); + expect(wrapper.children[0].children[2].sequenceNumber).toBe(3); + expect(wrapper.children[0].children[3].sequenceNumber).toBe(7); + expect(wrapper.children[0].children[4].sequenceNumber).toBe(5); + }); + }); + + it("should deal with the list widget and external templates", function() { + var wiki = new $tw.Wiki(); + // Add some tiddlers + wiki.addTiddlers([ + {title: "$:/myTemplate", text: "<$tiddler title=<>><$view field='title'/>"}, + {title: "TiddlerOne", text: "Jolly Old World"}, + {title: "TiddlerTwo", text: "Worldly Old Jelly"}, + {title: "TiddlerThree", text: "Golly Gosh"}, + {title: "TiddlerFour", text: "Lemon Squash"} + ]); + // Construct the widget node + var text = "<$list template='$:/myTemplate'>"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

"); + }); + + it("should deal with the list widget and empty lists", function() { + var wiki = new $tw.Wiki(); + // Construct the widget node + var text = "<$list emptyMessage='nothing'><$view field='title'/>"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nnothing

"); + }); + + it("should refresh lists that become empty", function() { + var wiki = new $tw.Wiki(); + // Add some tiddlers + wiki.addTiddlers([ + {title: "TiddlerOne", text: "Jolly Old World"}, + {title: "TiddlerTwo", text: "Worldly Old Jelly"}, + {title: "TiddlerThree", text: "Golly Gosh"}, + {title: "TiddlerFour", text: "Lemon Squash"} + ]); + // Construct the widget node + var text = "<$list emptyMessage='nothing'><$view field='title'/>"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

"); + // Get rid of the tiddlers + wiki.deleteTiddler("TiddlerOne"); + wiki.deleteTiddler("TiddlerTwo"); + wiki.deleteTiddler("TiddlerThree"); + wiki.deleteTiddler("TiddlerFour"); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerOne","TiddlerTwo","TiddlerThree","TiddlerFour"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nnothing

"); + }); + }); + +}); + +})(); diff --git a/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid b/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid index 870ee9d02..f1d8c7cc3 100644 --- a/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid +++ b/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid @@ -83,5 +83,5 @@ The following commands are available: <$list filter="[tag[command]sort[title]]"> !!! <$view field="title" format="link"/> - <$view field="text" format="wikified"/> + <$transclude /> diff --git a/nbld.sh b/nbld.sh new file mode 100755 index 000000000..c7dad786e --- /dev/null +++ b/nbld.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Testing the new widget mechanism + +# Set up the build output directory + +if [ -z "$TW5_BUILD_OUTPUT" ]; then + TW5_BUILD_OUTPUT=../jermolene.github.com +fi + +if [ ! -d "$TW5_BUILD_OUTPUT" ]; then + echo 'A valid TW5_BUILD_OUTPUT environment variable must be set' + exit 1 +fi + +echo "Using TW5_BUILD_OUTPUT as [$TW5_BUILD_OUTPUT]" + +# Build it + +node ./tiddlywiki.js \ + ./editions/tw5.com \ + --verbose \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \ + || exit 1 + +# Run tests + +./test.sh