diff --git a/2bld.sh b/2bld.sh index b432c3a9d..d95a7d5a9 100755 --- a/2bld.sh +++ b/2bld.sh @@ -11,7 +11,7 @@ mkdir -p tmp/tw2 node ./tiddlywiki.js \ editions/tw5.com \ --verbose \ - --rendertiddler TiddlyWiki2ReadMe editions/tw2/readme.md text/html \ + --new_rendertiddler TiddlyWiki2ReadMe editions/tw2/readme.md text/html \ || exit 1 # cook the TiddlyWiki 2.x.x index file @@ -20,7 +20,7 @@ node ./tiddlywiki.js \ editions/tw2 \ --verbose \ --load editions/tw2/source/tiddlywiki.com/index.html.recipe \ - --rendertiddler $:/core/templates/tiddlywiki2.template.html ./tmp/tw2/index.html text/plain \ + --new_rendertiddler $:/core/templates/tiddlywiki2.template.html ./tmp/tw2/index.html text/plain \ || exit 1 -opendiff tmp/tw2/index.html editions/tw2/target/index.2.6.5.html +diff -q tmp/tw2/index.html editions/tw2/target/prebuilt.html diff --git a/bld.sh b/bld.sh index 94b9092d0..6dce60d91 100755 --- a/bld.sh +++ b/bld.sh @@ -35,12 +35,12 @@ rm $TW5_BUILD_OUTPUT/static/* node ./tiddlywiki.js \ ./editions/tw5.com \ --verbose \ - --rendertiddler ReadMe ./readme.md text/html \ - --rendertiddler ContributingTemplate ./contributing.md text/html \ - --rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \ - --rendertiddler $:/core/templates/static.template.html $TW5_BUILD_OUTPUT/static.html text/plain \ - --rendertiddler $:/core/templates/static.template.css $TW5_BUILD_OUTPUT/static/static.css text/plain \ - --rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html $TW5_BUILD_OUTPUT/static text/plain \ + --new_rendertiddler ReadMe ./readme.md text/html \ + --new_rendertiddler ContributingTemplate ./contributing.md text/html \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \ + --new_rendertiddler $:/core/templates/static.template.html $TW5_BUILD_OUTPUT/static.html text/plain \ + --new_rendertiddler $:/core/templates/static.template.css $TW5_BUILD_OUTPUT/static/static.css text/plain \ + --new_rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html $TW5_BUILD_OUTPUT/static text/plain \ || exit 1 # Second, encrypted.html: a version of the main file encrypted with the password "password" @@ -49,7 +49,7 @@ node ./tiddlywiki.js \ ./editions/tw5.com \ --verbose \ --password password \ - --rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \ || exit 1 # Third, empty.html: empty wiki for reuse @@ -57,7 +57,7 @@ node ./tiddlywiki.js \ node ./tiddlywiki.js \ ./editions/empty \ --verbose \ - --rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/empty.html text/plain \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/empty.html text/plain \ || exit 1 # Fourth, tahoelafs.html: empty wiki with plugin for Tahoe-LAFS @@ -65,7 +65,7 @@ node ./tiddlywiki.js \ node ./tiddlywiki.js \ ./editions/tahoelafs \ --verbose \ - --rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/tahoelafs.html text/plain \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/tahoelafs.html text/plain \ || exit 1 # Fifth, d3demo.html: wiki to demo d3 plugin @@ -73,9 +73,25 @@ node ./tiddlywiki.js \ node ./tiddlywiki.js \ ./editions/d3demo \ --verbose \ - --rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/d3demo.html text/plain \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/d3demo.html text/plain \ || exit 1 -# Sixth, run the test edition to run the node.js tests and to generate test.html for tests in the browser +# Sixth, codemirrordemo.html: wiki to demo codemirror plugin + +node ./tiddlywiki.js \ + ./editions/codemirrordemo \ + --verbose \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/codemirrordemo.html text/plain \ + || exit 1 + +# Seventh, codemirrordemo.html: wiki to demo codemirror plugin + +node ./tiddlywiki.js \ + ./editions/markdowndemo \ + --verbose \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/markdowndemo.html text/plain \ + || exit 1 + +# Eighth, run the test edition to run the node.js tests and to generate test.html for tests in the browser ./test.sh diff --git a/boot/boot.js b/boot/boot.js index 8f4dd74f3..5b7edd7a7 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -107,6 +107,7 @@ children: array of further child nodes innerHTML: optional HTML for element class: class name(s) document: defaults to current document +eventListeners: array of event listeners (this option won't work until $tw.utils.addEventListeners() has been loaded) */ $tw.utils.domMaker = function(tag,options) { var doc = options.document || document; @@ -126,6 +127,9 @@ $tw.utils.domMaker = function(tag,options) { $tw.utils.each(options.attributes,function(attribute,name) { element.setAttribute(name,attribute); }); + if(options.eventListeners) { + $tw.utils.addEventListeners(element,options.eventListeners); + } return element; }; @@ -227,7 +231,8 @@ $tw.utils.stringifyDate = function(value) { $tw.utils.pad(value.getUTCMonth() + 1) + $tw.utils.pad(value.getUTCDate()) + $tw.utils.pad(value.getUTCHours()) + - $tw.utils.pad(value.getUTCMinutes()); + $tw.utils.pad(value.getUTCMinutes()) + + $tw.utils.pad(value.getUTCMilliseconds(),3); }; // Parse a date from a UTC YYYYMMDDHHMMSSMMM format string @@ -722,6 +727,7 @@ $tw.modules.define("$:/boot/tiddlerfields/created","tiddlerfield",{ }); $tw.modules.define("$:/boot/tiddlerfields/color","tiddlerfield",{ name: "color", + editTag: "input", editType: "color" }); $tw.modules.define("$:/boot/tiddlerfields/tags","tiddlerfield",{ diff --git a/contributing.md b/contributing.md index 7dbb858dd..312e9decf 100644 --- a/contributing.md +++ b/contributing.md @@ -1,60 +1,8 @@ -

-Contributing to -TiddlyWiki5

-
-

- -TiddlyWiki5 welcomes contributions to its code and documentation via -GitHub. Please take a moment to read these notes to help make the process as smooth as possible.

-Bug Reports

-From the perspective of the developers, a bug report that says little more than "it doesn't work" can be frustrating. For effective debugging, we need as much information as possible. At a minimum, please try to include:

-There's a lot of good material on the web about bug reports:

-Pull Requests

-Like other -OpenSource projects, -TiddlyWiki5 needs a signed -ContributorLicenseAgreement from individual contributors before contributions of code can be accepted. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the -UnaMesa Association (the legal entity that owns -TiddlyWiki on behalf of the community).

- -This is a first pass at a CLA for -TiddlyWiki. Please let us know if we missed something important. If we do have to make essential changes to the CLA, there is a possibility that all contributors will need to sign it again

-How to sign the CLA

-git clone https://github.com/Jermolene/TiddlyWiki5.git TiddlyWiki5
+

Contributing to TiddlyWiki5

TiddlyWiki5 welcomes contributions to its code and documentation via GitHub. Please take a moment to read these notes to help make the process as smooth as possible.

Bug Reports

From the perspective of the developers, a bug report that says little more than "it doesn't work" can be frustrating. For effective debugging, we need as much information as possible. At a minimum, please try to include:

  • A descriptive title
  • A summary
  • Steps to reproduce
  • Expected behaviour
  • Context (OS, browser etc.)

There's a lot of good material on the web about bug reports:

Pull Requests

Like other OpenSource projects, TiddlyWiki5 needs a signed contributor license agreement from individual contributors before contributions of code can be accepted. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the UnaMesa Association (the legal entity that owns TiddlyWiki on behalf of the community).

This is a first pass at a CLA for TiddlyWiki. Please let us know if we missed something important. If we do have to make essential changes to the CLA, there is a possibility that all contributors will need to sign it again

How to sign the CLA

git clone https://github.com/Jermolene/TiddlyWiki5.git TiddlyWiki5
 cd TiddlyWiki5
-git checkout -b sign-cla

- -Add your name and the date to cla-individual.md or cla-entity.md. Date format (YYYY/MM/DD) -eg: -Jeremy Ruston, @Jermolene, 2011/11/22

-git add .
+git checkout -b sign-cla

Add your name and the date to cla-individual.md or cla-entity.md. Date format (YYYY/MM/DD) +eg: Jeremy Ruston, @Jermolene, 2011/11/22

git add .
 git commit -m "sign contributor license agreement"
-git push origin sign-cla

- -Go to your github repo and create a pull request.

- -Thank you!

-Attribution

-The CLA documents used for this project where created using -Harmony Project Templates. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity" -

- -This file was automatically generated by -TiddlyWiki5 +git push origin sign-cla

Go to your github repo and create a pull request.

Thank you!

Attribution

The CLA documents used for this project where created using Harmony Project Templates. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity" +

This file was automatically generated by TiddlyWiki5

\ No newline at end of file diff --git a/core/modules/commands/rendertiddler.js b/core/modules/commands/new_rendertiddler.js old mode 100644 new mode 100755 similarity index 80% rename from core/modules/commands/rendertiddler.js rename to core/modules/commands/new_rendertiddler.js index b63d9855d..41dc5d66d --- a/core/modules/commands/rendertiddler.js +++ b/core/modules/commands/new_rendertiddler.js @@ -1,5 +1,5 @@ /*\ -title: $:/core/modules/commands/rendertiddler.js +title: $:/core/modules/commands/new_rendertiddler.js type: application/javascript module-type: command @@ -13,7 +13,7 @@ Command to render a tiddler and save it to a file "use strict"; exports.info = { - name: "rendertiddler", + name: "new_rendertiddler", synchronous: false }; @@ -33,7 +33,7 @@ Command.prototype.execute = function() { title = this.params[0], filename = this.params[1], type = this.params[2] || "text/html"; - fs.writeFile(filename,this.commander.wiki.renderTiddler(type,title),"utf8",function(err) { + fs.writeFile(filename,this.commander.wiki.new_renderTiddler(type,title),"utf8",function(err) { self.callback(err); }); return null; diff --git a/core/modules/commands/rendertiddlers.js b/core/modules/commands/new_rendertiddlers.js similarity index 78% rename from core/modules/commands/rendertiddlers.js rename to core/modules/commands/new_rendertiddlers.js index 1bb1fa448..26362f39d 100644 --- a/core/modules/commands/rendertiddlers.js +++ b/core/modules/commands/new_rendertiddlers.js @@ -1,5 +1,5 @@ /*\ -title: $:/core/modules/commands/rendertiddlers.js +title: $:/core/modules/commands/new_rendertiddlers.js type: application/javascript module-type: command @@ -12,8 +12,10 @@ Command to render several tiddlers to a folder of files /*global $tw: false */ "use strict"; +var widget = require("$:/core/modules/new_widgets/widget.js"); + exports.info = { - name: "rendertiddlers", + name: "new_rendertiddlers", synchronous: true }; @@ -36,13 +38,12 @@ Command.prototype.execute = function() { pathname = this.params[2], type = this.params[3] || "text/html", extension = this.params[4] || ".html", - parser = wiki.parseTiddler(template), tiddlers = wiki.filterTiddlers(filter); $tw.utils.each(tiddlers,function(title) { - var renderTree = new $tw.WikiRenderTree(parser,{wiki: wiki, context: {tiddlerTitle: title}, document: $tw.document}); - renderTree.execute(); + var parser = wiki.new_parseTiddler(template), + widgetNode = wiki.makeWidget(parser,{variables: {currentTiddler: title}}); var container = $tw.document.createElement("div"); - renderTree.renderInDom(container); + widgetNode.render(container,null); var text = type === "text/html" ? container.innerHTML : container.textContent; fs.writeFileSync(path.resolve(pathname,encodeURIComponent(title) + extension),text,"utf8"); }); diff --git a/core/modules/commands/server.js b/core/modules/commands/server.js index 78e3f5962..b71db0533 100644 --- a/core/modules/commands/server.js +++ b/core/modules/commands/server.js @@ -147,7 +147,7 @@ var Command = function(params,commander,callback) { path: /^\/$/, handler: function(request,response,state) { response.writeHead(200, {"Content-Type": state.server.get("serveType")}); - var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler")); + var text = state.wiki.new_renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler")); response.end(text,"utf8"); } }); diff --git a/core/modules/filters/modules.js b/core/modules/filters/modules.js new file mode 100644 index 000000000..bb9485876 --- /dev/null +++ b/core/modules/filters/modules.js @@ -0,0 +1,39 @@ +/*\ +title: $:/core/modules/filters/modules.js +type: application/javascript +module-type: filteroperator + +Filter operator for returning the titles of the modules of a given type in this wiki + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter function +*/ +exports.modules = function(source,operator,options) { + var results = [], + pushModules = function(type) { + $tw.utils.each($tw.modules.types[type],function(moduleInfo,moduleName) { + results.push(moduleName); + }); + }; + // Iterate through the source tiddlers + if($tw.utils.isArray(source)) { + $tw.utils.each(source,function(title) { + pushModules(title); + }); + } else { + $tw.utils.each(source,function(element,title) { + pushModules(title); + }); + } + results.sort(); + return results; +}; + +})(); diff --git a/core/modules/filters/moduletypes.js b/core/modules/filters/moduletypes.js new file mode 100644 index 000000000..67321caea --- /dev/null +++ b/core/modules/filters/moduletypes.js @@ -0,0 +1,27 @@ +/*\ +title: $:/core/modules/filters/moduletypes.js +type: application/javascript +module-type: filteroperator + +Filter operator for returning the names of the module types in this wiki + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter function +*/ +exports.moduletypes = function(source,operator,options) { + var results = []; + $tw.utils.each($tw.modules.types,function(moduleInfo,type) { + results.push(type); + }); + results.sort(); + return results; +}; + +})(); diff --git a/core/modules/macros/changecount.js b/core/modules/macros/changecount.js new file mode 100644 index 000000000..2d0f62e5a --- /dev/null +++ b/core/modules/macros/changecount.js @@ -0,0 +1,30 @@ +/*\ +title: $:/core/modules/macros/changecount.js +type: application/javascript +module-type: macro + +Macro to return the changecount for the current tiddler + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Information about this macro +*/ + +exports.name = "changecount"; + +exports.params = []; + +/* +Run the macro +*/ +exports.run = function() { + return this.wiki.getChangeCount(this.getVariable("currentTiddler")) + ""; +}; + +})(); diff --git a/core/modules/macros/makedatauri.js b/core/modules/macros/makedatauri.js new file mode 100644 index 000000000..418a95f6b --- /dev/null +++ b/core/modules/macros/makedatauri.js @@ -0,0 +1,44 @@ +/*\ +title: $:/core/modules/macros/makedatauri.js +type: application/javascript +module-type: macro + +Macro to convert the content of a tiddler to a data URI + +<> + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Information about this macro +*/ + +exports.name = "makedatauri"; + +exports.params = [ + {name: "text"}, + {name: "type"} +]; + +/* +Run the macro +*/ +exports.run = function(text,type) { + type = type || "text/vnd.tiddlywiki"; + var typeInfo = $tw.config.contentTypeInfo[type] || $tw.config.contentTypeInfo["text/plain"], + isBase64 = typeInfo.encoding === "base64", + parts = []; + parts.push("data:"); + parts.push(type); + parts.push(isBase64 ? ";base64" : ""); + parts.push(","); + parts.push(isBase64 ? text : encodeURIComponent(text)); + return parts.join(""); +}; + +})(); diff --git a/core/modules/macros/qualify.js b/core/modules/macros/qualify.js new file mode 100644 index 000000000..de9dc6c8e --- /dev/null +++ b/core/modules/macros/qualify.js @@ -0,0 +1,32 @@ +/*\ +title: $:/core/modules/macros/qualify.js +type: application/javascript +module-type: macro + +Macro to qualify a state tiddler title according + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Information about this macro +*/ + +exports.name = "qualify"; + +exports.params = [ + {name: "title"} +]; + +/* +Run the macro +*/ +exports.run = function(title) { + return title + "-" + this.getStateQualifier(); +}; + +})(); diff --git a/core/modules/macros/version.js b/core/modules/macros/version.js new file mode 100644 index 000000000..b0dd0b0d5 --- /dev/null +++ b/core/modules/macros/version.js @@ -0,0 +1,30 @@ +/*\ +title: $:/core/modules/macros/version.js +type: application/javascript +module-type: macro + +Macro to return the TiddlyWiki core version number + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Information about this macro +*/ + +exports.name = "version"; + +exports.params = []; + +/* +Run the macro +*/ +exports.run = function() { + return $tw.version; +}; + +})(); diff --git a/core/modules/new_widgets/browse.js b/core/modules/new_widgets/browse.js new file mode 100644 index 000000000..7a95b3725 --- /dev/null +++ b/core/modules/new_widgets/browse.js @@ -0,0 +1,78 @@ +/*\ +title: $:/core/modules/new_widgets/browse.js +type: application/javascript +module-type: new_widget + +Browse widget for browsing for files to import + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var BrowseWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +BrowseWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +BrowseWidget.prototype.render = function(parent,nextSibling) { + var self = this; + // Remember parent + this.parentDomNode = parent; + // Compute attributes and execute state + this.computeAttributes(); + this.execute(); + // Create element + var domNode = this.document.createElement("input"); + domNode.setAttribute("type","file"); + domNode.setAttribute("multiple","multiple"); + // Add a click event handler + domNode.addEventListener("change",function (event) { + self.wiki.readFiles(event.target.files,function(tiddlerFields) { + self.dispatchEvent({type: "tw-import-tiddlers", param: JSON.stringify([tiddlerFields])}); + }); + return false; + },false); + // Insert element + parent.insertBefore(domNode,nextSibling); + this.renderChildren(domNode,null); + this.domNodes.push(domNode); +}; + +/* +Compute the internal state of the widget +*/ +BrowseWidget.prototype.execute = function() { +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +BrowseWidget.prototype.refresh = function(changedTiddlers) { + return false; +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +BrowseWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.browse = BrowseWidget; + +})(); diff --git a/core/modules/new_widgets/button.js b/core/modules/new_widgets/button.js new file mode 100644 index 000000000..5ff07e255 --- /dev/null +++ b/core/modules/new_widgets/button.js @@ -0,0 +1,151 @@ +/*\ +title: $:/core/modules/new_widgets/button.js +type: application/javascript +module-type: new_widget + +Button widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var ButtonWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +ButtonWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +ButtonWidget.prototype.render = function(parent,nextSibling) { + var self = this; + // Remember parent + this.parentDomNode = parent; + // Compute attributes and execute state + this.computeAttributes(); + this.execute(); + // Create element + var domNode = this.document.createElement("button"); + // Assign classes + var classes = this["class"].split(" ") || []; + if(this.selectedClass) { + if(this.set && this.setTo && this.isSelected()) { + $tw.utils.pushTop(classes,this.selectedClass.split(" ")); + } + if(this.popup && this.isPoppedUp()) { + $tw.utils.pushTop(classes,this.selectedClass.split(" ")); + } + } + domNode.className = classes.join(" "); + // Assign classes + if(this.style) { + domNode.setAttribute("style",this.style); + } + // Add a click event handler + domNode.addEventListener("click",function (event) { + var handled = false; + if(self.message) { + self.dispatchMessage(event); + handled = true; + } + if(self.popup) { + self.triggerPopup(event); + handled = true; + } + if(self.set) { + self.setTiddler(); + handled = true; + } + if(handled) { + event.preventDefault(); + event.stopPropagation(); + } + return handled; + },false); + // Insert element + parent.insertBefore(domNode,nextSibling); + this.renderChildren(domNode,null); + this.domNodes.push(domNode); +}; + +ButtonWidget.prototype.isSelected = function() { + var tiddler = this.wiki.getTiddler(this.set); + return tiddler ? tiddler.fields.text === this.setTo : this.defaultSetValue === this.setTo; +}; + +ButtonWidget.prototype.isPoppedUp = function() { + var tiddler = this.wiki.getTiddler(this.popup); + var result = tiddler && tiddler.fields.text ? $tw.popup.readPopupState(this.popup,tiddler.fields.text) : false; + return result; +}; + +ButtonWidget.prototype.dispatchMessage = function(event) { + this.dispatchEvent({type: this.message, param: this.param, tiddlerTitle: this.getVariable("currentTiddler")}); +}; + +ButtonWidget.prototype.triggerPopup = function(event) { + $tw.popup.triggerPopup({ + domNode: this.domNodes[0], + title: this.popup, + wiki: this.wiki + }); +}; + +ButtonWidget.prototype.setTiddler = function() { + var tiddler = this.wiki.getTiddler(this.set); + this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: this.set, text: this.setTo})); +}; + +/* +Compute the internal state of the widget +*/ +ButtonWidget.prototype.execute = function() { + // Get attributes + this.message = this.getAttribute("message"); + this.param = this.getAttribute("param"); + this.set = this.getAttribute("set"); + this.setTo = this.getAttribute("setTo"); + this.popup = this.getAttribute("popup"); + this.hover = this.getAttribute("hover"); + this["class"] = this.getAttribute("class",""); + this.style = this.getAttribute("style"); + this.selectedClass = this.getAttribute("selectedClass"); + this.defaultSetValue = this.getAttribute("default"); + // Make child widgets + this.makeChildWidgets(); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +ButtonWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes["class"] || changedAttributes.selectedClass || changedAttributes.style || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) { + this.refreshSelf(); + return true; + } + return this.refreshChildren(changedTiddlers); +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +ButtonWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.button = ButtonWidget; + +})(); diff --git a/core/modules/new_widgets/checkbox.js b/core/modules/new_widgets/checkbox.js new file mode 100644 index 000000000..92b443722 --- /dev/null +++ b/core/modules/new_widgets/checkbox.js @@ -0,0 +1,118 @@ +/*\ +title: $:/core/modules/new_widgets/checkbox.js +type: application/javascript +module-type: new_widget + +Checkbox widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var CheckboxWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +CheckboxWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +CheckboxWidget.prototype.render = function(parent,nextSibling) { + // Save the parent dom node + this.parentDomNode = parent; + // Compute our attributes + this.computeAttributes(); + // Execute our logic + this.execute(); + // Create our elements + this.labelDomNode = this.document.createElement("label"); + this.inputDomNode = this.document.createElement("input"); + this.inputDomNode.setAttribute("type","checkbox"); + if(this.getValue()) { + this.inputDomNode.setAttribute("checked","true"); + } + this.labelDomNode.appendChild(this.inputDomNode); + this.spanDomNode = this.document.createElement("span"); + this.labelDomNode.appendChild(this.spanDomNode); + // Add a click event handler + $tw.utils.addEventListeners(this.inputDomNode,[ + {name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"} + ]); + // Insert the label into the DOM and render any children + parent.insertBefore(this.labelDomNode,nextSibling); + this.renderChildren(this.spanDomNode,null); + this.domNodes.push(this.labelDomNode); +}; + +CheckboxWidget.prototype.getValue = function() { + var tiddler = this.wiki.getTiddler(this.checkboxTitle); + return tiddler ? tiddler.hasTag(this.checkboxTag) : false; +}; + +CheckboxWidget.prototype.handleChangeEvent = function(event) { + var checked = this.inputDomNode.checked, + tiddler = this.wiki.getTiddler(this.checkboxTitle); + if(tiddler && tiddler.hasTag(this.checkboxTag) !== checked) { + var newTags = tiddler.fields.tags.slice(0), + pos = newTags.indexOf(this.checkboxTag); + if(pos !== -1) { + newTags.splice(pos,1); + } + if(checked) { + newTags.push(this.checkboxTag); + } + this.wiki.addTiddler(new $tw.Tiddler(tiddler,{tags: newTags},this.wiki.getModificationFields())); + } +}; + +/* +Compute the internal state of the widget +*/ +CheckboxWidget.prototype.execute = function() { + // Get the parameters from the attributes + this.checkboxTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); + this.checkboxTag = this.getAttribute("tag"); + // Make the child widgets + this.makeChildWidgets(); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +CheckboxWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.tiddler || changedAttributes.tag || changedAttributes["class"]) { + this.refreshSelf(); + return true; + } else { + var refreshed = false; + if(changedTiddlers[this.checkboxTitle]) { + this.inputDomNode.checked = this.getValue(); + refreshed = true; + } + return this.refreshChildren(changedTiddlers) || refreshed; + } +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +CheckboxWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.checkbox = CheckboxWidget; + +})(); diff --git a/core/modules/new_widgets/count.js b/core/modules/new_widgets/count.js new file mode 100644 index 000000000..8780b8d59 --- /dev/null +++ b/core/modules/new_widgets/count.js @@ -0,0 +1,81 @@ +/*\ +title: $:/core/modules/new_widgets/count.js +type: application/javascript +module-type: new_widget + +Count widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var CountWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +CountWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +CountWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + var textNode = this.document.createTextNode(this.currentCount); + parent.insertBefore(textNode,nextSibling); + this.domNodes.push(textNode); +}; + +/* +Compute the internal state of the widget +*/ +CountWidget.prototype.execute = function() { + // Get parameters from our attributes + this.filter = this.getAttribute("filter"); + // Execute the filter + if(this.filter) { + this.currentCount = this.wiki.filterTiddlers(this.filter,this.getVariable("currentTiddler")).length; + } else { + this.currentCount = undefined; + } +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +CountWidget.prototype.refresh = function(changedTiddlers) { + // Re-execute the filter to get the count + var oldCount = this.currentCount; + this.execute(); + if(this.currentCount !== oldCount) { + // Regenerate and rerender the widget and replace the existing DOM node + this.refreshSelf(); + return true; + } else { + return false; + } + +}; + +/* +Remove any DOM nodes created by this widget +*/ +CountWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.count = CountWidget; + +})(); diff --git a/core/modules/new_widgets/dropzone.js b/core/modules/new_widgets/dropzone.js new file mode 100644 index 000000000..78cc0e279 --- /dev/null +++ b/core/modules/new_widgets/dropzone.js @@ -0,0 +1,190 @@ +/*\ +title: $:/core/modules/new_widgets/dropzone.js +type: application/javascript +module-type: new_widget + +Dropzone widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var DropZoneWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +DropZoneWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +DropZoneWidget.prototype.render = function(parent,nextSibling) { + var self = this; + // Remember parent + this.parentDomNode = parent; + // Compute attributes and execute state + this.computeAttributes(); + this.execute(); + // Create element + var domNode = this.document.createElement("div"); + domNode.className = "tw-dropzone"; + // Add event handlers + $tw.utils.addEventListeners(domNode,[ + {name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"}, + {name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"}, + {name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"}, + {name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"}, + {name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"} + ]); + domNode.addEventListener("click",function (event) { + },false); + // Insert element + parent.insertBefore(domNode,nextSibling); + this.renderChildren(domNode,null); + this.domNodes.push(domNode); +}; + +DropZoneWidget.prototype.handleDragEnterEvent = function(event) { + // We count enter/leave events + this.dragEnterCount = (this.dragEnterCount || 0) + 1; + // If we're entering for the first time we need to apply highlighting + if(this.dragEnterCount === 1) { + $tw.utils.addClass(this.domNodes[0],"tw-dragover"); + } + // Tell the browser that we're ready to handle the drop + event.preventDefault(); + // Tell the browser not to ripple the drag up to any parent drop handlers + event.stopPropagation(); +}; + +DropZoneWidget.prototype.handleDragOverEvent = function(event) { + // Tell the browser that we're still interested in the drop + event.preventDefault(); + event.dataTransfer.dropEffect = "copy"; // Explicitly show this is a copy +}; + +DropZoneWidget.prototype.handleDragLeaveEvent = function(event) { + // Reduce the enter count + this.dragEnterCount = (this.dragEnterCount || 0) - 1; + // Remove highlighting if we're leaving externally + if(this.dragEnterCount <= 0) { + $tw.utils.removeClass(this.domNodes[0],"tw-dragover"); + } +}; + +DropZoneWidget.prototype.handleDropEvent = function(event) { + var self = this, + dataTransfer = event.dataTransfer; + // Reset the enter count + this.dragEnterCount = 0; + // Remove highlighting + $tw.utils.removeClass(this.domNodes[0],"tw-dragover"); + // Try to import the various data types we understand + this.importData(dataTransfer); + // Import any files in the drop + this.wiki.readFiles(dataTransfer.files,function(tiddlerFields) { + self.dispatchEvent({type: "tw-import-tiddlers", param: JSON.stringify([tiddlerFields])}); + }); + // Tell the browser that we handled the drop + event.preventDefault(); + // Stop the drop ripple up to any parent handlers + event.stopPropagation(); +}; + +DropZoneWidget.prototype.importData = function(dataTransfer) { + for(var t=0; t 0 && newWidth !== this.currCanvas.width) { + this.changeCanvasSize(newWidth,this.currCanvas.height); + } + // Update the input controls + this.updateSize(); +}; + +EditBitmapWidget.prototype.handleHeightChangeEvent = function(event) { + // Get the new width + var newHeight = parseInt(this.heightDomNode.value,10); + // Update if necessary + if(newHeight > 0 && newHeight !== this.currCanvas.height) { + this.changeCanvasSize(this.currCanvas.width,newHeight); + } + // Update the input controls + this.updateSize(); +}; + +EditBitmapWidget.prototype.handleTouchStartEvent = function(event) { + this.brushDown = true; + this.strokeStart(event.touches[0].clientX,event.touches[0].clientY); + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleTouchMoveEvent = function(event) { + if(this.brushDown) { + this.strokeMove(event.touches[0].clientX,event.touches[0].clientY); + } + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleTouchEndEvent = function(event) { + if(this.brushDown) { + this.brushDown = false; + this.strokeEnd(); + } + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleMouseDownEvent = function(event) { + this.strokeStart(event.clientX,event.clientY); + this.brushDown = true; + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleMouseMoveEvent = function(event) { + if(this.brushDown) { + this.strokeMove(event.clientX,event.clientY); + event.preventDefault(); + event.stopPropagation(); + return false; + } + return true; +}; + +EditBitmapWidget.prototype.handleMouseUpEvent = function(event) { + if(this.brushDown) { + this.brushDown = false; + this.strokeEnd(); + event.preventDefault(); + event.stopPropagation(); + return false; + } + return true; +}; + +EditBitmapWidget.prototype.adjustCoordinates = function(x,y) { + var canvasRect = this.canvasDomNode.getBoundingClientRect(), + scale = this.canvasDomNode.width/canvasRect.width; + return {x: (x - canvasRect.left) * scale, y: (y - canvasRect.top) * scale}; +}; + +EditBitmapWidget.prototype.strokeStart = function(x,y) { + // Start off a new stroke + this.stroke = [this.adjustCoordinates(x,y)]; +}; + +EditBitmapWidget.prototype.strokeMove = function(x,y) { + var ctx = this.canvasDomNode.getContext("2d"), + t; + // Add the new position to the end of the stroke + this.stroke.push(this.adjustCoordinates(x,y)); + // Redraw the previous image + ctx.drawImage(this.currCanvas,0,0); + // Render the stroke + ctx.strokeStyle = "#ff0"; + ctx.lineWidth = 3; + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + ctx.beginPath(); + ctx.moveTo(this.stroke[0].x,this.stroke[0].y); + for(t=1; t;base64," + var dataURL = this.canvasDomNode.toDataURL(tiddler.fields.type,1.0), + posColon = dataURL.indexOf(":"), + posSemiColon = dataURL.indexOf(";"), + posComma = dataURL.indexOf(","), + type = dataURL.substring(posColon+1,posSemiColon), + text = dataURL.substring(posComma+1); + var update = {type: type, text: text}; + this.wiki.addTiddler(new $tw.Tiddler(tiddler,update)); + } +}; + +exports["edit-bitmap"] = EditBitmapWidget; + +})(); diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js new file mode 100644 index 000000000..b79406f2d --- /dev/null +++ b/core/modules/new_widgets/edit-text.js @@ -0,0 +1,259 @@ +/*\ +title: $:/core/modules/new_widgets/edit-text.js +type: application/javascript +module-type: new_widget + +Edit-text widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var MIN_TEXT_AREA_HEIGHT = 100; // Minimum height of textareas in pixels + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var EditTextWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +EditTextWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +EditTextWidget.prototype.render = function(parent,nextSibling) { + var self = this; + // Save the parent dom node + this.parentDomNode = parent; + // Compute our attributes + this.computeAttributes(); + // Execute our logic + this.execute(); + // Create our element + var domNode = this.document.createElement(this.editTag); + if(this.editType) { + domNode.setAttribute("type",this.editType); + } + if(this.editPlaceholder) { + domNode.setAttribute("placeholder",this.editPlaceholder); + } + // Assign classes + domNode.className = this.editClass; + // Set the text + var editInfo = this.getEditInfo(); + if(this.editTag === "textarea") { + domNode.appendChild(this.document.createTextNode(editInfo.value)); + } else { + domNode.setAttribute("value",editInfo.value) + } + // Add an input event handler + $tw.utils.addEventListeners(domNode,[ + {name: "focus", handlerObject: this, handlerMethod: "handleFocusEvent"}, + {name: "input", handlerObject: this, handlerMethod: "handleInputEvent"} + ]); + // Insert the element into the DOM + parent.insertBefore(domNode,nextSibling); + this.domNodes.push(domNode); + if(this.postRender) { + this.postRender(); + } + // Fix height + this.fixHeight(); +}; + +/* +Get the tiddler being edited and current value +*/ +EditTextWidget.prototype.getEditInfo = function() { + // Get the edit value + var tiddler = this.wiki.getTiddler(this.editTitle), + value; + if(this.editIndex) { + value = this.wiki.extractTiddlerDataItem(this.editTitle,this.editIndex,this.editDefault); + } else { + // Get the current tiddler and the field name + if(tiddler) { + // If we've got a tiddler, the value to display is the field string value + value = tiddler.getFieldString(this.editField); + } else { + // Otherwise, we need to construct a default value for the editor + switch(this.editField) { + case "text": + value = "Type the text for the tiddler '" + this.editTitle + "'"; + break; + case "title": + value = this.editTitle; + break; + default: + value = ""; + break; + } + if(this.editDefault !== undefined) { + value = this.editDefault; + } + } + } + return {tiddler: tiddler, value: value}; +}; + +/* +Compute the internal state of the widget +*/ +EditTextWidget.prototype.execute = function() { + // Get our parameters + this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); + this.editField = this.getAttribute("field","text"); + this.editIndex = this.getAttribute("index"); + this.editDefault = this.getAttribute("default"); + this.editClass = this.getAttribute("class"); + this.editPlaceholder = this.getAttribute("placeholder"); + this.editFocusPopup = this.getAttribute("focusPopup"); + // Get the editor element tag and type + var tag,type; + if(this.editField === "text") { + tag = "textarea"; + } else { + tag = "input"; + var fieldModule = $tw.Tiddler.fieldModules[this.editField]; + if(fieldModule && fieldModule.editTag) { + tag = fieldModule.editTag; + } + if(fieldModule && fieldModule.editType) { + type = fieldModule.editType; + } + type = type || "text"; + } + // Get the rest of our parameters + this.editTag = this.getAttribute("tag",tag); + this.editType = this.getAttribute("type",type); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +EditTextWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + // Completely rerender if any of our attributes have changed + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index) { + this.refreshSelf(); + return true; + } else if(changedTiddlers[this.editTitle]) { + this.updateEditor(this.getEditInfo().value); + return true; + } + return false; +}; + +/* +Update the editor with new text. This method is separate from updateEditorDomNode() +so that subclasses can override updateEditor() and still use updateEditorDomNode() +*/ +EditTextWidget.prototype.updateEditor = function(text) { + this.updateEditorDomNode(text); +}; + +/* +Update the editor dom node with new text +*/ +EditTextWidget.prototype.updateEditorDomNode = function(text) { + // Replace the edit value if the tiddler we're editing has changed + var domNode = this.domNodes[0]; + if(!domNode.isTiddlyWikiFakeDom) { + if(this.document.activeElement !== domNode) { + domNode.value = text; + } + // Fix the height if needed + this.fixHeight(); + } +}; + +/* +Fix the height of textareas to fit their content +*/ +EditTextWidget.prototype.fixHeight = function() { + var self = this, + domNode = this.domNodes[0]; + if(domNode && !domNode.isTiddlyWikiFakeDom && this.editTag === "textarea") { + $tw.utils.nextTick(function() { + // Resize the textarea to fit its content, preserving scroll position + var scrollPosition = $tw.utils.getScrollPosition(), + scrollTop = scrollPosition.y; + // Set its height to auto so that it snaps to the correct height + domNode.style.height = "auto"; + // Calculate the revised height + var newHeight = Math.max(domNode.scrollHeight + domNode.offsetHeight - domNode.clientHeight,MIN_TEXT_AREA_HEIGHT); + // Only try to change the height if it has changed + if(newHeight !== domNode.offsetHeight) { + domNode.style.height = newHeight + "px"; + // Make sure that the dimensions of the textarea are recalculated + $tw.utils.forceLayout(domNode); + // Check that the scroll position is still visible before trying to scroll back to it + scrollTop = Math.min(scrollTop,self.document.body.scrollHeight - window.innerHeight); + window.scrollTo(scrollPosition.x,scrollTop); + } + }); + } +}; + +/* +Handle a dom "input" event +*/ +EditTextWidget.prototype.handleInputEvent = function(event) { + this.saveChanges(this.domNodes[0].value); + this.fixHeight(); + return true; +}; + +EditTextWidget.prototype.handleFocusEvent = function(event) { + if(this.editFocusPopup) { + $tw.popup.triggerPopup({ + domNode: this.domNodes[0], + title: this.editFocusPopup, + wiki: this.wiki, + force: true + }); + } + return true; +}; + +EditTextWidget.prototype.saveChanges = function(text) { + if(this.editField) { + var tiddler = this.wiki.getTiddler(this.editTitle); + if(!tiddler) { + tiddler = new $tw.Tiddler({title: this.editTitle}); + } + var oldValue = tiddler.getFieldString(this.editField); + if(text !== oldValue) { + var update = {}; + update[this.editField] = text; + this.wiki.addTiddler(new $tw.Tiddler(tiddler,update)); + } + } else { + var data = this.wiki.getTiddlerData(this.editTitle,{}); + if(data[this.editIndex] !== text) { + data[this.editIndex] = text; + this.wiki.setTiddlerData(this.editTitle,data); + } + } +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +EditTextWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports["edit-text"] = EditTextWidget; + +})(); diff --git a/core/modules/new_widgets/edit.js b/core/modules/new_widgets/edit.js new file mode 100644 index 000000000..6fbee958a --- /dev/null +++ b/core/modules/new_widgets/edit.js @@ -0,0 +1,94 @@ +/*\ +title: $:/core/modules/new_widgets/edit.js +type: application/javascript +module-type: new_widget + +Edit widget is a meta-widget chooses the appropriate actual editting widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var EditWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +EditWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +EditWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +// Mappings from content type to editor type +// TODO: This information should be configurable/extensible +var editorTypeMappings = { + "text/vnd.tiddlywiki": "text", + "image/svg+xml": "text", + "image/jpg": "bitmap", + "image/jpeg": "bitmap", + "image/gif": "bitmap", + "image/png": "bitmap" +}; + +/* +Compute the internal state of the widget +*/ +EditWidget.prototype.execute = function() { + // Get our parameters + this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); + this.editField = this.getAttribute("field","text"); + this.editIndex = this.getAttribute("index"); + this.editClass = this.getAttribute("class"); + // Get the content type of the thing we're editing + var type; + if(this.editField === "text") { + var tiddler = this.wiki.getTiddler(this.editTitle); + if(tiddler) { + type = tiddler.fields.type; + } + } + type = type || "text/vnd.tiddlywiki"; + // Choose the appropriate edit widget + var editorType = editorTypeMappings[type] || "text"; + // Make the child widgets + this.makeChildWidgets([{ + type: "edit-" + editorType, + attributes: { + title: {type: "string", value: this.editTitle}, + field: {type: "string", value: this.editField}, + index: {type: "string", value: this.editIndex}, + "class": {type: "string", value: this.editClass} + } + }]); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +EditWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index) { + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +exports.edit = EditWidget; + +})(); diff --git a/core/modules/new_widgets/element.js b/core/modules/new_widgets/element.js new file mode 100755 index 000000000..5ee1ed761 --- /dev/null +++ b/core/modules/new_widgets/element.js @@ -0,0 +1,85 @@ +/*\ +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.createElementNS(this.namespace,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() { + // Select the namespace for the tag + var tagNamespaces = { + svg: "http://www.w3.org/2000/svg", + math: "http://www.w3.org/1998/Math/MathML", + body: "http://www.w3.org/1999/xhtml" + }; + this.namespace = tagNamespaces[this.parseTreeNode.tag]; + if(this.namespace) { + this.setVariable("namespace",this.namespace); + } else { + this.namespace = this.getVariable("namespace",{defaultValue: "http://www.w3.org/1999/xhtml"}); + } + // Make the child widgets + 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]); + } + return this.refreshChildren(changedTiddlers) || 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/encrypt.js b/core/modules/new_widgets/encrypt.js new file mode 100644 index 000000000..777dcb89b --- /dev/null +++ b/core/modules/new_widgets/encrypt.js @@ -0,0 +1,85 @@ +/*\ +title: $:/core/modules/new_widgets/encrypt.js +type: application/javascript +module-type: new_widget + +Encrypt widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var EncryptWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +EncryptWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +EncryptWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + var textNode = this.document.createTextNode(this.encryptedText); + parent.insertBefore(textNode,nextSibling); + this.domNodes.push(textNode); +}; + +/* +Compute the internal state of the widget +*/ +EncryptWidget.prototype.execute = function() { + // Get parameters from our attributes + this.filter = this.getAttribute("filter","[!is[system]]"); + // Encrypt the filtered tiddlers + var tiddlers = this.wiki.filterTiddlers(this.filter), + json = {}, + self = this; + $tw.utils.each(tiddlers,function(title) { + var tiddler = self.wiki.getTiddler(title), + jsonTiddler = {}; + for(var f in tiddler.fields) { + jsonTiddler[f] = tiddler.getFieldString(f); + } + json[title] = jsonTiddler; + }); + this.encryptedText = $tw.utils.htmlEncode($tw.crypto.encrypt(JSON.stringify(json))); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +EncryptWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(), + affectedTiddlers = this.wiki.filterTiddlers(this.filter,null,changedTiddlers); + if(changedAttributes.filter || affectedTiddlers.length > 0) { + this.refreshSelf(); + return true; + } else { + return false; + } +}; + +/* +Remove any DOM nodes created by this widget +*/ +EncryptWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.encrypt = EncryptWidget; + +})(); 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/fieldmangler.js b/core/modules/new_widgets/fieldmangler.js new file mode 100644 index 000000000..94f5f5eb9 --- /dev/null +++ b/core/modules/new_widgets/fieldmangler.js @@ -0,0 +1,110 @@ +/*\ +title: $:/core/modules/new_widgets/fieldmangler.js +type: application/javascript +module-type: new_widget + +Field mangler widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var FieldManglerWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); + this.addEventListeners([ + {type: "tw-remove-field", handler: "handleRemoveFieldEvent"}, + {type: "tw-add-field", handler: "handleAddFieldEvent"}, + {type: "tw-remove-tag", handler: "handleRemoveTagEvent"}, + {type: "tw-add-tag", handler: "handleAddTagEvent"} + ]); +}; + +/* +Inherit from the base widget class +*/ +FieldManglerWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +FieldManglerWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +FieldManglerWidget.prototype.execute = function() { + // Get our parameters + this.mangleTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); + // 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 +*/ +FieldManglerWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.tiddler) { + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +FieldManglerWidget.prototype.handleRemoveFieldEvent = function(event) { + var tiddler = this.wiki.getTiddler(this.mangleTitle), + deletion = {}; + deletion[event.param] = undefined; + this.wiki.addTiddler(new $tw.Tiddler(tiddler,deletion)); + return true; +}; + +FieldManglerWidget.prototype.handleAddFieldEvent = function(event) { + var tiddler = this.wiki.getTiddler(this.mangleTitle); + if(tiddler && typeof event.param === "string" && event.param !== "" && !$tw.utils.hop(tiddler.fields,event.param)) { + var addition = {}; + addition[event.param] = ""; + this.wiki.addTiddler(new $tw.Tiddler(tiddler,addition)); + } + return true; +}; + +FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) { + var tiddler = this.wiki.getTiddler(this.mangleTitle); + if(tiddler && tiddler.fields.tags) { + var p = tiddler.fields.tags.indexOf(event.param); + if(p !== -1) { + var modification = {}; + modification.tags = (tiddler.fields.tags || []).slice(0); + modification.tags.splice(p,1); + this.wiki.addTiddler(new $tw.Tiddler(tiddler,modification)); + } + } + return true; +}; + +FieldManglerWidget.prototype.handleAddTagEvent = function(event) { + var tiddler = this.wiki.getTiddler(this.mangleTitle); + if(tiddler && typeof event.param === "string" && event.param !== "") { + var modification = {}; + modification.tags = (tiddler.fields.tags || []).slice(0); + $tw.utils.pushTop(modification.tags,event.param); + this.wiki.addTiddler(new $tw.Tiddler(tiddler,modification)); + } + return true; +}; + +exports.fieldmangler = FieldManglerWidget; + +})(); diff --git a/core/modules/new_widgets/fields.js b/core/modules/new_widgets/fields.js new file mode 100755 index 000000000..9e71ef150 --- /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 + +Fields 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("currentTiddler")); + 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: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ + {type: "text", text: title} + ]}]}]; + } + } + // Return the list item + return {type: "listitem", itemTitle: title, variableName: this.variableName, 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.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) { + this.refreshSelf(); + return true; + } else { + // Handle any changes to the list + var hasChanged = this.handleListChanges(changedTiddlers); + // Handle any changes to the history stack + if(this.historyTitle && changedTiddlers[this.historyTitle]) { + this.handleHistoryChanges(); + } + return hasChanged; + } +}; + +/* +Handle any changes to the history list +*/ +ListWidget.prototype.handleHistoryChanges = function() { + // Get the history data + var newHistory = this.wiki.getTiddlerData(this.historyTitle,[]); + // Ignore any entries of the history that match the previous history + var entry = 0; + while(entry < newHistory.length && entry < this.history.length && newHistory[entry].title === this.history[entry].title) { + entry++; + } + // Navigate forwards to each of the new tiddlers + while(entry < newHistory.length) { + if(this.storyview && this.storyview.navigateTo) { + this.storyview.navigateTo(newHistory[entry]); + } + entry++; + } + // Update the history + this.history = newHistory; +}; + +/* +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 + for(t=this.children.length-1; t>=0; t--) { + this.removeListItem(t); + } + var nextSibling = this.findNextSiblingDomNode(); + 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) { + // Create, insert and render the new child widgets + var widget = this.makeChildWidget(this.makeItemTemplate(title)); + widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work + this.children.splice(index,0,widget); + var nextSibling = widget.findNextSiblingDomNode(); + widget.render(this.parentDomNode,nextSibling); + // Animate the insertion if required + if(this.storyview && this.storyview.insert) { + this.storyview.insert(widget); + } + return true; +}; + +/* +Remove the specified list item +*/ +ListWidget.prototype.removeListItem = function(index) { + var widget = this.children[index]; + // Animate the removal if required + if(this.storyview && this.storyview.remove) { + this.storyview.remove(widget); + } else { + widget.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(this.parseTreeNode.variableName,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/macrocall.js b/core/modules/new_widgets/macrocall.js new file mode 100644 index 000000000..f100f4059 --- /dev/null +++ b/core/modules/new_widgets/macrocall.js @@ -0,0 +1,84 @@ +/*\ +title: $:/core/modules/new_widgets/macrocall.js +type: application/javascript +module-type: new_widget + +Macrocall widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var MacroCallWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +MacroCallWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +MacroCallWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +MacroCallWidget.prototype.execute = function() { + // Get the parse type if specified + this.parseType = this.getAttribute("$type","text/vnd.tiddlywiki"); + this.renderOutput = this.getAttribute("$output","text/html"); + // Merge together the parameters specified in the parse tree with the specified attributes + var params = this.parseTreeNode.params ? this.parseTreeNode.params.slice(0) : []; + $tw.utils.each(this.attributes,function(attribute,name) { + if(name.charAt(0) !== "$") { + params.push({name: name, value: attribute}); + } + }); + // Get the macro value + var text = this.getVariable(this.parseTreeNode.name || this.getAttribute("$name"),{params: params}), + parseTreeNodes; + // Are we rendering to HTML? + if(this.renderOutput === "text/html") { + // If so we'll return the parsed macro + var parser = this.wiki.new_parseText(this.parseType,text, + {parseAsInline: !this.parseTreeNode.isBlock}); + parseTreeNodes = parser ? parser.tree : []; + } else { + // Otherwise, we'll render the text + var plainText = this.wiki.new_renderText("text/plain",this.parseType,text,{parentWidget: this}); + parseTreeNodes = [{type: "text", text: plainText}]; + } + // Construct the child widgets + this.makeChildWidgets(parseTreeNodes); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +MacroCallWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if($tw.utils.count(changedAttributes) > 0) { + // Rerender ourselves + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +exports.macrocall = MacroCallWidget; + +})(); diff --git a/core/modules/widgets/navigator.js b/core/modules/new_widgets/navigator.js old mode 100644 new mode 100755 similarity index 50% rename from core/modules/widgets/navigator.js rename to core/modules/new_widgets/navigator.js index 92ffe1d4f..91d86e90f --- a/core/modules/widgets/navigator.js +++ b/core/modules/new_widgets/navigator.js @@ -1,9 +1,9 @@ /*\ -title: $:/core/modules/widgets/navigator.js +title: $:/core/modules/new_widgets/navigator.js type: application/javascript -module-type: widget +module-type: new_widget -Implements the navigator widget. +Navigator widget \*/ (function(){ @@ -12,51 +12,69 @@ Implements the navigator widget. /*global $tw: false */ "use strict"; -var NavigatorWidget = function(renderer) { - // Save state - this.renderer = renderer; - // Generate child nodes - this.generate(); +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"}, + {type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"}, + ]); }; -NavigatorWidget.prototype.generate = function() { +/* +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.renderer.getAttribute("story"); - this.historyTitle = this.renderer.getAttribute("history"); - // Set the element - this.tag = "div"; - this.attributes = { - "class": "tw-navigator" - }; - this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children); - this.events = [ - {name: "tw-navigate", handlerObject: this, handlerMethod: "handleNavigateEvent"}, - {name: "tw-edit-tiddler", handlerObject: this, handlerMethod: "handleEditTiddlerEvent"}, - {name: "tw-delete-tiddler", handlerObject: this, handlerMethod: "handleDeleteTiddlerEvent"}, - {name: "tw-save-tiddler", handlerObject: this, handlerMethod: "handleSaveTiddlerEvent"}, - {name: "tw-cancel-tiddler", handlerObject: this, handlerMethod: "handleCancelTiddlerEvent"}, - {name: "tw-close-tiddler", handlerObject: this, handlerMethod: "handleCloseTiddlerEvent"}, - {name: "tw-close-all-tiddlers", handlerObject: this, handlerMethod: "handleCloseAllTiddlersEvent"}, - {name: "tw-new-tiddler", handlerObject: this, handlerMethod: "handleNewTiddlerEvent"} - ]; + this.storyTitle = this.getAttribute("story"); + this.historyTitle = this.getAttribute("history"); + // Construct the child widgets + this.makeChildWidgets(); }; -NavigatorWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { - // We don't need to refresh ourselves, so just refresh any child nodes - $tw.utils.each(this.children,function(node) { - if(node.refreshInDom) { - node.refreshInDom(changedTiddlers); - } - }); +/* +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.renderer.renderTree.wiki.getTiddlerList(this.storyTitle); + this.storyList = this.wiki.getTiddlerList(this.storyTitle); }; NavigatorWidget.prototype.saveStoryList = function() { - var storyTiddler = this.renderer.renderTree.wiki.getTiddler(this.storyTitle); - this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler({ + var storyTiddler = this.wiki.getTiddler(this.storyTitle); + this.wiki.addTiddler(new $tw.Tiddler({ title: this.storyTitle },storyTiddler,{list: this.storyList})); }; @@ -70,7 +88,9 @@ NavigatorWidget.prototype.findTitleInStory = function(title,defaultIndex) { return defaultIndex; }; -// Navigate to a specified tiddler +/* +Handle a tw-navigate event +*/ NavigatorWidget.prototype.handleNavigateEvent = function(event) { if(this.storyTitle) { // Update the story tiddler if specified @@ -89,11 +109,10 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) { } // Add a new record to the top of the history stack if(this.historyTitle) { - var historyList = this.renderer.renderTree.wiki.getTiddlerData(this.historyTitle,[]); + var historyList = this.wiki.getTiddlerData(this.historyTitle,[]); historyList.push({title: event.navigateTo, fromPageRect: event.navigateFromClientRect}); - this.renderer.renderTree.wiki.setTiddlerData(this.historyTitle,historyList); + this.wiki.setTiddlerData(this.historyTitle,historyList); } - event.stopPropagation(); return false; }; @@ -106,7 +125,6 @@ NavigatorWidget.prototype.handleCloseTiddlerEvent = function(event) { this.storyList.splice(slot,1); this.saveStoryList(); } - event.stopPropagation(); return false; }; @@ -114,7 +132,6 @@ NavigatorWidget.prototype.handleCloseTiddlerEvent = function(event) { NavigatorWidget.prototype.handleCloseAllTiddlersEvent = function(event) { this.storyList = []; this.saveStoryList(); - event.stopPropagation(); return false; }; @@ -122,44 +139,37 @@ NavigatorWidget.prototype.handleCloseAllTiddlersEvent = function(event) { NavigatorWidget.prototype.handleEditTiddlerEvent = function(event) { this.getStoryList(); // Replace the specified tiddler with a draft in edit mode - for(var t=0; t=0; t--) { + // Replace the first story instance of the original tiddler name with the draft title if(this.storyList[t] === event.tiddlerTitle) { - // Compute the title for the draft - var draftTitle = this.generateDraftTitle(event.tiddlerTitle); - this.storyList[t] = draftTitle; - // Get the current value of the tiddler we're editing - var tiddler = this.renderer.renderTree.wiki.getTiddler(event.tiddlerTitle); - // Save the initial value of the draft tiddler - this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler( - { - text: "Type the text for the tiddler '" + event.tiddlerTitle + "'" - }, - tiddler, - { - title: draftTitle, - "draft.title": event.tiddlerTitle, - "draft.of": event.tiddlerTitle - }, - this.renderer.renderTree.wiki.getModificationFields() - )); + if(!gotOne) { + this.storyList[t] = draftTiddler.fields.title; + gotOne = true; + } else { + this.storyList.splice(t,1); + } + } else if(this.storyList[t] === draftTiddler.fields.title) { + // Remove any existing references to the draft + this.storyList.splice(t,1); } } this.saveStoryList(); - event.stopPropagation(); return false; }; // Delete a tiddler NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) { - // Get the tiddler title we're deleting - var tiddler = this.renderer.renderTree.wiki.getTiddler(event.tiddlerTitle); + // Get the tiddler we're deleting + var tiddler = this.wiki.getTiddler(event.tiddlerTitle); // Check if the tiddler we're deleting is in draft mode if(tiddler.hasField("draft.title")) { // Delete the original tiddler - this.renderer.renderTree.wiki.deleteTiddler(tiddler.fields["draft.of"]); + this.wiki.deleteTiddler(tiddler.fields["draft.of"]); } // Delete this tiddler - this.renderer.renderTree.wiki.deleteTiddler(event.tiddlerTitle); + this.wiki.deleteTiddler(event.tiddlerTitle); // Remove the closed tiddler from the story this.getStoryList(); // Look for tiddler with this title to close @@ -168,10 +178,41 @@ NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) { this.storyList.splice(slot,1); this.saveStoryList(); } - event.stopPropagation(); return false; }; +/* +Create/reuse the draft tiddler for a given title +*/ +NavigatorWidget.prototype.getDraftTiddler = function(targetTitle) { + // See if there is already a draft tiddler for this tiddler + var drafts = []; + this.wiki.forEachTiddler(function(title,tiddler) { + if(tiddler.fields["draft.title"] && tiddler.fields["draft.of"] === targetTitle) { + drafts.push(tiddler); + } + }); + if(drafts.length > 0) { + return drafts[0]; + } + // Get the current value of the tiddler we're editing + var tiddler = this.wiki.getTiddler(targetTitle), + draftTitle = this.generateDraftTitle(targetTitle); + // Save the initial value of the draft tiddler + var draftTiddler = new $tw.Tiddler( + {text: "Type the text for the tiddler '" + targetTitle + "'"}, + tiddler, + { + title: draftTitle, + "draft.title": targetTitle, + "draft.of": targetTitle + }, + this.wiki.getModificationFields() + ); + this.wiki.addTiddler(draftTiddler); + return draftTiddler; +}; + /* Generate a title for the draft of a given tiddler */ @@ -180,7 +221,7 @@ NavigatorWidget.prototype.generateDraftTitle = function(title) { do { var draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'"; c++; - } while(this.renderer.renderTree.wiki.tiddlerExists(draftTitle)); + } while(this.wiki.tiddlerExists(draftTitle)); return draftTitle; }; @@ -190,28 +231,28 @@ NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) { var storyTiddlerModified = false; // We have to special case saving the story tiddler itself 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 +options: see below +Options include +params: array of {name:, value:} for each parameter +defaultValue: default value if the variable is not defined +*/ +Widget.prototype.getVariable = function(name,options) { + options = options || {}; + var actualParams = options.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 we get to the root then look for a macro module + if(!node) { + return this.evaluateMacroModule(name,actualParams,options.defaultValue); + } + // Get the value + var value = node.variables[name].value || ""; + // Substitute any parameters specified in the definition + value = this.substituteVariableParameters(value,node.variables[name].params,actualParams); + value = this.substituteVariableReferences(value); + return value; +}; + +Widget.prototype.substituteVariableParameters = function(text,formalParams,actualParams) { + if(formalParams) { + 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.parentWidget.children[index-1]; + } + } + return null; +}; + +/* +Render the children of this widget into the DOM +*/ +Widget.prototype.renderChildren = function(parent,nextSibling) { + $tw.utils.each(this.children,function(childWidget) { + childWidget.render(parent,nextSibling); + }); +}; + +/* +Add a list of event listeners from an array [{type:,handler:},...] +*/ +Widget.prototype.addEventListeners = function(listeners) { + var self = this; + $tw.utils.each(listeners,function(listenerInfo) { + self.addEventListener(listenerInfo.type,listenerInfo.handler); + }); +}; + +/* +Add an event listener +*/ +Widget.prototype.addEventListener = function(type,handler) { + var self = this; + if(typeof handler === "string") { // The handler is a method name on this widget + this.eventListeners[type] = function(event) { + return self[handler].call(self,event); + }; + } else { // The handler is a function + this.eventListeners[type] = function(event) { + return handler.call(self,event); + } + + } +}; + +/* +Dispatch an event to a widget. If the widget doesn't handle the event then it is also dispatched to the parent widget +*/ +Widget.prototype.dispatchEvent = function(event) { + // Dispatch the event if this widget handles it + var listener = this.eventListeners[event.type]; + if(listener) { + // Don't propagate the event if the listener returned false + if(!listener(event)) { + return false; + } + } + // Dispatch the event to the parent widget + if(this.parentWidget) { + return this.parentWidget.dispatchEvent(event); + } + return true; +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +Widget.prototype.refresh = function(changedTiddlers) { + return this.refreshChildren(changedTiddlers); +}; + +/* +Rebuild a previously rendered widget +*/ +Widget.prototype.refreshSelf = function() { + var nextSibling = this.findNextSiblingDomNode(); + this.removeChildDomNodes(); + this.render(this.parentDomNode,nextSibling); +}; + +/* +Refresh all the children of a widget +*/ +Widget.prototype.refreshChildren = function(changedTiddlers) { + var self = this, + refreshed = false; + $tw.utils.each(this.children,function(childWidget) { + refreshed = childWidget.refresh(changedTiddlers) || refreshed; + }); + return refreshed; +}; + +/* +Find the next sibling in the DOM to this widget. This is done by scanning the widget tree through all next siblings and their descendents that share the same parent DOM node +*/ +Widget.prototype.findNextSiblingDomNode = function(startIndex) { + // Refer to this widget by its index within its parents children + var parent = this.parentWidget, + index = startIndex !== undefined ? startIndex : parent.children.indexOf(this); +if(index === -1) { + throw "node not found in parents children"; +} + // Look for a DOM node in the later siblings + while(++index < parent.children.length) { + var domNode = parent.children[index].findFirstDomNode(); + if(domNode) { + return domNode; + } + } + // Go back and look for later siblings of our parent if it has the same parent dom node + var grandParent = parent.parentWidget; + if(grandParent && parent.parentDomNode === this.parentDomNode) { + index = grandParent.children.indexOf(parent); + return parent.findNextSiblingDomNode(index); + } + return null; +}; + +/* +Find the first DOM node generated by a widget or its children +*/ +Widget.prototype.findFirstDomNode = function() { + // Return the first dom node of this widget, if we've got one + if(this.domNodes.length > 0) { + return this.domNodes[0]; + } + // Otherwise, recursively call our children + for(var t=0; t tag - var renderTree = new $tw.WikiRenderTree(parser,{wiki: $tw.wiki, document: $tw.document}); - renderTree.execute(); - var container = $tw.document.createElement("div"); - renderTree.renderInDom(container); + var widgetNode = this.parser.wiki.makeWidget(parser), + container = $tw.document.createElement("div"); + widgetNode.render(container,null); var text = renderType === "text/html" ? container.innerHTML : container.textContent; return [{ type: "element", diff --git a/core/modules/rendertree/renderers/element.js b/core/modules/rendertree/renderers/element.js deleted file mode 100644 index 722e24db8..000000000 --- a/core/modules/rendertree/renderers/element.js +++ /dev/null @@ -1,212 +0,0 @@ -/*\ -title: $:/core/modules/rendertree/renderers/element.js -type: application/javascript -module-type: wikirenderer - -Element renderer - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Element widget. A degenerate widget that renders ordinary HTML elements -*/ -var ElementWidget = function(renderer) { - this.renderer = renderer; - this.tag = this.renderer.parseTreeNode.tag; - this.attributes = this.renderer.attributes; - this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children); - this.events = this.renderer.parseTreeNode.events; -}; - -ElementWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { - // Check if any of our attribute dependencies have changed - if($tw.utils.count(changedAttributes) > 0) { - // Update our attributes - this.renderer.assignAttributes(); - } - // Refresh any child nodes - $tw.utils.each(this.children,function(node) { - if(node.refreshInDom) { - node.refreshInDom(changedTiddlers); - } - }); -}; - -/* -Element renderer -*/ -var ElementRenderer = function(renderTree,parentRenderer,parseTreeNode) { - // Store state information - this.renderTree = renderTree; - this.parentRenderer = parentRenderer; - this.parseTreeNode = parseTreeNode; - // Initialise widget classes - if(!this.widgetClasses) { - ElementRenderer.prototype.widgetClasses = $tw.modules.applyMethods("widget"); - } - // Select the namespace for the tag - var tagNameSpaces = { - svg: "http://www.w3.org/2000/svg", - math: "http://www.w3.org/1998/Math/MathML" - }; - this.namespace = tagNameSpaces[this.parseTreeNode.tag]; - if(this.namespace) { - this.context = this.context || {}; - this.context.namespace = this.namespace; - } else { - this.namespace = this.renderTree.getContextVariable(this.parentRenderer,"namespace","http://www.w3.org/1999/xhtml"); - } - // Get the context tiddler title - this.tiddlerTitle = this.renderTree.getContextVariable(this.parentRenderer,"tiddlerTitle"); - // Compute our dependencies - this.dependencies = {}; - var self = this; - $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { - if(attribute.type === "indirect") { - var tr = $tw.utils.parseTextReference(attribute.textReference); - self.dependencies[tr.title ? tr.title : self.tiddlerTitle] = true; - } - }); - // Compute our attributes - this.attributes = {}; - this.computeAttributes(); - // Create the parasite widget object if required - if(this.parseTreeNode.tag.charAt(0) === "$") { - // Choose the class - var WidgetClass = this.widgetClasses[this.parseTreeNode.tag.substr(1)]; - // Instantiate the widget - if(WidgetClass) { - this.widget = new WidgetClass(this); - } else { - WidgetClass = this.widgetClasses.error; - if(WidgetClass) { - this.widget = new WidgetClass(this,"Unknown widget '<" + this.parseTreeNode.tag + ">'"); - } - } - } - // If we haven't got a widget, use the generic HTML element widget - if(!this.widget) { - this.widget = new ElementWidget(this); - } -}; - -ElementRenderer.prototype.computeAttributes = function() { - var changedAttributes = {}, - self = this, - value; - $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { - if(attribute.type === "indirect") { - value = self.renderTree.wiki.getTextReference(attribute.textReference,"",self.tiddlerTitle); - } else if(attribute.type === "macro") { - // Get the macro definition - var macro = self.renderTree.findMacroDefinition(self.parentRenderer,attribute.value.name); - if(!macro) { - value = ""; - } else { - // Substitute the macro parameters - value = self.renderTree.substituteParameters(macro,attribute.value); - // Parse the text and render it as text - value = self.renderTree.wiki.renderText("text/plain","text/vnd.tiddlywiki",value,self.context); - } - } else { // String attribute - value = attribute.value; - } - // Check whether the attribute has changed - if(self.attributes[name] !== value) { - self.attributes[name] = value; - changedAttributes[name] = true; - } - }); - return changedAttributes; -}; - -ElementRenderer.prototype.hasAttribute = function(name) { - return $tw.utils.hop(this.attributes,name); -}; - -ElementRenderer.prototype.getAttribute = function(name,defaultValue) { - if($tw.utils.hop(this.attributes,name)) { - return this.attributes[name]; - } else { - return defaultValue; - } -}; - -ElementRenderer.prototype.renderInDom = function() { - // Check if our widget is providing an element - if(this.widget.tag) { - // Create the element - this.domNode = this.renderTree.document.createElementNS(this.namespace,this.widget.tag); - // Assign any specified event handlers - $tw.utils.addEventListeners(this.domNode,this.widget.events); - // Assign the attributes - this.assignAttributes(); - // Render any child nodes - var self = this; - $tw.utils.each(this.widget.children,function(node) { - if(node.renderInDom) { - self.domNode.appendChild(node.renderInDom()); - } - }); - // Call postRenderInDom if the widget provides it and we're in the browser - if($tw.browser && this.widget.postRenderInDom) { - this.widget.postRenderInDom(); - } - // Return the dom node - return this.domNode; - } else { - // If we're not generating an element, just render our first child - return this.widget.children[0].renderInDom(); - } -}; - -ElementRenderer.prototype.assignAttributes = function() { - var self = this; - $tw.utils.each(this.widget.attributes,function(v,a) { - if(v !== undefined) { - if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className? - self.domNode.className = v.join(" "); - } else if (typeof v === "object") { // ...or objects other than style? - for(var p in v) { - self.domNode.style[$tw.utils.unHyphenateCss(p)] = v[p]; - } - } else { - // Setting certain attributes can cause a DOM error (eg xmlns on the svg element) - try { - self.domNode.setAttributeNS(null,a,v); - } catch(e) { - } - } - } - }); -}; - -ElementRenderer.prototype.refreshInDom = function(changedTiddlers) { - // Update our attributes if required - var changedAttributes = {}; - if($tw.utils.checkDependencies(this.dependencies,changedTiddlers)) { - changedAttributes = this.computeAttributes(); - } - // Check if the widget has a refreshInDom method - if(this.widget.refreshInDom) { - // Let the widget refresh itself - this.widget.refreshInDom(changedAttributes,changedTiddlers); - } else { - // If not, assign the attributes and refresh any child nodes - this.assignAttributes(); - $tw.utils.each(this.widget.children,function(node) { - if(node.refreshInDom) { - node.refreshInDom(changedTiddlers); - } - }); - } -}; - -exports.element = ElementRenderer - -})(); diff --git a/core/modules/rendertree/renderers/entity.js b/core/modules/rendertree/renderers/entity.js deleted file mode 100644 index 7556513f7..000000000 --- a/core/modules/rendertree/renderers/entity.js +++ /dev/null @@ -1,31 +0,0 @@ -/*\ -title: $:/core/modules/rendertree/renderers/entity.js -type: application/javascript -module-type: wikirenderer - -Entity renderer - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Entity renderer -*/ -var EntityRenderer = function(renderTree,parentRenderer,parseTreeNode) { - // Store state information - this.renderTree = renderTree; - this.parentRenderer = parentRenderer; - this.parseTreeNode = parseTreeNode; -}; - -EntityRenderer.prototype.renderInDom = function() { - return this.renderTree.document.createTextNode($tw.utils.entityDecode(this.parseTreeNode.entity)); -}; - -exports.entity = EntityRenderer - -})(); diff --git a/core/modules/rendertree/renderers/macrocall.js b/core/modules/rendertree/renderers/macrocall.js deleted file mode 100644 index 1b9cfb3ca..000000000 --- a/core/modules/rendertree/renderers/macrocall.js +++ /dev/null @@ -1,65 +0,0 @@ -/*\ -title: $:/core/modules/rendertree/renderers/macrocall.js -type: application/javascript -module-type: wikirenderer - -Macro call renderer - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Macro call renderer -*/ -var MacroCallRenderer = function(renderTree,parentRenderer,parseTreeNode) { - // Store state information - this.renderTree = renderTree; - this.parentRenderer = parentRenderer; - this.parseTreeNode = parseTreeNode; - // Find the macro definition - var macro = this.renderTree.findMacroDefinition(this.parentRenderer,this.parseTreeNode.name); - // Insert an error message if we couldn't find the macro - var childTree; - if(!macro) { - childTree = [{type: "text", text: "<>"}]; - } else { - // Substitute the macro parameters - var text = this.renderTree.substituteParameters(macro,this.parseTreeNode); - // Parse the text - childTree = this.renderTree.wiki.parseText("text/vnd.tiddlywiki",text,{parseAsInline: !this.parseTreeNode.isBlock}).tree; - } - // Create the renderers for the child nodes - this.children = this.renderTree.createRenderers(this,childTree); -}; - -MacroCallRenderer.prototype.renderInDom = function() { - // Create the element - this.domNode = this.renderTree.document.createElement(this.parseTreeNode.isBlock ? "div" : "span"); - this.domNode.setAttribute("data-macro-name",this.parseTreeNode.name); - // Render any child nodes - var self = this; - $tw.utils.each(this.children,function(node,index) { - if(node.renderInDom) { - self.domNode.appendChild(node.renderInDom()); - } - }); - // Return the dom node - return this.domNode; -}; - -MacroCallRenderer.prototype.refreshInDom = function(changedTiddlers) { - // Refresh any child nodes - $tw.utils.each(this.children,function(node) { - if(node.refreshInDom) { - node.refreshInDom(changedTiddlers); - } - }); -}; - -exports.macrocall = MacroCallRenderer - -})(); diff --git a/core/modules/rendertree/renderers/macrodef.js b/core/modules/rendertree/renderers/macrodef.js deleted file mode 100644 index f7336a215..000000000 --- a/core/modules/rendertree/renderers/macrodef.js +++ /dev/null @@ -1,30 +0,0 @@ -/*\ -title: $:/core/modules/rendertree/renderers/macrodef.js -type: application/javascript -module-type: wikirenderer - -Macro definition renderer - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Macro definition renderer -*/ -var MacroDefRenderer = function(renderTree,parentRenderer,parseTreeNode) { - // Store state information - this.renderTree = renderTree; - this.parentRenderer = parentRenderer; - this.parseTreeNode = parseTreeNode; - // Save the macro definition into the context of the rendertree - this.renderTree.context.macroDefinitions = this.renderTree.context.macroDefinitions || {}; - this.renderTree.context.macroDefinitions[this.parseTreeNode.name] = this.parseTreeNode; -}; - -exports.macrodef = MacroDefRenderer - -})(); diff --git a/core/modules/rendertree/renderers/raw.js b/core/modules/rendertree/renderers/raw.js deleted file mode 100644 index 98c77e788..000000000 --- a/core/modules/rendertree/renderers/raw.js +++ /dev/null @@ -1,33 +0,0 @@ -/*\ -title: $:/core/modules/rendertree/renderers/raw.js -type: application/javascript -module-type: wikirenderer - -Raw HTML renderer - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Raw HTML renderer -*/ -var RawRenderer = function(renderTree,parentRenderer,parseTreeNode) { - // Store state information - this.renderTree = renderTree; - this.parentRenderer = parentRenderer; - this.parseTreeNode = parseTreeNode; -}; - -RawRenderer.prototype.renderInDom = function() { - var domNode = this.renderTree.document.createElement("div"); - domNode.innerHTML = this.parseTreeNode.html; - return domNode; -}; - -exports.raw = RawRenderer - -})(); diff --git a/core/modules/rendertree/renderers/text.js b/core/modules/rendertree/renderers/text.js deleted file mode 100644 index b915d938c..000000000 --- a/core/modules/rendertree/renderers/text.js +++ /dev/null @@ -1,31 +0,0 @@ -/*\ -title: $:/core/modules/rendertree/renderers/text.js -type: application/javascript -module-type: wikirenderer - -Text renderer - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Text renderer -*/ -var TextRenderer = function(renderTree,parentRenderer,parseTreeNode) { - // Store state information - this.renderTree = renderTree; - this.parentRenderer = parentRenderer; - this.parseTreeNode = parseTreeNode; -}; - -TextRenderer.prototype.renderInDom = function() { - return this.renderTree.document.createTextNode(this.parseTreeNode.text); -}; - -exports.text = TextRenderer - -})(); diff --git a/core/modules/rendertree/wikirendertree.js b/core/modules/rendertree/wikirendertree.js deleted file mode 100644 index dd7fc6b4c..000000000 --- a/core/modules/rendertree/wikirendertree.js +++ /dev/null @@ -1,198 +0,0 @@ -/*\ -title: $:/core/modules/rendertree/wikirendertree.js -type: application/javascript -module-type: global - -Wiki text render tree - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Create a render tree object for a parse tree - parser: reference to the parse tree to be rendered - options: see below -Options include: - wiki: mandatory reference to wiki associated with this render tree - context: optional hashmap of context variables (see below) - parentRenderer: optional reference to a parent renderer node for the context chain - document: optional document object to use instead of global document -Context variables include: - tiddlerTitle: title of the tiddler providing the context - templateTitle: title of the tiddler providing the current template - macroDefinitions: hashmap of macro definitions -*/ -var WikiRenderTree = function(parser,options) { - this.parser = parser; - this.wiki = options.wiki; - this.context = options.context || {}; - this.parentRenderer = options.parentRenderer; - this.document = options.document; - // Hashmap of the renderer classes - if(!this.rendererClasses) { - WikiRenderTree.prototype.rendererClasses = $tw.modules.applyMethods("wikirenderer"); - } -}; - -/* -Generate the full render tree for this parse tree -*/ -WikiRenderTree.prototype.execute = function() { - this.rendererTree = this.createRenderers(this,this.parser.tree); -}; - -/* -Create an array of renderers for an array of parse tree nodes -*/ -WikiRenderTree.prototype.createRenderers = function(parentRenderer,parseTreeNodes) { - var rendererNodes = []; - if(parseTreeNodes) { - for(var t=0; t widget -*/ -function findTitleNode(node) { - var t,r; - // Return true if this node is a view widget with the field attribute set to "title" - if(node instanceof $tw.WikiRenderTree.prototype.rendererClasses.element) { - if(node.widget instanceof $tw.WikiRenderTree.prototype.rendererClasses.element.prototype.widgetClasses.view && node.attributes.field === "title") { - return node; - } - if(node.widget.children) { - for(t=0; t