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

\nMy Jolly Old World is Jolly

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

\nJolly Old World

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

\nWorld-wide Jelly

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

\nMy Jolly Old World is Jolly

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

\nMy Jolly Old World is Jolly

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

\n

\nContent

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

\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

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

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

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

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerTwo

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

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

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

\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

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

\nnothing

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

\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

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

\nnothing

"); + }); + }); + +}); + +})(); diff --git a/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid b/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid index 870ee9d02..f1d8c7cc3 100644 --- a/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid +++ b/editions/tw5.com/tiddlers/editions/TiddlyWiki5_Node_Edition.tid @@ -83,5 +83,5 @@ The following commands are available: <$list filter="[tag[command]sort[title]]"> !!! <$view field="title" format="link"/> - <$view field="text" format="wikified"/> + <$transclude /> diff --git a/nbld.sh b/nbld.sh new file mode 100755 index 000000000..c7dad786e --- /dev/null +++ b/nbld.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Testing the new widget mechanism + +# Set up the build output directory + +if [ -z "$TW5_BUILD_OUTPUT" ]; then + TW5_BUILD_OUTPUT=../jermolene.github.com +fi + +if [ ! -d "$TW5_BUILD_OUTPUT" ]; then + echo 'A valid TW5_BUILD_OUTPUT environment variable must be set' + exit 1 +fi + +echo "Using TW5_BUILD_OUTPUT as [$TW5_BUILD_OUTPUT]" + +# Build it + +node ./tiddlywiki.js \ + ./editions/tw5.com \ + --verbose \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \ + || exit 1 + +# Run tests + +./test.sh From b0503cf709c848631c81780baa6181eaf2ff9089 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 13 Oct 2013 20:14:31 +0100 Subject: [PATCH 002/183] Fixes to make SVG and MathML elements work properly --- core/modules/new_widgets/element.js | 14 +++++++++++++- core/modules/new_widgets/widget.js | 5 +++-- core/modules/utils/fakedom.js | 5 +++-- editions/test/tiddlers/tests/test-widget.js | 16 ++++++++++++++-- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/core/modules/new_widgets/element.js b/core/modules/new_widgets/element.js index 99215a3cd..034490bb9 100755 --- a/core/modules/new_widgets/element.js +++ b/core/modules/new_widgets/element.js @@ -30,7 +30,7 @@ ElementWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); this.execute(); - var domNode = this.document.createElement(this.parseTreeNode.tag); + var domNode = this.document.createElementNS(this.namespace,this.parseTreeNode.tag); this.assignAttributes(domNode); parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); @@ -41,6 +41,18 @@ ElementWidget.prototype.render = function(parent,nextSibling) { 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" + }; + this.namespace = tagNamespaces[this.parseTreeNode.tag]; + if(this.namespace) { + this.setVariable("namespace",this.namespace); + } else { + this.namespace = this.getVariable("namespace",null,"http://www.w3.org/1999/xhtml"); + } + // Make the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index c02bc391d..72b0f4049 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -74,14 +74,15 @@ Get the prevailing value of a context variable name: name of variable params: array of {name:, value:} for each parameter */ -Widget.prototype.getVariable = function(name,params) { +Widget.prototype.getVariable = function(name,params,defaultValue) { + params = params || []; // Search up the widget tree for the variable name var node = this; while(node && !$tw.utils.hop(node.variables,name)) { node = node.parentWidget; } if(!node) { - return undefined; + return defaultValue; } // Get the value var value = node.variables[name].value; diff --git a/core/modules/utils/fakedom.js b/core/modules/utils/fakedom.js index 8231f3cde..fd9be9e90 100755 --- a/core/modules/utils/fakedom.js +++ b/core/modules/utils/fakedom.js @@ -26,12 +26,13 @@ var TW_TextNode = function(text) { this.textContent = text; }; -var TW_Element = function(tag) { +var TW_Element = function(tag,namespace) { bumpSequenceNumber(this); this.tag = tag; this.attributes = {}; this.isRaw = false; this.children = []; + this.namespaceURI = namespace || "http://www.w3.org/1999/xhtml"; }; TW_Element.prototype.setAttribute = function(name,value) { @@ -160,7 +161,7 @@ var document = { sequenceNumber = value; }, createElementNS: function(namespace,tag) { - return new TW_Element(tag); + return new TW_Element(tag,namespace); }, createElement: function(tag) { return new TW_Element(tag); diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js index bf3068962..470ba7ee7 100755 --- a/editions/test/tiddlers/tests/test-widget.js +++ b/editions/test/tiddlers/tests/test-widget.js @@ -24,8 +24,8 @@ describe("Widget module", function() { }); } - function parseText(text,wiki) { - var parser = wiki.new_parseText("text/vnd.tiddlywiki",text); + function parseText(text,wiki,options) { + var parser = wiki.new_parseText("text/vnd.tiddlywiki",text,options); return parser ? {type: "widget", children: parser.tree} : undefined; } @@ -169,6 +169,18 @@ describe("Widget module", function() { }); + it("should deal with SVG elements", function() { + var wiki = new $tw.Wiki(); + // Construct the widget node + var text = "\n"; + var widgetNode = createWidgetNode(parseText(text,wiki,{parseAsInline:true}),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("\n\n\n"); + expect(wrapper.firstChild.namespaceURI).toBe("http://www.w3.org/2000/svg"); + }); + it("should parse and render transclusions", function() { var wiki = new $tw.Wiki(); // Add a tiddler From 987890c085e4a4fe16ef5027579a09dad589b2fd Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 13 Oct 2013 21:31:00 +0100 Subject: [PATCH 003/183] Add button widget --- core/modules/new_widgets/button.js | 149 ++++++++++++++++++++++++ core/modules/new_widgets/link.js | 8 +- core/modules/new_widgets/navigator.js | 12 ++ core/modules/new_widgets/tempwidgets.js | 1 - core/modules/new_widgets/tiddler.js | 4 +- core/ui/PageTemplate.tid | 2 +- core/ui/SideBar.tid | 2 +- core/ui/ViewTemplate.tid | 2 +- themes/tiddlywiki/snowwhite/base.tid | 2 +- 9 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 core/modules/new_widgets/button.js diff --git a/core/modules/new_widgets/button.js b/core/modules/new_widgets/button.js new file mode 100644 index 000000000..ab8988632 --- /dev/null +++ b/core/modules/new_widgets/button.js @@ -0,0 +1,149 @@ +/*\ +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.set && this.setTo && this.selectedClass) { + if(this.isSelected()) { + classes.push(this.selectedClass.split(" ")); + } + } + domNode.className = classes.join(" "); + // 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 title = this.set; + if(this.qualifyTiddlerTitles) { + title = title + "-" + this.getStateQualifier(); + } + var tiddler = this.wiki.getTiddler(title); + return tiddler ? tiddler.fields.text === this.setTo : false; +}; + +ButtonWidget.prototype.dispatchMessage = function(event) { + this.dispatchEvent({type: this.message, param: this.param, tiddlerTitle: this.getVariable("tiddlerTitle")}); +}; + +ButtonWidget.prototype.triggerPopup = function(event) { + var title = this.popup; + if(this.qualifyTiddlerTitles) { + title = title + "-" + this.getStateQualifier(); + } + $tw.popup.triggerPopup({ + domNode: this.domNodes[0], + title: title, + wiki: this.wiki + }); +}; + +ButtonWidget.prototype.setTiddler = function() { + var title = this.set; + if(this.qualifyTiddlerTitles) { + title = title + "-" + this.getStateQualifier(); + } + var tiddler = this.wiki.getTiddler(title); + this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: title, 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.qualifyTiddlerTitles = this.getAttribute("qualifyTiddlerTitles"); + this["class"] = this.getAttribute("class",""); + this.selectedClass = this.getAttribute("selectedClass"); + // 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.qualifyTiddlerTitles || changedAttributes["class"] || changedAttributes.selectedClass) { + 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/link.js b/core/modules/new_widgets/link.js index 6ad90887a..7c74d3c5d 100755 --- a/core/modules/new_widgets/link.js +++ b/core/modules/new_widgets/link.js @@ -58,13 +58,7 @@ LinkWidget.prototype.render = function(parent,nextSibling) { type: "tw-navigate", navigateTo: self.to, navigateFromNode: self, - navigateFromClientRect: { - top: bounds.top, - left: bounds.left, - width: bounds.width, - right: bounds.right, - bottom: bounds.bottom, - height: bounds.height + navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height } }); event.preventDefault(); diff --git a/core/modules/new_widgets/navigator.js b/core/modules/new_widgets/navigator.js index cc7122c5e..448ce29a2 100755 --- a/core/modules/new_widgets/navigator.js +++ b/core/modules/new_widgets/navigator.js @@ -115,6 +115,18 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) { return false; }; +// Close a specified tiddler +NavigatorWidget.prototype.handleCloseTiddlerEvent = function(event) { + this.getStoryList(); + // Look for tiddlers with this title to close + var slot = this.findTitleInStory(event.tiddlerTitle,-1); + if(slot !== -1) { + this.storyList.splice(slot,1); + this.saveStoryList(); + } + return false; +}; + exports.navigator = NavigatorWidget; })(); diff --git a/core/modules/new_widgets/tempwidgets.js b/core/modules/new_widgets/tempwidgets.js index c3413bfaa..c5bd3ab2f 100755 --- a/core/modules/new_widgets/tempwidgets.js +++ b/core/modules/new_widgets/tempwidgets.js @@ -14,7 +14,6 @@ Temporary shim widgets var Widget = require("$:/core/modules/new_widgets/widget.js").widget; -exports.button = Widget; exports.linkcatcher = Widget; exports.setstyle = Widget; exports["import"] = Widget; diff --git a/core/modules/new_widgets/tiddler.js b/core/modules/new_widgets/tiddler.js index a892ad87b..3aacef3e0 100755 --- a/core/modules/new_widgets/tiddler.js +++ b/core/modules/new_widgets/tiddler.js @@ -42,8 +42,9 @@ Compute the internal state of the widget TiddlerWidget.prototype.execute = function() { // Get our parameters this.tiddlerTitle = this.getAttribute("title",""); - // Set context variable + // Set context variables this.setVariable("tiddlerTitle",this.tiddlerTitle); + this.setVariable("tiddlerMissing",this.wiki.tiddlerExists(this.tiddlerTitle) ? "tw-tiddler-exists" : "tw-tiddler-missing"); // Construct the child widgets this.makeChildWidgets(); }; @@ -65,7 +66,6 @@ TiddlerWidget.prototype.refresh = function(changedTiddlers) { Handle a tw-navigate event */ TiddlerWidget.prototype.handleNavigateEvent = function(event) { -console.log("Setting navigateFromTitle to",this.tiddlerTitle) event.navigateFromTitle = this.tiddlerTitle; return true; }; diff --git a/core/ui/PageTemplate.tid b/core/ui/PageTemplate.tid index 10a458884..c2c37dce7 100644 --- a/core/ui/PageTemplate.tid +++ b/core/ui/PageTemplate.tid @@ -26,7 +26,7 @@ title: $:/core/ui/PageTemplate
-<$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template="$:/core/ui/ViewTemplate" editTemplate="$:/core/ui/EditTemplate" listview={{$:/view}} itemClass="tw-tiddler-frame"/> +<$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template="$:/core/ui/ViewTemplate" editTemplate="$:/core/ui/EditTemplate" listview={{$:/view}} />
diff --git a/core/ui/SideBar.tid b/core/ui/SideBar.tid index bde6fd168..edfed5b37 100644 --- a/core/ui/SideBar.tid +++ b/core/ui/SideBar.tid @@ -31,7 +31,7 @@ title: $:/core/ui/SideBar
<$reveal type="match" state="$:/state/sideBarTabSet" text="openTab" qualifyTiddlerTitles="yes"> <$list filter="[list[$:/StoryList]]" history="$:/HistoryList" listview="pop" itemClass="tw-menu-list-item tw-open-tiddler-list"> -<$button message="tw-close-tiddler" class="btn-invisible btn-mini">× <$view field="title" format="link"/> +<$button message="tw-close-tiddler" class="btn-invisible btn-mini">× <$link to={{!!title}}><$view field="title"/> <$button message="tw-close-all-tiddlers" class="btn-invisible btn-mini">close all diff --git a/core/ui/ViewTemplate.tid b/core/ui/ViewTemplate.tid index c841b2008..68afa61b9 100644 --- a/core/ui/ViewTemplate.tid +++ b/core/ui/ViewTemplate.tid @@ -1,5 +1,5 @@ title: $:/core/ui/ViewTemplate modifier: JeremyRuston -
<$list filter="[is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]] [!is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]]" hackTemplate=true/> +
<$list filter="[is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]] [!is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]]" hackTemplate=true/>
diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 2623df74a..00e9c94df 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -490,7 +490,7 @@ a.tw-tiddlylink-external { float: right; } -.tw-tiddler-controls .tw-button { +.tw-tiddler-controls button { margin: 0 0 0 5px; vertical-align: baseline; } From 79dcc9a55703d19beb9ce2c0c0df9054440c1a45 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 13 Oct 2013 22:38:46 +0100 Subject: [PATCH 004/183] Refactor macro parameter substitution Now macros can also include references to variables as `$(variable)$` --- core/modules/new_widgets/widget.js | 40 +++++++++++++++++++----------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index 72b0f4049..3ffff98dc 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -74,8 +74,8 @@ Get the prevailing value of a context variable name: name of variable params: array of {name:, value:} for each parameter */ -Widget.prototype.getVariable = function(name,params,defaultValue) { - params = params || []; +Widget.prototype.getVariable = function(name,actualParams,defaultValue) { + actualParams = actualParams || []; // Search up the widget tree for the variable name var node = this; while(node && !$tw.utils.hop(node.variables,name)) { @@ -87,34 +87,46 @@ Widget.prototype.getVariable = function(name,params,defaultValue) { // Get the value var value = node.variables[name].value; // Substitute any parameters specified in the definition - var defParams = node.variables[name].params; - if(defParams) { + 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 Date: Sun, 13 Oct 2013 22:39:05 +0100 Subject: [PATCH 005/183] Add support for macro definitions --- core/modules/new_widgets/macrodef.js | 55 ++++++++++++++++++++++++++++ core/modules/wiki.js | 36 +++++++++++++----- 2 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 core/modules/new_widgets/macrodef.js diff --git a/core/modules/new_widgets/macrodef.js b/core/modules/new_widgets/macrodef.js new file mode 100644 index 000000000..a386fe4ab --- /dev/null +++ b/core/modules/new_widgets/macrodef.js @@ -0,0 +1,55 @@ +/*\ +title: $:/core/modules/new_widgets/macrodef.js +type: application/javascript +module-type: new_widget + +Macro definition widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var MacroDefWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +MacroDefWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +MacroDefWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +MacroDefWidget.prototype.execute = function() { + // Set macro definition + this.setVariable(this.parseTreeNode.name,this.parseTreeNode.text,this.parseTreeNode.params); + // 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 +*/ +MacroDefWidget.prototype.refresh = function(changedTiddlers) { + return this.refreshChildren(changedTiddlers); +}; + +exports.macrodef = MacroDefWidget; + +})(); diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 50583206a..479cfbf12 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -629,19 +629,35 @@ exports.parseTiddler = function(title,options) { // We need to tweak parse trees generated by the existing parser because of the change from {type:"element",tag:"$tiddler",...} to {type:"tiddler",...} var tweakParseTreeNode = function(node) { - if(node.type === "element" && node.tag.charAt(0) === "$") { - node.type = node.tag.substr(1); - } - tweakParseTreeNodes(node.children); - }, - tweakParseTreeNodes = function(nodeList) { - $tw.utils.each(nodeList,tweakParseTreeNode); - }; + if(node.type === "element" && node.tag.charAt(0) === "$") { + node.type = node.tag.substr(1); + } + tweakParseTreeNodes(node.children); +}; + +var tweakParseTreeNodes = function(nodeList) { + $tw.utils.each(nodeList,tweakParseTreeNode); +}; + +var tweakMacroDefinition = function(nodeList) { + if(nodeList && nodeList[0] && nodeList[0].type === "macrodef") { + nodeList[0].children = nodeList.slice(1); + nodeList.splice(1,nodeList.length-1); + tweakMacroDefinition(nodeList.children); + } +}; + +var tweakParser = function(parser) { + // Move any macro definitions to contain the body tree + tweakMacroDefinition(parser.tree); + // Tweak widgets + tweakParseTreeNodes(parser.tree); +}; exports.new_parseText = function(type,text,options) { var parser = this.parseText(type,text,options); if(parser) { - tweakParseTreeNodes(parser.tree) + tweakParser(parser) }; return parser; }; @@ -649,7 +665,7 @@ exports.new_parseText = function(type,text,options) { exports.new_parseTiddler = function(title,options) { var parser = this.parseTiddler(title,options); if(parser) { - tweakParseTreeNodes(parser.tree) + tweakParser(parser) }; return parser; }; From ccd5eeebfd95b9e21f66e2dc84f28d13f40b1502 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 13 Oct 2013 22:39:18 +0100 Subject: [PATCH 006/183] Add a class for missing tiddlers --- core/ui/ViewTemplate.tid | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/ui/ViewTemplate.tid b/core/ui/ViewTemplate.tid index 68afa61b9..8bf5812ab 100644 --- a/core/ui/ViewTemplate.tid +++ b/core/ui/ViewTemplate.tid @@ -1,5 +1,8 @@ title: $:/core/ui/ViewTemplate modifier: JeremyRuston -
<$list filter="[is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]] [!is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]]" hackTemplate=true/> +\define frame-classes() +tw-tiddler-frame $(tiddlerMissing)$ +\end +
>><$list filter="[is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]] [!is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]]" hackTemplate=true/>
From ce4a6ffa491551252ce724c55712c839a1a7770b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 13 Oct 2013 22:46:45 +0100 Subject: [PATCH 007/183] More useful class variables for tiddler status --- core/modules/new_widgets/tiddler.js | 4 +++- core/ui/ViewTemplate.tid | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/modules/new_widgets/tiddler.js b/core/modules/new_widgets/tiddler.js index 3aacef3e0..c61eaeca4 100755 --- a/core/modules/new_widgets/tiddler.js +++ b/core/modules/new_widgets/tiddler.js @@ -44,7 +44,9 @@ TiddlerWidget.prototype.execute = function() { this.tiddlerTitle = this.getAttribute("title",""); // Set context variables this.setVariable("tiddlerTitle",this.tiddlerTitle); - this.setVariable("tiddlerMissing",this.wiki.tiddlerExists(this.tiddlerTitle) ? "tw-tiddler-exists" : "tw-tiddler-missing"); + this.setVariable("missingTiddlerClass",(this.wiki.tiddlerExists(this.tiddlerTitle) || this.wiki.isShadowTiddler(this.tiddlerTitle)) ? "tw-tiddler-exists" : "tw-tiddler-missing"); + this.setVariable("shadowTiddlerClass",this.wiki.isShadowTiddler(this.tiddlerTitle) ? "tw-tiddler-shadow" : ""); + this.setVariable("systemTiddlerClass",this.wiki.isSystemTiddler(this.tiddlerTitle) ? "tw-tiddler-system" : ""); // Construct the child widgets this.makeChildWidgets(); }; diff --git a/core/ui/ViewTemplate.tid b/core/ui/ViewTemplate.tid index 8bf5812ab..a68b3f591 100644 --- a/core/ui/ViewTemplate.tid +++ b/core/ui/ViewTemplate.tid @@ -2,7 +2,7 @@ title: $:/core/ui/ViewTemplate modifier: JeremyRuston \define frame-classes() -tw-tiddler-frame $(tiddlerMissing)$ +tw-tiddler-frame $(missingTiddlerClass)$ $(shadowTiddlerClass)$ $(systemTiddlerClass)$ \end
>><$list filter="[is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]] [!is[shadow]!has[draft.of]tag[$:/tags/ViewTemplate]]" hackTemplate=true/>
From 942e463b20a56d74d75021401e2e1e62e0b18dc2 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 13 Oct 2013 22:59:14 +0100 Subject: [PATCH 008/183] Fixes to get recent changes list to work --- core/modules/new_widgets/view.js | 5 +++-- core/wiki/recentchanges.tid | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index 228d1af13..988c96964 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -44,6 +44,7 @@ ViewWidget.prototype.execute = function() { this.viewField = this.getAttribute("field","text"); this.viewIndex = this.getAttribute("index"); this.viewFormat = this.getAttribute("format","text"); + this.viewTemplate = this.getAttribute("template",""); switch(this.viewFormat) { case "wikified": this.text = this.getValueAsWikified(); @@ -55,7 +56,7 @@ ViewWidget.prototype.execute = function() { this.text = this.getValueAsHtmlEncoded(); break; case "date": - this.text = this.getValueAsDate(this.viewFormat); + this.text = this.getValueAsDate(this.viewTemplate); break; case "relativedate": this.text = this.getValueAsRelativeDate(); @@ -120,7 +121,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ ViewWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.title || changedAttributes.field || changedAttributes.index || changedTiddlers[this.viewTitle]) { + if(changedAttributes.title || changedAttributes.field || changedAttributes.index || changedAttributes.template || changedAttributes.format || changedTiddlers[this.viewTitle]) { this.refreshSelf(); return true; } else { diff --git a/core/wiki/recentchanges.tid b/core/wiki/recentchanges.tid index 650842a75..f1fd438fc 100644 --- a/core/wiki/recentchanges.tid +++ b/core/wiki/recentchanges.tid @@ -1,8 +1,14 @@ title: $:/snippets/recentchanges <$list filter="[!is[system]has[modified]!sort[modified]limit[100]eachday[modified]]" itemClass="tw-menu-list-item"> +
<$view field="modified" format="date" template="DDth MMM YYYY"/> <$list filter="[sameday{!!modified}!is[system]!sort[modified]]" itemClass="tw-menu-list-subitem"> -<$view field="title" format="link"/> +
+<$link to={{!!title}}> +<$view field="title"/> + +
+
From cc4940f41fe00214ac37b324130c9c94d362625b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 13 Oct 2013 23:40:11 +0100 Subject: [PATCH 009/183] Use the setvariable widget instead of macrodef setvariable is more general --- core/modules/new_widgets/macrodef.js | 55 ------------------------- core/modules/new_widgets/setvariable.js | 2 +- core/modules/wiki.js | 5 +++ 3 files changed, 6 insertions(+), 56 deletions(-) delete mode 100644 core/modules/new_widgets/macrodef.js diff --git a/core/modules/new_widgets/macrodef.js b/core/modules/new_widgets/macrodef.js deleted file mode 100644 index a386fe4ab..000000000 --- a/core/modules/new_widgets/macrodef.js +++ /dev/null @@ -1,55 +0,0 @@ -/*\ -title: $:/core/modules/new_widgets/macrodef.js -type: application/javascript -module-type: new_widget - -Macro definition widget - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -var Widget = require("$:/core/modules/new_widgets/widget.js").widget; - -var MacroDefWidget = function(parseTreeNode,options) { - this.initialise(parseTreeNode,options); -}; - -/* -Inherit from the base widget class -*/ -MacroDefWidget.prototype = new Widget(); - -/* -Render this widget into the DOM -*/ -MacroDefWidget.prototype.render = function(parent,nextSibling) { - this.parentDomNode = parent; - this.computeAttributes(); - this.execute(); - this.renderChildren(parent,nextSibling); -}; - -/* -Compute the internal state of the widget -*/ -MacroDefWidget.prototype.execute = function() { - // Set macro definition - this.setVariable(this.parseTreeNode.name,this.parseTreeNode.text,this.parseTreeNode.params); - // 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 -*/ -MacroDefWidget.prototype.refresh = function(changedTiddlers) { - return this.refreshChildren(changedTiddlers); -}; - -exports.macrodef = MacroDefWidget; - -})(); diff --git a/core/modules/new_widgets/setvariable.js b/core/modules/new_widgets/setvariable.js index 2fa0cb5d9..3be58e54d 100755 --- a/core/modules/new_widgets/setvariable.js +++ b/core/modules/new_widgets/setvariable.js @@ -41,7 +41,7 @@ SetVariableWidget.prototype.execute = function() { this.setName = this.getAttribute("name","tiddlerTitle"); this.setValue = this.getAttribute("value"); // Set context variable - this.setVariable(this.setName,this.setValue); + this.setVariable(this.setName,this.setValue,this.parseTreeNode.params); // Construct the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 479cfbf12..d87eff86e 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -641,6 +641,11 @@ var tweakParseTreeNodes = function(nodeList) { var tweakMacroDefinition = function(nodeList) { if(nodeList && nodeList[0] && nodeList[0].type === "macrodef") { + nodeList[0].type = "setvariable"; + nodeList[0].attributes = { + name: {type: "string", value: nodeList[0].name}, + value: {type: "string", value: nodeList[0].text} + }; nodeList[0].children = nodeList.slice(1); nodeList.splice(1,nodeList.length-1); tweakMacroDefinition(nodeList.children); From ea0b298b78d9104695ff3b0236f797a5b9177466 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 12:59:39 +0100 Subject: [PATCH 010/183] Deal with viewing date fields that don't exist --- core/modules/new_widgets/view.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index 988c96964..e78a7bd0a 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -80,7 +80,11 @@ ViewWidget.prototype.getValueAsText = function() { // Calling getTiddlerText() triggers lazy loading of skinny tiddlers text = this.wiki.getTiddlerText(this.viewTitle); } else { - text = tiddler.fields[this.viewField]; + if($tw.utils.hop(tiddler.fields,this.viewField)) { + text = tiddler.fields[this.viewField]; + } else { + text = ""; + } } } else { // Use a special value if the tiddler is missing switch(this.viewField) { @@ -112,8 +116,12 @@ ViewWidget.prototype.getValueAsDate = function(format) { }; ViewWidget.prototype.getValueAsRelativeDate = function(format) { - var d = new Date(this.getValueAsText()); - return $tw.utils.getRelativeDate((new Date()) - d).description; + var value = this.getValueAsText(); + if(value) { + return $tw.utils.getRelativeDate((new Date()) - (new Date(value))).description; + } else { + return ""; + } }; /* From 1669c6eab52e76792b6b1da5e8fdd099aa7763b8 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 13:06:07 +0100 Subject: [PATCH 011/183] Make recursion markers include the current tiddler Otherwise the generated qualifying state title is not unique. In other words, clicking the "i" button on a tiddler was opening the info panels for all open tiddlers. --- core/modules/new_widgets/transclude.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/modules/new_widgets/transclude.js b/core/modules/new_widgets/transclude.js index 6038f0228..754e34608 100755 --- a/core/modules/new_widgets/transclude.js +++ b/core/modules/new_widgets/transclude.js @@ -66,6 +66,8 @@ Compose a string comprising the title, field and/or index to identify this trans TranscludeWidget.prototype.makeRecursionMarker = function() { var output = []; output.push("{"); + output.push(this.getVariable("tiddlerTitle","")); + output.push("|"); output.push(this.transcludeTitle || ""); output.push("|"); output.push(this.transcludeField || ""); From 44e622ce2860971e2ae65bd6311d51a07179dc37 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 13:11:58 +0100 Subject: [PATCH 012/183] Proper styling for the tiddler info panel --- core/ui/TiddlerInfo.tid | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/ui/TiddlerInfo.tid b/core/ui/TiddlerInfo.tid index a73e661dd..e84d4ba7a 100644 --- a/core/ui/TiddlerInfo.tid +++ b/core/ui/TiddlerInfo.tid @@ -1,6 +1,6 @@ title: $:/core/ui/TiddlerInfo -
<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="refTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">References<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="taggingTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">Tagging<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="listTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">List<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="listedTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">Listed<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="fieldsTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">Fields
+
<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="refTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">References<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="taggingTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">Tagging<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="listTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">List<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="listedTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">Listed<$button type="set" set="$:/state/tiddlerDropDownTabSet" setTo="fieldsTab" qualifyTiddlerTitles="yes" selectedClass="tw-tab-selected">Fields
@@ -21,4 +21,5 @@ title: $:/core/ui/TiddlerInfo <$transclude title="$:/core/ui/TiddlerFields"/>
-
\ No newline at end of file +
+
From b0f4d72dac0ec607d3c455e564633c7492533ddf Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 13:51:54 +0100 Subject: [PATCH 013/183] Correct name for the state tiddler that makes sure the open sidebar tab is open by default --- core/wiki/sideBarTabSet.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/wiki/sideBarTabSet.tid b/core/wiki/sideBarTabSet.tid index dc9a5d6b1..44fb50824 100644 --- a/core/wiki/sideBarTabSet.tid +++ b/core/wiki/sideBarTabSet.tid @@ -1,3 +1,3 @@ -title: $:/state/sideBarTabSet--tiddlerTitle:$:/core/ui/SideBar;-tiddlerTitle:$:/core/ui/PageTemplate;- +title: $:/state/sideBarTabSet-{$:/core/ui/SideBar|$:/core/ui/SideBar||} openTab \ No newline at end of file From 0c73c56b704ab2ebe72613dfc4f1b18bf9efbe70 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 13:52:03 +0100 Subject: [PATCH 014/183] Fixes to button refreshing --- core/modules/new_widgets/button.js | 32 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/core/modules/new_widgets/button.js b/core/modules/new_widgets/button.js index ab8988632..f09f64a91 100644 --- a/core/modules/new_widgets/button.js +++ b/core/modules/new_widgets/button.js @@ -71,11 +71,7 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { }; ButtonWidget.prototype.isSelected = function() { - var title = this.set; - if(this.qualifyTiddlerTitles) { - title = title + "-" + this.getStateQualifier(); - } - var tiddler = this.wiki.getTiddler(title); + var tiddler = this.wiki.getTiddler(this.set); return tiddler ? tiddler.fields.text === this.setTo : false; }; @@ -84,24 +80,16 @@ ButtonWidget.prototype.dispatchMessage = function(event) { }; ButtonWidget.prototype.triggerPopup = function(event) { - var title = this.popup; - if(this.qualifyTiddlerTitles) { - title = title + "-" + this.getStateQualifier(); - } $tw.popup.triggerPopup({ domNode: this.domNodes[0], - title: title, + title: this.popup, wiki: this.wiki }); }; ButtonWidget.prototype.setTiddler = function() { - var title = this.set; - if(this.qualifyTiddlerTitles) { - title = title + "-" + this.getStateQualifier(); - } - var tiddler = this.wiki.getTiddler(title); - this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: title, text: this.setTo})); + var tiddler = this.wiki.getTiddler(this.set); + this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: this.set, text: this.setTo})); }; /* @@ -118,6 +106,16 @@ ButtonWidget.prototype.execute = function() { this.qualifyTiddlerTitles = this.getAttribute("qualifyTiddlerTitles"); this["class"] = this.getAttribute("class",""); this.selectedClass = this.getAttribute("selectedClass"); + // Qualify tiddler titles if required + if(this.qualifyTiddlerTitles) { + var qualifier = this.getStateQualifier(); + if(this.set) { + this.set = this.set + "-" + qualifier; + } + if(this.popup) { + this.popup = this.popup + "-" + qualifier; + } + } // Make child widgets this.makeChildWidgets(); }; @@ -127,7 +125,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ ButtonWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || changedAttributes.selectedClass) { + if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || changedAttributes.selectedClass || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) { this.refreshSelf(); return true; } From fe6c1ae2dde1ff51b7e1bc92f3b2b8b689682909 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 16:55:29 +0100 Subject: [PATCH 015/183] A new template for menu list items --- core/ui/ListItemTemplate.tid | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 core/ui/ListItemTemplate.tid diff --git a/core/ui/ListItemTemplate.tid b/core/ui/ListItemTemplate.tid new file mode 100644 index 000000000..274fe6c76 --- /dev/null +++ b/core/ui/ListItemTemplate.tid @@ -0,0 +1,5 @@ +title: $:/core/ui/ListItemTemplate + +
+<$link to={{!!title}}><$view field="title"/> +
From 94c2eacdc9c4cad8d1e4a766b13aad39e4d0fe15 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 16:56:13 +0100 Subject: [PATCH 016/183] Support selectedClass properly in the button widget --- core/modules/new_widgets/button.js | 15 ++++++++++++--- core/modules/utils/dom/popup.js | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/core/modules/new_widgets/button.js b/core/modules/new_widgets/button.js index f09f64a91..76f1bf0e5 100644 --- a/core/modules/new_widgets/button.js +++ b/core/modules/new_widgets/button.js @@ -37,9 +37,12 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { var domNode = this.document.createElement("button"); // Assign classes var classes = this["class"].split(" ") || []; - if(this.set && this.setTo && this.selectedClass) { - if(this.isSelected()) { - classes.push(this.selectedClass.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(" "); @@ -75,6 +78,12 @@ ButtonWidget.prototype.isSelected = function() { return tiddler ? tiddler.fields.text === this.setTo : false; }; +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("tiddlerTitle")}); }; diff --git a/core/modules/utils/dom/popup.js b/core/modules/utils/dom/popup.js index cd2c92277..2626fff0f 100644 --- a/core/modules/utils/dom/popup.js +++ b/core/modules/utils/dom/popup.js @@ -54,8 +54,7 @@ Popup.prototype.triggerPopup = function(options) { // Get the current popup state tiddler var value = options.wiki.getTextReference(options.title,""); // Check if the popup is open by checking whether it matches "(,)" - var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/, - state = !popupLocationRegExp.test(value); + var state = !this.readPopupState(options.title,value); if("force" in options) { state = options.force; } @@ -71,6 +70,18 @@ Popup.prototype.triggerPopup = function(options) { } }; +/* +Returns true if the specified title and text identifies an active popup +*/ +Popup.prototype.readPopupState = function(title,text) { + var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/, + result = false; + if(this.title === title) { + result = popupLocationRegExp.test(text); + } + return result; +}; + exports.Popup = Popup; })(); From 59986ca4f65355ca0ae3bcfcdf258de25856e16a Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 16:56:42 +0100 Subject: [PATCH 017/183] Style for selected tiddler info button --- themes/tiddlywiki/snowwhite/base.tid | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 00e9c94df..972ab1ae5 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -495,12 +495,17 @@ a.tw-tiddlylink-external { vertical-align: baseline; } -.tw-tiddler-controls svg { +.tw-tiddler-controls button svg { height: 0.75em; fill: #ccc; <> } +.tw-tiddler-controls button.tw-selected svg { + fill: #fff; + <> +} + .tw-tiddler-controls svg:hover { fill: #888; } From b4d33b9614ac5cd1dce3ac2450fc1a25a68023fe Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 16:57:16 +0100 Subject: [PATCH 018/183] Fixups to the sidebar and tiddler info button --- core/ui/MoreSideBar.tid | 20 ++++++++++++-------- core/ui/ViewTemplate/title.tid | 2 +- core/wiki/moreSideBarTabSet.tid | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/core/ui/MoreSideBar.tid b/core/ui/MoreSideBar.tid index 0c6aabe9b..5b35fb71e 100644 --- a/core/ui/MoreSideBar.tid +++ b/core/ui/MoreSideBar.tid @@ -22,7 +22,7 @@ title: $:/core/ui/MoreSideBar
<$reveal type="match" state="$:/state/moreSideBarTabSet" text="allTab" qualifyTiddlerTitles="yes"> -<$list filter="[!is[system]sort[title]]" itemClass="tw-menu-list-item"/> +<$list filter="[!is[system]sort[title]]" itemClass="tw-menu-list-item" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/moreSideBarTabSet" text="recentTab" qualifyTiddlerTitles="yes"> {{$:/snippets/recentchanges}} @@ -38,23 +38,27 @@ title: $:/core/ui/MoreSideBar <$list filter="[is[missing]sort[title]]" itemClass="tw-menu-list-item" template="$:/core/ui/MissingTemplate"/> <$reveal type="match" state="$:/state/moreSideBarTabSet" text="draftsTab" qualifyTiddlerTitles="yes"> -<$list filter="[has[draft.of]sort[title]]" itemClass="tw-menu-list-item" /> +<$list filter="[has[draft.of]sort[title]]" itemClass="tw-menu-list-item" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/moreSideBarTabSet" text="orphanTab" qualifyTiddlerTitles="yes"> -<$list filter="[is[orphan]sort[title]]" itemClass="tw-menu-list-item"/> +<$list filter="[is[orphan]sort[title]]" itemClass="tw-menu-list-item" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/moreSideBarTabSet" text="typeTab" qualifyTiddlerTitles="yes"> -<$list filter="[!is[system]has[type]each[type]sort[type]]" itemClass="tw-menu-list-item"> +<$list filter="[!is[system]has[type]each[type]sort[type]]"> +
<$view field="type" default="untyped"/> -<$list filter="[type{!!type}!is[system]sort[title]]" itemClass="tw-menu-list-subitem"> -<$view field="title" format="link"/> +<$list filter="[type{!!type}!is[system]sort[title]]"> +
+<$link to={{!!title}}><$view field="title"/> +
+
<$reveal type="match" state="$:/state/moreSideBarTabSet" text="systemTab" qualifyTiddlerTitles="yes"> -<$list filter="[is[system]sort[title]]" itemClass="tw-menu-list-item"/> +<$list filter="[is[system]sort[title]]" itemClass="tw-menu-list-item" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/moreSideBarTabSet" text="shadowsTab" qualifyTiddlerTitles="yes"> -<$list filter="[is[shadow]sort[title]]" itemClass="tw-menu-list-item"/> +<$list filter="[is[shadow]sort[title]]" itemClass="tw-menu-list-item" template="$:/core/ui/ListItemTemplate"/>
diff --git a/core/ui/ViewTemplate/title.tid b/core/ui/ViewTemplate/title.tid index 9adab010e..edd65f6c8 100644 --- a/core/ui/ViewTemplate/title.tid +++ b/core/ui/ViewTemplate/title.tid @@ -1,7 +1,7 @@ title: $:/core/ui/ViewTemplate/title tags: $:/tags/ViewTemplate -
<$setstyle name="fill" value={{!!color}}><$button popup="$:/state/tiddlerInfo" qualifyTiddlerTitles="yes" class="btn-invisible">{{$:/core/images/info-button}}<$button message="tw-edit-tiddler" class="btn-invisible">{{$:/core/images/edit-button}}<$button message="tw-close-tiddler" class="btn-invisible">{{$:/core/images/close-button}} +
<$setstyle name="fill" value={{!!color}}><$button popup="$:/state/tiddlerInfo" qualifyTiddlerTitles="yes" class="btn-invisible" selectedClass="tw-selected">{{$:/core/images/info-button}}<$button message="tw-edit-tiddler" class="btn-invisible">{{$:/core/images/edit-button}}<$button message="tw-close-tiddler" class="btn-invisible">{{$:/core/images/close-button}} <$transclude title={{!!icon}}/> <$view field="title"/>
diff --git a/core/wiki/moreSideBarTabSet.tid b/core/wiki/moreSideBarTabSet.tid index 57d4a0596..7d31013d6 100644 --- a/core/wiki/moreSideBarTabSet.tid +++ b/core/wiki/moreSideBarTabSet.tid @@ -1,3 +1,3 @@ -title: $:/state/moreSideBarTabSet--tiddlerTitle:$:/core/ui/MoreSideBar;--tiddlerTitle:$:/core/ui/SideBar;-tiddlerTitle:$:/core/ui/PageTemplate;- +title: $:/state/moreSideBarTabSet-{$:/core/ui/MoreSideBar|$:/core/ui/MoreSideBar||}{$:/core/ui/SideBar|$:/core/ui/SideBar||} tagsTab \ No newline at end of file From ebd28c8dc729c2edad409b61e8aa75e0a69f72b8 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 17:35:52 +0100 Subject: [PATCH 019/183] Get the tiddler info panel fields list working properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now we can display the fields list as a proper table. We've had to temporarily hack the list widget again… --- core/modules/new_widgets/list.js | 4 +++- core/ui/ListItemTemplate.tid | 5 ++--- core/ui/TiddlerFieldTemplate.tid | 3 +++ core/ui/TiddlerFields.tid | 7 +++---- core/ui/TiddlerInfo.tid | 8 ++++---- themes/tiddlywiki/snowwhite/base.tid | 6 ------ 6 files changed, 15 insertions(+), 18 deletions(-) create mode 100644 core/ui/TiddlerFieldTemplate.tid diff --git a/core/modules/new_widgets/list.js b/core/modules/new_widgets/list.js index 25692a8b0..21e6096bd 100755 --- a/core/modules/new_widgets/list.js +++ b/core/modules/new_widgets/list.js @@ -104,7 +104,9 @@ ListWidget.prototype.makeItemTemplate = function(title) { ]}]; } } - templateTree = [{type: "tiddler", attributes: {title: {type: "string", value: title}}, children: templateTree}] + if(!this.hasAttribute("hackCurrentTiddler")) { + templateTree = [{type: "tiddler", attributes: {title: {type: "string", value: title}}, children: templateTree}] + } } // Return the list item return {type: "listitem", itemTitle: title, children: templateTree}; diff --git a/core/ui/ListItemTemplate.tid b/core/ui/ListItemTemplate.tid index 274fe6c76..008847b31 100644 --- a/core/ui/ListItemTemplate.tid +++ b/core/ui/ListItemTemplate.tid @@ -1,5 +1,4 @@ title: $:/core/ui/ListItemTemplate -
-<$link to={{!!title}}><$view field="title"/> -
+
<$link to={{!!title}}><$view field="title"/> +
\ No newline at end of file diff --git a/core/ui/TiddlerFieldTemplate.tid b/core/ui/TiddlerFieldTemplate.tid new file mode 100644 index 000000000..a91813f40 --- /dev/null +++ b/core/ui/TiddlerFieldTemplate.tid @@ -0,0 +1,3 @@ +title: $:/core/ui/TiddlerFieldTemplate + +<><$view field=<>/> \ No newline at end of file diff --git a/core/ui/TiddlerFields.tid b/core/ui/TiddlerFields.tid index 2f473075f..66153fd89 100644 --- a/core/ui/TiddlerFields.tid +++ b/core/ui/TiddlerFields.tid @@ -1,6 +1,5 @@ title: $:/core/ui/TiddlerFields -\define renderfield(title) -
//$title$//: <$view field="$title$"/>
-\end -<$list filter="[is[current]fields[]sort[title]] -text" macro="renderfield"/> +<$list filter="[is[current]fields[]sort[title]] -text" template="$:/core/ui/TiddlerFieldTemplate" hackCurrentTiddler=true/> + +
diff --git a/core/ui/TiddlerInfo.tid b/core/ui/TiddlerInfo.tid index e84d4ba7a..6cdf2b4dc 100644 --- a/core/ui/TiddlerInfo.tid +++ b/core/ui/TiddlerInfo.tid @@ -5,17 +5,17 @@ title: $:/core/ui/TiddlerInfo
<$reveal type="match" state="$:/state/tiddlerDropDownTabSet" text="refTab" default="refTab" qualifyTiddlerTitles="yes"> -<$list filter="[is[current]backlinks[]sort[title]]" emptyMessage="No tiddlers link to this one"> +<$list filter="[is[current]backlinks[]sort[title]]" emptyMessage="No tiddlers link to this one" template="$:/core/ui/ListItemTemplate"> <$reveal type="match" state="$:/state/tiddlerDropDownTabSet" text="taggingTab" qualifyTiddlerTitles="yes"> -<$list filter="[is[current]tagging[]]" itemClass="tw-menu-list-item" emptyMessage="No tiddlers are tagged with this one"/> +<$list filter="[is[current]tagging[]]" itemClass="tw-menu-list-item" emptyMessage="No tiddlers are tagged with this one" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/tiddlerDropDownTabSet" text="listTab" qualifyTiddlerTitles="yes"> -<$list filter="[list{!!title}]" itemClass="tw-menu-list-item" emptyMessage="This tiddler does not have a list"/> +<$list filter="[list{!!title}]" itemClass="tw-menu-list-item" emptyMessage="This tiddler does not have a list" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/tiddlerDropDownTabSet" text="listedTab" qualifyTiddlerTitles="yes"> -<$list filter="[is[current]listed[]!is[system]]" itemClass="tw-menu-list-item" emptyMessage="This tiddler is not listed by any others"/> +<$list filter="[is[current]listed[]!is[system]]" itemClass="tw-menu-list-item" emptyMessage="This tiddler is not listed by any others" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/tiddlerDropDownTabSet" text="fieldsTab" qualifyTiddlerTitles="yes"> <$transclude title="$:/core/ui/TiddlerFields"/> diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 972ab1ae5..7f6ad7e2e 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -426,16 +426,10 @@ a.tw-tiddlylink-external { } .tw-view-field-name { - vertical-align: top; - display: inline-block; - width: 15%; text-align: right; } .tw-view-field-value { - vertical-align: top; - display: inline-block; - width: 75%; } @media screen { From 468fe8b6f1f18e6872b236d09e8d05ded961f64b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 17:48:26 +0100 Subject: [PATCH 020/183] Add macrocall widget Now the fields table works in the tiddler info popup --- core/modules/new_widgets/macrocall.js | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 core/modules/new_widgets/macrocall.js diff --git a/core/modules/new_widgets/macrocall.js b/core/modules/new_widgets/macrocall.js new file mode 100644 index 000000000..36b0ed4e5 --- /dev/null +++ b/core/modules/new_widgets/macrocall.js @@ -0,0 +1,59 @@ +/*\ +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 macro value + var text = this.getVariable(this.parseTreeNode.name,this.parseTreeNode.params); + // Parse the macro + var parser = this.wiki.new_parseText("text/vnd.tiddlywiki",text, + {parseAsInline: !this.parseTreeNode.isBlock}), + parseTreeNodes = parser ? parser.tree : []; + // 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) { + return this.refreshChildren(changedTiddlers); +}; + +exports.macrocall = MacroCallWidget; + +})(); From 7c0cc367391de02c5f266b82aa859f4c72bf2ffe Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 21:01:45 +0100 Subject: [PATCH 021/183] Style tweaks for tiddler info panel --- themes/tiddlywiki/snowwhite/base.tid | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 7f6ad7e2e..44fed2a4c 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -427,6 +427,7 @@ a.tw-tiddlylink-external { .tw-view-field-name { text-align: right; + font-style: italic; } .tw-view-field-value { @@ -496,8 +497,8 @@ a.tw-tiddlylink-external { } .tw-tiddler-controls button.tw-selected svg { - fill: #fff; - <> + fill: #444; + <> } .tw-tiddler-controls svg:hover { From e3dcb8cdc107075bd85dd41364498367d507e8af Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 21:02:03 +0100 Subject: [PATCH 022/183] Fix view widget to display friendly text of tiddler values --- core/modules/new_widgets/view.js | 50 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index e78a7bd0a..245a149e3 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -71,6 +71,31 @@ ViewWidget.prototype.execute = function() { The various formatter functions are baked into this widget for the moment. Eventually they will be replaced by macro functions */ +ViewWidget.prototype.getValue = function() { + // Get the value to display + var value, + tiddler = this.wiki.getTiddler(this.viewTitle); + if(tiddler) { + if(this.viewField === "text") { + // Calling getTiddlerText() triggers lazy loading of skinny tiddlers + value = this.wiki.getTiddlerText(this.viewTitle); + } else { + if($tw.utils.hop(tiddler.fields,this.viewField)) { + value = tiddler.fields[this.viewField]; + } else { + value = ""; + } + } + } else { + if(this.viewField === "title") { + value = this.viewTitle; + } else { + value = undefined; + } + } + return value; +}; + ViewWidget.prototype.getValueAsText = function() { // Get the value to display var text, @@ -80,24 +105,13 @@ ViewWidget.prototype.getValueAsText = function() { // Calling getTiddlerText() triggers lazy loading of skinny tiddlers text = this.wiki.getTiddlerText(this.viewTitle); } else { - if($tw.utils.hop(tiddler.fields,this.viewField)) { - text = tiddler.fields[this.viewField]; - } else { - text = ""; - } + text = tiddler.getFieldString(this.viewField); } } else { // Use a special value if the tiddler is missing - switch(this.viewField) { - case "title": - text = this.getVariable("tiddlerTitle"); - break; - case "modified": - case "created": - text = new Date(); - break; - default: - text = ""; - break; + if(this.viewField === "title") { + text = this.viewTitle; + } else { + text = ""; } } return text; @@ -112,11 +126,11 @@ ViewWidget.prototype.getValueAsHtmlEncoded = function() { }; ViewWidget.prototype.getValueAsDate = function(format) { - return $tw.utils.formatDateString(this.getValueAsText(),format); + return $tw.utils.formatDateString(this.getValue(),format); }; ViewWidget.prototype.getValueAsRelativeDate = function(format) { - var value = this.getValueAsText(); + var value = this.getValue(); if(value) { return $tw.utils.getRelativeDate((new Date()) - (new Date(value))).description; } else { From 0e037e3c5bb7b4ac7f1cef070ebb7b216b00b084 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 21:05:49 +0100 Subject: [PATCH 023/183] Make the field table in the tiddler info panel have width 100% --- core/ui/TiddlerFields.tid | 2 +- themes/tiddlywiki/snowwhite/base.tid | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/ui/TiddlerFields.tid b/core/ui/TiddlerFields.tid index 66153fd89..2f5689dc4 100644 --- a/core/ui/TiddlerFields.tid +++ b/core/ui/TiddlerFields.tid @@ -1,5 +1,5 @@ title: $:/core/ui/TiddlerFields -<$list filter="[is[current]fields[]sort[title]] -text" template="$:/core/ui/TiddlerFieldTemplate" hackCurrentTiddler=true/> +
<$list filter="[is[current]fields[]sort[title]] -text" template="$:/core/ui/TiddlerFieldTemplate" hackCurrentTiddler=true/>
diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 44fed2a4c..545c85fec 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -425,6 +425,10 @@ a.tw-tiddlylink-external { <> } +.tw-view-field-table { + width: 100%; +} + .tw-view-field-name { text-align: right; font-style: italic; From 707024a1181fee0f332d71f51186975dcee5e49e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 21:38:12 +0100 Subject: [PATCH 024/183] Set the colour of the tag pills We no longer need the old setstyle widget --- core/modules/new_widgets/button.js | 5 ++++- core/ui/TagTemplate.tid | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/modules/new_widgets/button.js b/core/modules/new_widgets/button.js index 76f1bf0e5..77224cb7c 100644 --- a/core/modules/new_widgets/button.js +++ b/core/modules/new_widgets/button.js @@ -46,6 +46,8 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { } } domNode.className = classes.join(" "); + // Assign classes + domNode.setAttribute("style",this.style); // Add a click event handler domNode.addEventListener("click",function (event) { var handled = false; @@ -114,6 +116,7 @@ ButtonWidget.prototype.execute = function() { this.hover = this.getAttribute("hover"); this.qualifyTiddlerTitles = this.getAttribute("qualifyTiddlerTitles"); this["class"] = this.getAttribute("class",""); + this.style = this.getAttribute("style"); this.selectedClass = this.getAttribute("selectedClass"); // Qualify tiddler titles if required if(this.qualifyTiddlerTitles) { @@ -134,7 +137,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ ButtonWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || changedAttributes.selectedClass || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) { + if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || changedAttributes.selectedClass || changedAttributes.style || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) { this.refreshSelf(); return true; } diff --git a/core/ui/TagTemplate.tid b/core/ui/TagTemplate.tid index c9fe4d610..ab631f3d7 100644 --- a/core/ui/TagTemplate.tid +++ b/core/ui/TagTemplate.tid @@ -1,6 +1,9 @@ title: $:/core/ui/TagTemplate -<$button popup="$:/state/tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible"><$setstyle name="background-color" value={{!!color}} class="tw-tag-label"><$transclude title={{!!icon}}/> <$view field="title" format="text" /> +\define tag-styles() +background-color:$(backgroundColor)$; +\end +<$setvariable name="backgroundColor" value={{!!color}}><$button popup="$:/state/tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-tag-label" style=<>><$transclude title={{!!icon}}/> <$view field="title" format="text" /> <$reveal state="$:/state/tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes" >
<$view field="title" format="link" /> ---- From c621339729425b35ee4e4bccaf3df3a7b9504e12 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 21:57:00 +0100 Subject: [PATCH 025/183] More fixes to the missing tiddler dropdown and the tag dropdown --- core/ui/MissingTemplate.tid | 8 +++----- core/ui/TagTemplate.tid | 13 +++++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/core/ui/MissingTemplate.tid b/core/ui/MissingTemplate.tid index d2a1bb71f..4e7980908 100644 --- a/core/ui/MissingTemplate.tid +++ b/core/ui/MissingTemplate.tid @@ -2,9 +2,7 @@ title: $:/core/ui/MissingTemplate <$button popup="$:/state/missingpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-missing-tiddler-label"><$view field="title" format="text" /> <$reveal state="$:/state/missingpopup" type="popup" position="below" qualifyTiddlerTitles="yes" >
-<$view field="title" format="link" /> ----- -<$list filter="[is[current]backlinks[]sort[title]]"> -<$view field="title" format="link" /> - +<$transclude title="$:/core/ui/ListItemTemplate"/> +
+<$list filter="[is[current]backlinks[]sort[title]]" template="$:/core/ui/ListItemTemplate"/>
diff --git a/core/ui/TagTemplate.tid b/core/ui/TagTemplate.tid index ab631f3d7..17a8849ef 100644 --- a/core/ui/TagTemplate.tid +++ b/core/ui/TagTemplate.tid @@ -3,11 +3,8 @@ title: $:/core/ui/TagTemplate \define tag-styles() background-color:$(backgroundColor)$; \end -<$setvariable name="backgroundColor" value={{!!color}}><$button popup="$:/state/tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-tag-label" style=<>><$transclude title={{!!icon}}/> <$view field="title" format="text" /> -<$reveal state="$:/state/tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes" >
-<$view field="title" format="link" /> ----- -<$list filter="[is[current]tagging[]]"> -<$view field="title" format="link" /> - -
+<$setvariable name="backgroundColor" value={{!!color}}><$button popup="$:/state/tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-tag-label" style=<>><$transclude title={{!!icon}}/> <$view field="title" format="text" /> +<$reveal state="$:/state/tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes">
<$transclude title="$:/core/ui/ListItemTemplate"/> +
+<$list filter="[is[current]tagging[]]" template="$:/core/ui/ListItemTemplate"/> +
From 20cd398e8853e1279031f4b22507ea40d95c93b6 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2013 23:32:01 +0100 Subject: [PATCH 026/183] Fix popup positioning in the reveal widget --- core/modules/new_widgets/reveal.js | 34 ++++++++++++++++++++++++++++++ core/modules/utils/fakedom.js | 1 + core/ui/MissingTemplate.tid | 6 +++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/core/modules/new_widgets/reveal.js b/core/modules/new_widgets/reveal.js index 9f094bfd8..f57ccf7c2 100755 --- a/core/modules/new_widgets/reveal.js +++ b/core/modules/new_widgets/reveal.js @@ -33,9 +33,43 @@ RevealWidget.prototype.render = function(parent,nextSibling) { var domNode = this.document.createElement("div"); parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); + if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) { + this.positionPopup(domNode); + } this.domNodes.push(domNode); }; +RevealWidget.prototype.positionPopup = function(domNode) { + domNode.style.position = "absolute"; + domNode.style.zIndex = "1000"; + switch(this.position) { + case "left": + domNode.style.left = (this.popup.left - domNode.offsetWidth) + "px"; + domNode.style.top = this.popup.top + "px"; + break; + case "above": + domNode.style.left = this.popup.left + "px"; + domNode.style.top = (this.popup.top - domNode.offsetHeight) + "px"; + break; + case "aboveright": + domNode.style.left = (this.popup.left + this.popup.width) + "px"; + domNode.style.top = (this.popup.top + this.popup.height - domNode.offsetHeight) + "px"; + break; + case "right": + domNode.style.left = (this.popup.left + this.popup.width) + "px"; + domNode.style.top = this.popup.top + "px"; + break; + case "belowleft": + domNode.style.left = (this.popup.left + this.popup.width - domNode.offsetWidth) + "px"; + domNode.style.top = (this.popup.top + this.popup.height) + "px"; + break; + default: // Below + domNode.style.left = this.popup.left + "px"; + domNode.style.top = (this.popup.top + this.popup.height) + "px"; + break; + } +}; + /* Compute the internal state of the widget */ diff --git a/core/modules/utils/fakedom.js b/core/modules/utils/fakedom.js index fd9be9e90..5b3502f46 100755 --- a/core/modules/utils/fakedom.js +++ b/core/modules/utils/fakedom.js @@ -28,6 +28,7 @@ var TW_TextNode = function(text) { var TW_Element = function(tag,namespace) { bumpSequenceNumber(this); + this.isTiddlyWikiFakeDom = true; this.tag = tag; this.attributes = {}; this.isRaw = false; diff --git a/core/ui/MissingTemplate.tid b/core/ui/MissingTemplate.tid index 4e7980908..c7ab0ed19 100644 --- a/core/ui/MissingTemplate.tid +++ b/core/ui/MissingTemplate.tid @@ -1,8 +1,8 @@ title: $:/core/ui/MissingTemplate -<$button popup="$:/state/missingpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-missing-tiddler-label"><$view field="title" format="text" /> -<$reveal state="$:/state/missingpopup" type="popup" position="below" qualifyTiddlerTitles="yes" >
+
<$button popup="$:/state/missingpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-missing-tiddler-label"><$view field="title" format="text" /> +<$reveal state="$:/state/missingpopup" type="popup" position="below" qualifyTiddlerTitles="yes">
<$transclude title="$:/core/ui/ListItemTemplate"/>
<$list filter="[is[current]backlinks[]sort[title]]" template="$:/core/ui/ListItemTemplate"/> -
+
From 933afee996d6b0e1c3f5753b7f63ad558a1c7be3 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 15 Oct 2013 08:36:19 +0100 Subject: [PATCH 027/183] Get rid of the old setstyle widget And fix up the colouring of tiddler icons --- core/modules/new_widgets/tempwidgets.js | 1 - core/ui/ViewTemplate/title.tid | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/modules/new_widgets/tempwidgets.js b/core/modules/new_widgets/tempwidgets.js index c5bd3ab2f..bdae08a62 100755 --- a/core/modules/new_widgets/tempwidgets.js +++ b/core/modules/new_widgets/tempwidgets.js @@ -15,7 +15,6 @@ Temporary shim widgets var Widget = require("$:/core/modules/new_widgets/widget.js").widget; exports.linkcatcher = Widget; -exports.setstyle = Widget; exports["import"] = Widget; })(); diff --git a/core/ui/ViewTemplate/title.tid b/core/ui/ViewTemplate/title.tid index edd65f6c8..7bb840817 100644 --- a/core/ui/ViewTemplate/title.tid +++ b/core/ui/ViewTemplate/title.tid @@ -1,9 +1,12 @@ title: $:/core/ui/ViewTemplate/title tags: $:/tags/ViewTemplate -
<$setstyle name="fill" value={{!!color}}><$button popup="$:/state/tiddlerInfo" qualifyTiddlerTitles="yes" class="btn-invisible" selectedClass="tw-selected">{{$:/core/images/info-button}}<$button message="tw-edit-tiddler" class="btn-invisible">{{$:/core/images/edit-button}}<$button message="tw-close-tiddler" class="btn-invisible">{{$:/core/images/close-button}} +\define title-styles() +fill:$(foregroundColor)$; +\end +
<$button popup="$:/state/tiddlerInfo" qualifyTiddlerTitles="yes" class="btn-invisible" selectedClass="tw-selected">{{$:/core/images/info-button}}<$button message="tw-edit-tiddler" class="btn-invisible">{{$:/core/images/edit-button}}<$button message="tw-close-tiddler" class="btn-invisible">{{$:/core/images/close-button}} -<$transclude title={{!!icon}}/> <$view field="title"/>
+<$setvariable name="foregroundColor" value={{!!color}}>>><$transclude title={{!!icon}}/> <$view field="title"/>
<$reveal type="nomatch" text="" default="" state="$:/state/tiddlerInfo" qualifyTiddlerTitles="yes" class="tw-tiddler-info" animate="yes"> From a696c716e39840a93d30859c7296d7ce3f8d2b92 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 15 Oct 2013 14:30:36 +0100 Subject: [PATCH 028/183] Support the remaining events on the navigator widget --- core/modules/new_widgets/navigator.js | 178 ++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/core/modules/new_widgets/navigator.js b/core/modules/new_widgets/navigator.js index 448ce29a2..33702ebf8 100755 --- a/core/modules/new_widgets/navigator.js +++ b/core/modules/new_widgets/navigator.js @@ -127,6 +127,184 @@ NavigatorWidget.prototype.handleCloseTiddlerEvent = function(event) { return false; }; +// Close all tiddlers +NavigatorWidget.prototype.handleCloseAllTiddlersEvent = function(event) { + this.storyList = []; + this.saveStoryList(); + return false; +}; + +// Place a tiddler in edit mode +NavigatorWidget.prototype.handleEditTiddlerEvent = function(event) { + this.getStoryList(); + // Replace the specified tiddler with a draft in edit mode + for(var t=0; t Date: Tue, 15 Oct 2013 14:32:27 +0100 Subject: [PATCH 029/183] Add a edit text widget Now rather than a separate module type for different editors, we instead have a meta-widget "edit" that chooses the appropriate concrete widget (currently just "edit-text", but soon to be joined by "edit-bitmap" as well) --- core/modules/new_widgets/edit-text.js | 219 ++++++++++++++++++++++++++ core/modules/new_widgets/edit.js | 94 +++++++++++ 2 files changed, 313 insertions(+) create mode 100644 core/modules/new_widgets/edit-text.js create mode 100644 core/modules/new_widgets/edit.js diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js new file mode 100644 index 000000000..61d0a6764 --- /dev/null +++ b/core/modules/new_widgets/edit-text.js @@ -0,0 +1,219 @@ +/*\ +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); + } + // 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 + domNode.addEventListener("input",function (event) { + return self.handleInputEvent(event); + },false); + // Insert the element into the DOM + parent.insertBefore(domNode,nextSibling); + this.domNodes.push(domNode); + // 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.editField) { + // 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; + } + value = this.editDefault; + } + } else { + value = this.wiki.extractTiddlerDataItem(this.editTitle,this.editIndex,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("title",this.getVariable("tiddlerTitle")); + this.editField = this.getAttribute("field","text"); + this.editIndex = this.getAttribute("index"); + this.editDefault = this.getAttribute("default",""); + this.editClass = this.getAttribute("class"); + // 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.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]){ + // 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) { + var editInfo = this.getEditInfo(); + domNode.value = editInfo.value; + } + // Fix the height if needed + this.fixHeight(); + } + } + return false; +}; + +/* +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.fixHeight(); + return true; +}; + +EditTextWidget.prototype.saveChanges = function() { + var text = this.domNodes[0].value + 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..e649341f0 --- /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("title",this.getVariable("tiddlerTitle")); + 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.title || changedAttributes.field || changedAttributes.index) { + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +exports.edit = EditWidget; + +})(); From ebcd5177ec2f74e44702d890643cbaf45cacbdd6 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 15 Oct 2013 14:32:45 +0100 Subject: [PATCH 030/183] Fix-ups to make the new edit widgets work --- core/ui/EditTemplate.tid | 15 ++++++++------- core/ui/SideBar.tid | 4 ++-- themes/tiddlywiki/snowwhite/base.tid | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/core/ui/EditTemplate.tid b/core/ui/EditTemplate.tid index 1f3c2041f..a2114a492 100644 --- a/core/ui/EditTemplate.tid +++ b/core/ui/EditTemplate.tid @@ -1,13 +1,14 @@ title: $:/core/ui/EditTemplate modifier: JeremyRuston - <$button message="tw-delete-tiddler" class="btn-invisible">{{$:/core/images/delete-button}} <$button message="tw-cancel-tiddler" class="btn-invisible">{{$:/core/images/cancel-button}} <$button message="tw-save-tiddler" class="btn-invisible">{{$:/core/images/done-button}} +\define frame-classes() +tw-tiddler-frame $(missingTiddlerClass)$ $(shadowTiddlerClass)$ $(systemTiddlerClass)$ +\end +
>> <$button message="tw-delete-tiddler" class="btn-invisible">{{$:/core/images/delete-button}} <$button message="tw-cancel-tiddler" class="btn-invisible">{{$:/core/images/cancel-button}} <$button message="tw-save-tiddler" class="btn-invisible">{{$:/core/images/done-button}} <$view field="title"/> -@@.title -<$edit field="draft.title"/> -@@ +<$edit-text field="draft.title" class="title tw-edit-texteditor"/> <$transclude title="$:/core/ui/TagsEditor"/> @@ -18,13 +19,13 @@ modifier: JeremyRuston <$transclude />
-<$edit field="text"/> +<$edit field="text" class="tw-edit-texteditor"/>
<$reveal state="$:/ShowEditPreview" type="nomatch" text="yes"> <$transclude title="$:/core/ui/EditorHint"/> <$button type="set" set="$:/ShowEditPreview" setTo="yes">show preview -<$edit field="text"/> +<$edit field="text" class="tw-edit-texteditor"/> -<$transclude title="$:/core/ui/FieldEditor"/> +<$transclude title="$:/core/ui/FieldEditor"/>
diff --git a/core/ui/SideBar.tid b/core/ui/SideBar.tid index edfed5b37..0aaa63c66 100644 --- a/core/ui/SideBar.tid +++ b/core/ui/SideBar.tid @@ -6,7 +6,7 @@ title: $:/core/ui/SideBar <$linkcatcher to="$:/temp/search"> - + @@ -16,7 +16,7 @@ title: $:/core/ui/SideBar //<$count filter="[!is[system]search{$:/temp/search}]"/> matches// -<$list filter="[!is[system]search{$:/temp/search}sort[title]limit[250]]"/> +<$list filter="[!is[system]search{$:/temp/search}sort[title]limit[250]]" template="$:/core/ui/ListItemTemplate"/>
diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 545c85fec..c31d336e2 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -524,7 +524,7 @@ a.tw-tiddlylink-external { color: #888888; } -.tw-tiddler-frame .tw-edit-texteditor input, .tw-tiddler-frame .tw-edit-texteditor textarea { +.tw-tiddler-frame input.tw-edit-texteditor, .tw-tiddler-frame textarea.tw-edit-texteditor { width: 100%; padding: 3px 3px 3px 3px; border: 1px solid #ccc; @@ -533,7 +533,7 @@ a.tw-tiddlylink-external { margin: 4px 0 4px 0; } -.tw-tiddler-frame .tw-edit-texteditor input { +.tw-tiddler-frame input.tw-edit-texteditor { background-color: #f8f8f8; <> } From 5b4ca67d6e9be8ffa999ded824cfeb8a6ad4c618 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 15 Oct 2013 21:06:52 +0100 Subject: [PATCH 031/183] Make the reveal widget hide the div element if it is not open --- core/modules/new_widgets/reveal.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/modules/new_widgets/reveal.js b/core/modules/new_widgets/reveal.js index f57ccf7c2..9bab01071 100755 --- a/core/modules/new_widgets/reveal.js +++ b/core/modules/new_widgets/reveal.js @@ -36,6 +36,9 @@ RevealWidget.prototype.render = function(parent,nextSibling) { if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) { this.positionPopup(domNode); } + if(!this.isOpen) { + domNode.setAttribute("hidden","true") + } this.domNodes.push(domNode); }; From 77248d08a8be57d3cf693c4d89f303963355660a Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 15 Oct 2013 21:07:13 +0100 Subject: [PATCH 032/183] Add the count widget --- core/modules/new_widgets/count.js | 81 +++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 core/modules/new_widgets/count.js diff --git a/core/modules/new_widgets/count.js b/core/modules/new_widgets/count.js new file mode 100644 index 000000000..3385a3d9e --- /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("tiddlerTitle")).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; + +})(); From 06e269f3cb73e6ba5bfde09637b74ded77ab282a Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 15 Oct 2013 21:07:35 +0100 Subject: [PATCH 033/183] Fix the untagged list in the tags list --- core/ui/UntaggedTemplate.tid | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/ui/UntaggedTemplate.tid b/core/ui/UntaggedTemplate.tid index 80a63313e..a6a7e89b3 100644 --- a/core/ui/UntaggedTemplate.tid +++ b/core/ui/UntaggedTemplate.tid @@ -1,8 +1,6 @@ title: $:/core/ui/UntaggedTemplate <$button popup="$:/state/tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-untagged-label tw-tag-label">untagged -<$reveal state="$:/state/tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes" >
-<$list filter="[untagged[]!is[system]] -[tags[]] +[sort[title]]"> -<$view field="title" format="link" /> - +<$reveal state="$:/state/tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes">
+<$list filter="[untagged[]!is[system]] -[tags[]] +[sort[title]]" template="$:/core/ui/ListItemTemplate"/>
From 7f6d770ca6b7de2d5621706c0402d14bd65f7556 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 16 Oct 2013 16:29:35 +0100 Subject: [PATCH 034/183] The reveal widget should generate a span when in inline mode The only reason that the reveal widget generates an element is so that we can use `display:none/block;` (as did the pre-refactored code) as a quick way of hiding and showing the content. We don't do that yet, and it may be best never to do it. --- core/modules/new_widgets/reveal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/new_widgets/reveal.js b/core/modules/new_widgets/reveal.js index 9bab01071..f4a4f00c4 100755 --- a/core/modules/new_widgets/reveal.js +++ b/core/modules/new_widgets/reveal.js @@ -30,7 +30,7 @@ RevealWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); this.execute(); - var domNode = this.document.createElement("div"); + var domNode = this.document.createElement(this.parseTreeNode.isBlock ? "div" : "span"); parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) { From adb395d10fce53c24499a16d7782c292c04b9b73 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 16 Oct 2013 16:29:51 +0100 Subject: [PATCH 035/183] Fix edit-text widget to work with indexed values --- core/modules/new_widgets/edit-text.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index 61d0a6764..d961f2d33 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -68,7 +68,9 @@ EditTextWidget.prototype.getEditInfo = function() { // Get the edit value var tiddler = this.wiki.getTiddler(this.editTitle), value; - if(this.editField) { + 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 @@ -88,8 +90,6 @@ EditTextWidget.prototype.getEditInfo = function() { } value = this.editDefault; } - } else { - value = this.wiki.extractTiddlerDataItem(this.editTitle,this.editIndex,this.editDefault); } return {tiddler: tiddler, value: value}; }; From a3eaffa666f6cefa1449ba73b7c089384e9588dd Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 16 Oct 2013 16:30:06 +0100 Subject: [PATCH 036/183] Add a linkcatcher widget --- core/modules/new_widgets/linkcatcher.js | 87 +++++++++++++++++++++++++ core/modules/new_widgets/tempwidgets.js | 1 - 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 core/modules/new_widgets/linkcatcher.js diff --git a/core/modules/new_widgets/linkcatcher.js b/core/modules/new_widgets/linkcatcher.js new file mode 100644 index 000000000..f5afb1767 --- /dev/null +++ b/core/modules/new_widgets/linkcatcher.js @@ -0,0 +1,87 @@ +/*\ +title: $:/core/modules/new_widgets/linkcatcher.js +type: application/javascript +module-type: new_widget + +Linkcatcher widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var LinkCatcherWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); + this.addEventListeners([ + {type: "tw-navigate", handler: "handleNavigateEvent"} + ]); +}; + +/* +Inherit from the base widget class +*/ +LinkCatcherWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +LinkCatcherWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +LinkCatcherWidget.prototype.execute = function() { + // Get our parameters + this.catchTo = this.getAttribute("to"); + this.catchMessage = this.getAttribute("message"); + this.catchSet = this.getAttribute("set"); + this.catchSetTo = this.getAttribute("setTo"); + // 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 +*/ +LinkCatcherWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.to || changedAttributes.message || changedAttributes.set || changedAttributes.setTo) { + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +/* +Handle a tw-navigate event +*/ +LinkCatcherWidget.prototype.handleNavigateEvent = function(event) { + if(this.catchTo) { + this.wiki.setTextReference(this.catchTo,event.navigateTo,this.getVariable("tiddlerTitle")); + } + if(this.catchMessage) { + this.dispatchEvent({ + type: this.catchMessage, + param: event.navigateTo + }); + } + if(this.catchSet) { + var tiddler = this.wiki.getTiddler(this.catchSet); + this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: this.catchSet, text: this.catchSetTo})); + } + return false; +}; + +exports.linkcatcher = LinkCatcherWidget; + +})(); diff --git a/core/modules/new_widgets/tempwidgets.js b/core/modules/new_widgets/tempwidgets.js index bdae08a62..34c5c77d9 100755 --- a/core/modules/new_widgets/tempwidgets.js +++ b/core/modules/new_widgets/tempwidgets.js @@ -14,7 +14,6 @@ Temporary shim widgets var Widget = require("$:/core/modules/new_widgets/widget.js").widget; -exports.linkcatcher = Widget; exports["import"] = Widget; })(); From 93c2ce6521f1895521a30e865c8c91ef240713a8 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 16 Oct 2013 16:30:24 +0100 Subject: [PATCH 037/183] Fix up edit widgets --- core/wiki/ControlPanel.tid | 8 ++--- editions/tw5.com/tiddlers/TiddlySpot.tid | 4 +-- themes/tiddlywiki/snowwhite/ThemeTweaks.tid | 38 ++++++++++----------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/core/wiki/ControlPanel.tid b/core/wiki/ControlPanel.tid index 7d25f1a46..b367a3a39 100644 --- a/core/wiki/ControlPanel.tid +++ b/core/wiki/ControlPanel.tid @@ -2,13 +2,13 @@ title: $:/ControlPanel ''Initial setup'' -:[[Title of this TiddlyWiki|SiteTitle]]: <$edit tiddler="SiteTitle" default="" type="input"/> +:[[Title of this TiddlyWiki|SiteTitle]]: <$edit-text title="SiteTitle" default="" tag="input"/> -:[[Subtitle|SiteSubtitle]]: <$edit tiddler="SiteSubtitle" default="" type="input"/> +:[[Subtitle|SiteSubtitle]]: <$edit-text title="SiteSubtitle" default="" tag="input"/> -:[[Username for signing edits|$:/status/UserName]]: <$edit tiddler="$:/status/UserName" default="" type="input"/> +:[[Username for signing edits|$:/status/UserName]]: <$edit-text title="$:/status/UserName" default="" tag="input"/> -:[[Animation duration|$:/config/AnimationDuration]]: <$edit tiddler="$:/config/AnimationDuration" default="" type="input"/>

+:[[Animation duration|$:/config/AnimationDuration]]: <$edit-text title="$:/config/AnimationDuration" default="" tag="input"/>

:Edit [[DefaultTiddlers|$:/DefaultTiddlers]] to choose which tiddlers are displayed at startup :{{$:/snippets/encryptionstatus}} diff --git a/editions/tw5.com/tiddlers/TiddlySpot.tid b/editions/tw5.com/tiddlers/TiddlySpot.tid index 199555dad..2d88fb366 100644 --- a/editions/tw5.com/tiddlers/TiddlySpot.tid +++ b/editions/tw5.com/tiddlers/TiddlySpot.tid @@ -18,7 +18,7 @@ TiddlyWiki5 isn't yet built in to TiddlySpot but you can use it by following the ! TiddlySpot details -Wiki name: <$edit tiddler="$:/UploadName" default="" type="input"/> +Wiki name: <$edit-text title="$:/UploadName" default="" tag="input"/> Password: <$password name="upload"/> @@ -26,6 +26,6 @@ Password: <$password name="upload"/> If you're using a server other than TiddlySpot, you can set the server URL here: -Service URL: <$edit tiddler="$:/UploadURL" default="" type="input"/> +Service URL: <$edit-text title="$:/UploadURL" default="" tag="input"/> //(by default, the server URL is `http://.tiddlyspot.com/store.cgi`)// diff --git a/themes/tiddlywiki/snowwhite/ThemeTweaks.tid b/themes/tiddlywiki/snowwhite/ThemeTweaks.tid index 2e8bf11cc..075ee30d5 100644 --- a/themes/tiddlywiki/snowwhite/ThemeTweaks.tid +++ b/themes/tiddlywiki/snowwhite/ThemeTweaks.tid @@ -6,32 +6,32 @@ You can tweak certain aspects of the ''Snow White'' theme. ! Colours -* Primary colour: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="primary" default="" type="color"/> -* Background colour: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="background" default="" type="color"/> -* Foreground colour: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="foreground" default="" type="color"/> -* Page background colour: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="pagebackground" default="" type="color"/> -* Medium contrast color: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="medium" default="" type="color"/> +* Primary colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="primary" default="" tag="input" type="color"/> +* Background colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="background" default="" tag="input" type="color"/> +* Foreground colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="foreground" default="" tag="input" type="color"/> +* Page background colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="pagebackground" default="" tag="input" type="color"/> +* Medium contrast color: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="medium" default="" tag="input" type="color"/> ! Colour mappings -* Tiddler background colour mapping: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="tiddlerbackground" default="" type="input"/> -* Tiddler foreground colour mapping: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="foreground" default="" type="input"/> -* Page background colour mapping: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="pagebackground" default="" type="input"/> -* Link background colour mapping: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkbackground" default="" type="input"/> -* Link foreground colour mapping: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkforeground" default="" type="input"/> -* Dropdown background colour mapping: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownbackground" default="" type="input"/> -* Dropdown border colour mapping: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownborder" default="" type="input"/> +* Tiddler background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="tiddlerbackground" default="" tag="input"/> +* Tiddler foreground colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="foreground" default="" tag="input"/> +* Page background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="pagebackground" default="" tag="input"/> +* Link background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkbackground" default="" tag="input"/> +* Link foreground colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkforeground" default="" tag="input"/> +* Dropdown background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownbackground" default="" tag="input"/> +* Dropdown border colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownborder" default="" tag="input"/> ! Settings -* Font family: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/settings" index="fontfamily" default="" type="input"/> +* Font family: <$edit-text title="$:/themes/tiddlywiki/snowwhite/settings" index="fontfamily" default="" tag="input"/> ! Sizes -* Font size: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="fontsize" default="" type="input"/> -* Line height: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="lineheight" default="" type="input"/> -* Story left position //(the distance between the left of the screen and the left margin of the story river or tiddler area)//: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storyleft" default="" type="input"/> -* Story top position //(the distance between the top of the screen ad the top margin of the story river or tiddler area)//: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storytop" default="" type="input"/> -* Story right //(the distance between the left side of the screen and the left margin of the sidebar area)//: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storyright" default="" type="input"/> -* Story width //(the width of the story river or tiddler area)//: <$edit tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storywidth" default="" type="input"/> +* Font size: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="fontsize" default="" tag="input"/> +* Line height: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="lineheight" default="" tag="input"/> +* Story left position //(the distance between the left of the screen and the left margin of the story river or tiddler area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storyleft" default="" tag="input"/> +* Story top position //(the distance between the top of the screen ad the top margin of the story river or tiddler area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storytop" default="" tag="input"/> +* Story right //(the distance between the left side of the screen and the left margin of the sidebar area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storyright" default="" tag="input"/> +* Story width //(the width of the story river or tiddler area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storywidth" default="" tag="input"/> From 6b9b75142b6482f9b711e127746ba2610240a20e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 17 Oct 2013 16:40:13 +0100 Subject: [PATCH 038/183] Fix text reference regexp to allow for tiddler titles with embedded spaces --- core/modules/utils/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index 46c2a444c..d99b3bd33 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -351,7 +351,7 @@ Returns an object with the following fields, all optional: */ exports.parseTextReference = function(textRef) { // Separate out the title, field name and/or JSON indices - var reTextRef = /^\s*([^\s!#]+)?(?:(?:!!([^\s]+))|(?:##([^\s]+)))?\s*/mg, + var reTextRef = /^\s*([^!#]+)?(?:(?:!!([^\s]+))|(?:##([^\s]+)))?\s*/mg, match = reTextRef.exec(textRef); if(match && reTextRef.lastIndex === textRef.length) { // Return the parts From 55d479c540f5e0a05e6c59ef7eb14606b89857c3 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 17 Oct 2013 16:55:23 +0100 Subject: [PATCH 039/183] Extend macrocall widget to take parameters both from the parse tree node and the attributes This allows us to invoke macros using the ordinary widget syntax, which in turn allows us to use features like indirection on the parameters. --- core/modules/new_widgets/macrocall.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/modules/new_widgets/macrocall.js b/core/modules/new_widgets/macrocall.js index 36b0ed4e5..3d47a9969 100644 --- a/core/modules/new_widgets/macrocall.js +++ b/core/modules/new_widgets/macrocall.js @@ -37,8 +37,13 @@ MacroCallWidget.prototype.render = function(parent,nextSibling) { Compute the internal state of the widget */ MacroCallWidget.prototype.execute = function() { + // 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) { + params.push({name: name, value: attribute}); + }); // Get the macro value - var text = this.getVariable(this.parseTreeNode.name,this.parseTreeNode.params); + var text = this.getVariable(this.parseTreeNode.name || this.getAttribute("$name"),params); // Parse the macro var parser = this.wiki.new_parseText("text/vnd.tiddlywiki",text, {parseAsInline: !this.parseTreeNode.isBlock}), @@ -51,7 +56,14 @@ MacroCallWidget.prototype.execute = function() { Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ MacroCallWidget.prototype.refresh = function(changedTiddlers) { - return this.refreshChildren(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; From 3f151ba70e96db70a7158316e0cee9ea3ce8b517 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 17 Oct 2013 16:57:07 +0100 Subject: [PATCH 040/183] Add support for macro modules Now JavaScript macros can be defined in "macro" modules --- core/modules/macros/makedatauri.js | 44 +++++++++++++++++++++ core/modules/macros/version.js | 30 ++++++++++++++ core/modules/new_widgets/widget.js | 37 ++++++++++++++++- core/modules/startup.js | 1 + editions/test/tiddlers/tests/test-widget.js | 15 +++++++ readme.md | 40 ++++++++----------- 6 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 core/modules/macros/makedatauri.js create mode 100644 core/modules/macros/version.js 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/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/widget.js b/core/modules/new_widgets/widget.js index 3ffff98dc..984efe512 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -81,8 +81,9 @@ Widget.prototype.getVariable = function(name,actualParams,defaultValue) { 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 defaultValue; + return this.evaluateMacroModule(name,actualParams,defaultValue); } // Get the value var value = node.variables[name].value; @@ -129,6 +130,40 @@ Widget.prototype.substituteVariableReferences = function(text) { }); }; +Widget.prototype.evaluateMacroModule = function(name,actualParams,defaultValue) { + if($tw.utils.hop($tw.macros,name)) { + var macro = $tw.macros[name], + args = []; + 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\n
\nContent

"); }); + it("should deal with built-in macros", function() { + var wiki = new $tw.Wiki(); + // Add some tiddlers + wiki.addTiddlers([ + {title: "TiddlerOne", text: "Jolly Old World", type: "text/vnd.tiddlywiki"} + ]); + // Construct the widget node + var text = "\\define makelink(text,type)\n>>My linky link\n\\end\n\n<$macrocall $name=\"makelink\" text={{TiddlerOne}} type={{TiddlerOne!!type}}/>"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\n\nMy linky link

"); + }); + it("should deal with the list widget", function() { var wiki = new $tw.Wiki(); // Add some tiddlers diff --git a/readme.md b/readme.md index eeff45223..2b9b1ab5a 100644 --- a/readme.md +++ b/readme.md @@ -128,8 +128,7 @@ The following commands are available:

-LoadCommand

-
+LoadCommand

Load tiddlers from 2.x.x TiddlyWiki files ( @@ -137,22 +136,20 @@ TiddlyWiki files ( .tiddler, .tid, .json or other files

---load <filepath>
+--load <filepath>

-PasswordCommand

-
+PasswordCommand

Set a password for subsequent crypto operations

---password <password>
+--password <password>

-PrintCommand

-
+PrintCommand

The print command outputs specified information.

@@ -167,24 +164,22 @@ Print the titles of the system tiddlers in the wiki store

 --print system

print config

Print the current core configuration

---print config
+--print config

-RenderTiddlerCommand

-
+RenderTiddlerCommand

Render an individual tiddler as a specified ContentType, defaults to text/html and save it to the specified filename

---rendertiddler <title> <filename> [<type>]
+--rendertiddler <title> <filename> [<type>]

-RenderTiddlersCommand

-
+RenderTiddlersCommand

Render a set of tiddlers matching a filter to separate files of a specified ContentType (defaults to @@ -192,13 +187,12 @@ text/html) and extension (defaults to .html).

 --rendertiddlers <filter> <template> <pathname> [<type>] [<extension>]

For example:

---rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html ./static text/plain
+--rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html ./static text/plain

-VerboseCommand

-
+VerboseCommand

Triggers verbose output, useful for debugging

---verbose
+--verbose

-VersionCommand

-
+VersionCommand

Displays the version number of TiddlyWiki.

---version

+--version

This readme file was automatically generated by TiddlyWiki5 From 35c70943069b3a331a1e730103e420d5c481cb8b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 18 Oct 2013 15:20:33 +0100 Subject: [PATCH 041/183] Start building the static files There's a few parts that don't work yet --- contributing.md | 36 +++++---- nbld.sh | 3 + readme.md | 189 ++++++++++++++++++------------------------------ 3 files changed, 92 insertions(+), 136 deletions(-) diff --git a/contributing.md b/contributing.md index 7dbb858dd..8df961a09 100644 --- a/contributing.md +++ b/contributing.md @@ -1,10 +1,8 @@

-Contributing to -TiddlyWiki5

-
-

- -TiddlyWiki5 welcomes contributions to its code and documentation via +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:

    @@ -16,24 +14,24 @@ 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 -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 +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 +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
@@ -51,10 +49,10 @@ Go to your github repo and create a pull request.

Thank you!

Attribution

-The CLA documents used for this project where created using +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 +This file was automatically generated by TiddlyWiki5

\ No newline at end of file diff --git a/nbld.sh b/nbld.sh index c7dad786e..382d8c25d 100755 --- a/nbld.sh +++ b/nbld.sh @@ -21,6 +21,9 @@ node ./tiddlywiki.js \ ./editions/tw5.com \ --verbose \ --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \ + --new_rendertiddler ReadMe ./readme.md text/html \ + --new_rendertiddler ContributingTemplate ./contributing.md text/html \ + --new_rendertiddler $:/core/templates/static.template.html $TW5_BUILD_OUTPUT/static.html text/plain \ || exit 1 # Run tests diff --git a/readme.md b/readme.md index 2b9b1ab5a..bf179a546 100644 --- a/readme.md +++ b/readme.md @@ -1,105 +1,101 @@

-Welcome to -TiddlyWiki5

-
-

-Welcome to -TiddlyWiki5, a reboot of -TiddlyWiki, the non-linear personal web notebook first released in 2004. It is a complete interactive wiki in -JavaScript that can be run from a single HTML file in the browser or as a powerful +Welcome to +TiddlyWiki5

+Welcome to +TiddlyWiki5, a reboot of +TiddlyWiki, the non-linear personal web notebook first released in 2004. It is a complete interactive wiki in +JavaScript that can be run from a single HTML file in the browser or as a powerful node.js application.

- -TiddlyWiki5 is currently in alpha, meaning it is working but incomplete. It is a great time to get involved and support its + +TiddlyWiki5 is currently in alpha, meaning it is working but incomplete. It is a great time to get involved and support its future development:

- -TiddlyWiki is a free, open source project that depends on + +TiddlyWiki is a free, open source project that depends on your love and support for its survival -

-Getting started with -TiddlyWiki under node.js

-
-

- -TiddlyWiki5 can be used on the command line to perform an extensive set of operations based on tiddlers, -TiddlyWikiFolders, -TiddlerFiles and -TiddlyWikiFiles. For example, the following command loads the tiddlers from a +

+Getting started with +TiddlyWiki under node.js

+ +TiddlyWiki5 can be used on the command line to perform an extensive set of operations based on tiddlers, +TiddlyWikiFolders, +TiddlerFiles and +TiddlyWikiFiles. For example, the following command loads the tiddlers from a TiddlyWiki HTML file and then saves one of them in HTML:

 node tiddlywiki.js --verbose --load mywiki.html --rendertiddler ReadMe ./readme.html

-In order to use -TiddlyWiki5 on the command line you must first install node.js from +In order to use +TiddlyWiki5 on the command line you must first install node.js from http://nodejs.org/

Usage

Running -tiddlywiki.js from the command line boots the +tiddlywiki.js from the command line boots the TiddlyWiki kernel, loads the core plugins and establishes an empty wiki store. It then sequentially processes the command line arguments from left to right. The arguments are separated with spaces.

-The first argument is the optional path to the +The first argument is the optional path to the TiddlyWikiFolder to be loaded. If not present, then the current directory is used.

The commands and their individual arguments follow, each command being identified by the prefix --.

 node tiddlywiki.js [<wikipath>] [--<command> [<arg>[,<arg>]]]

Batch Files

-For trying +For trying TiddlyWiki5 out under node.js, several batch files are provided:

bld.sh builds tw5.com

-This batch file builds several variants of +This batch file builds several variants of TiddlyWiki5 for deployment on tiddlywiki.com.

By default, files are output to a folder called -jermolene.github.com in the same directory as the main +jermolene.github.com in the same directory as the main TiddlyWiki5 directory. You will need to create the directory before running the batch file. For example:

 * /TiddlyWork/ - Directory for working with TiddlyWiki5
 * /TiddlyWork/TiddlyWiki5/ - Directory containing the TiddlyWiki5 repo from GitHub
 * /TiddlyWork/jermolene.github.com/ - Directory for output files

You can override the build output directory by defining the environment variable -TW5_BUILD_OUTPUT. The easiest way to do this is to create a personal batch file to invoke +TW5_BUILD_OUTPUT. The easiest way to do this is to create a personal batch file to invoke TiddlyWiki5 that first sets the environment variable and then invokes bld.sh.

The files output by bld.sh are:

-bld.sh also runs the -TiddlyWiki5 node.js-based test suite (see +bld.sh also runs the +TiddlyWiki5 node.js-based test suite (see TestingMechanism)

serve.sh serves tw5.com

-This batch file starts +This batch file starts TiddlyWiki5 running as an HTTP server with the content from the clientserver edition. By default, the script serves on port 8080.

 ./server.sh UserName

@@ -108,49 +104,33 @@ To experiment with this configuration, run the script and then visit http://0.0.0.0:8080 in a browser.

Changes made in the browser propagate to the server over HTTP (use the browser developer console to see these requests). The server then syncs changes to the file system (and logs each change to the screen).

-wbld.sh builds -TiddlyWiki5 for +wbld.sh builds +TiddlyWiki5 for TiddlyWeb

-This batch file builds and deploys the code for -TiddlyWiki5 in the Sky for TiddlyWeb. If you want to experiment with your own builds of -TiddlyWiki5 for +This batch file builds and deploys the code for +TiddlyWiki5 in the Sky for TiddlyWeb. If you want to experiment with your own builds of +TiddlyWiki5 for TiddlyWeb you could use this batch file as a base.

-2bld.sh builds +2bld.sh builds TiddlyWiki 2.6.5

-This batch file builds +This batch file builds TiddlyWiki 2.6.5 from the original source and then uses the opendiff program to display the differences between them.

Commands

-The following commands are available:

-
-
-

- - -LoadCommand

-

-Load tiddlers from 2.x.x +The following commands are available:

+LoadCommand

+Load tiddlers from 2.x.x TiddlyWiki files ( .html), .tiddler, .tid, .json or other files

---load <filepath>
-
-

- - -PasswordCommand

-

+--load <filepath>

+PasswordCommand

Set a password for subsequent crypto operations

---password <password>
-
-

- - -PrintCommand

-

+--password <password>

+PrintCommand

The print command outputs specified information.

print tiddlers

@@ -164,39 +144,24 @@ Print the titles of the system tiddlers in the wiki store

 --print system

print config

Print the current core configuration

---print config
-
-

- - -RenderTiddlerCommand

-

-Render an individual tiddler as a specified +--print config

+RenderTiddlerCommand

+Render an individual tiddler as a specified ContentType, defaults to text/html and save it to the specified filename

---rendertiddler <title> <filename> [<type>]
-
-

- - -RenderTiddlersCommand

-

-Render a set of tiddlers matching a filter to separate files of a specified +--rendertiddler <title> <filename> [<type>]

+RenderTiddlersCommand

+Render a set of tiddlers matching a filter to separate files of a specified ContentType (defaults to text/html) and extension (defaults to .html).

 --rendertiddlers <filter> <template> <pathname> [<type>] [<extension>]

For example:

---rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html ./static text/plain
-
-

- - -ServerCommand

-

-The server built in to -TiddlyWiki5 is very simple. Although compatible with -TiddlyWeb it doesn't support many of the features needed for robust Internet-facing usage - in particular, +--rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html ./static text/plain

+ServerCommand

+The server built in to +TiddlyWiki5 is very simple. Although compatible with +TiddlyWeb it doesn't support many of the features needed for robust Internet-facing usage - in particular, TiddlyWiki5 is an old-school wiki in the sense that it offers no authentication.

At the root, it serves a rendering of a specified tiddler. Away from the root, it serves individual tiddlers encoded in JSON, and supports the basic HTTP operations for GET, @@ -216,25 +181,15 @@ servetype - the content type with which the root tiddler should be serv username - the default username for signing edits

For example:

---server 8080 $:/core/tiddlywiki5.template.html text/plain text/html MyUserName
-
-

- - -VerboseCommand

-

+--server 8080 $:/core/tiddlywiki5.template.html text/plain text/html MyUserName

+VerboseCommand

Triggers verbose output, useful for debugging

---verbose

+--version

-This readme file was automatically generated by +This readme file was automatically generated by TiddlyWiki5

\ No newline at end of file From 3005604b8371463dd05ec722ea54c1e3fdb19eb0 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:31:04 +0100 Subject: [PATCH 042/183] Give link widget support for configuration variables One can now use `\define tw-wikilinks() no` to disable wikilinks, and `tw-wikilink-template` to configure the link template. --- core/modules/new_widgets/link.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/link.js b/core/modules/new_widgets/link.js index 7c74d3c5d..39c531d83 100755 --- a/core/modules/new_widgets/link.js +++ b/core/modules/new_widgets/link.js @@ -34,6 +34,25 @@ LinkWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); // Execute our logic this.execute(); + // Get the value of the tw-wikilinks configuration macro + var wikiLinksMacro = this.getVariable("tw-wikilinks"), + useWikiLinks = wikiLinksMacro ? !(wikiLinksMacro.trim() === "no") : true; + // Render the link if required + if(useWikiLinks) { + this.renderLink(parent,nextSibling); + } else { + // Just insert the link text + var domNode = this.document.createElement("span"); + parent.insertBefore(domNode,nextSibling); + this.renderChildren(domNode,null); + this.domNodes.push(domNode); + } +}; + +/* +Render this widget into the DOM +*/ +LinkWidget.prototype.renderLink = function(parent,nextSibling) { // Create our element var domNode = this.document.createElement("a"); // Assign classes @@ -49,7 +68,11 @@ LinkWidget.prototype.render = function(parent,nextSibling) { } } // Set an href - domNode.setAttribute("href",this.to); + var wikiLinkTemplateMacro = this.getVariable("tw-wikilink-template"), + wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$", + wikiLinkText = wikiLinkTemplate.replace("$uri_encoded$",encodeURIComponent(this.to)); + wikiLinkText = wikiLinkText.replace("$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to))); + domNode.setAttribute("href",wikiLinkText); // Add a click event handler domNode.addEventListener("click",function (event) { // Send the click on it's way as a navigate event From 167116d4161d3717cd5a9d97f0cb93a2390a7c93 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:31:27 +0100 Subject: [PATCH 043/183] Fix output type for view format `htmlwikified` --- core/modules/new_widgets/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index 245a149e3..5294b8e15 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -118,7 +118,7 @@ ViewWidget.prototype.getValueAsText = function() { }; ViewWidget.prototype.getValueAsHtmlWikified = function() { - return this.wiki.new_renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText()); + return this.wiki.new_renderText("text/html","text/vnd.tiddlywiki",this.getValueAsText(),this); }; ViewWidget.prototype.getValueAsHtmlEncoded = function() { From dc9c2522b954505def44010658896695f7af9589 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:31:41 +0100 Subject: [PATCH 044/183] Fix typo in macro variable substitution --- core/modules/new_widgets/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index 984efe512..68307dcc1 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -126,7 +126,7 @@ Widget.prototype.substituteVariableParameters = function(text,formalParams,actua Widget.prototype.substituteVariableReferences = function(text) { var self = this; return text.replace(/\$\(([^\)\$]+)\)\$/g,function(match,p1,offset,string) { - return self.getVariable(p1,""); + return self.getVariable(p1,null,""); }); }; From e33f588b73ecd8b586df02f13988ccfbecfc3415 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:32:19 +0100 Subject: [PATCH 045/183] Let the new rendering methods accept a parent widget --- core/modules/wiki.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/modules/wiki.js b/core/modules/wiki.js index d87eff86e..c65c7dc10 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -715,11 +715,12 @@ Parse text in a specified format and render it into another format textType: content type of the input text text: input text */ -exports.new_renderText = function(outputType,textType,text,context) { +exports.new_renderText = function(outputType,textType,text,parentWidget) { var parser = $tw.wiki.new_parseText(textType,text), parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined, widgetNode = new widget.widget(parseTreeNode,{ wiki: this, + parentWidget: parentWidget, document: $tw.document }); var container = $tw.document.createElement("div"); @@ -746,11 +747,12 @@ Parse text from a tiddler and render it into another format outputType: content type for the output title: title of the tiddler to be rendered */ -exports.new_renderTiddler = function(outputType,title,context) { +exports.new_renderTiddler = function(outputType,title,parentWidget) { var parser = $tw.wiki.new_parseTiddler(title), parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined, widgetNode = new widget.widget(parseTreeNode,{ wiki: this, + parentWidget: parentWidget, document: $tw.document }); var container = $tw.document.createElement("div"); From 26ac9406374db689efbfc264a6757d7cffbd1682 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:32:29 +0100 Subject: [PATCH 046/183] Fix typo --- core/modules/wiki.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/wiki.js b/core/modules/wiki.js index c65c7dc10..cca58c2c1 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -648,7 +648,7 @@ var tweakMacroDefinition = function(nodeList) { }; nodeList[0].children = nodeList.slice(1); nodeList.splice(1,nodeList.length-1); - tweakMacroDefinition(nodeList.children); + tweakMacroDefinition(nodeList[0].children); } }; From 3d30ff982875f4713be9cd71045161a05eba0326 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:32:40 +0100 Subject: [PATCH 047/183] Fix static tiddler template --- core/templates/static.tiddler.html.tid | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/templates/static.tiddler.html.tid b/core/templates/static.tiddler.html.tid index 198d5a5f4..75ced02e4 100644 --- a/core/templates/static.tiddler.html.tid +++ b/core/templates/static.tiddler.html.tid @@ -14,9 +14,7 @@ title: $:/core/templates/static.tiddler.html `{{$:/StaticBanner||$:/core/templates/html-tiddler}}`
-
-`<$view tiddler="$:/core/ui/ViewTemplate" format="htmlwikified"/>` -
+`<$view title="$:/core/ui/ViewTemplate" format="htmlwikified"/>`
From 89310e71c832e2845ac6d31b20e9c2e9a922e614 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:33:00 +0100 Subject: [PATCH 048/183] Add a new version of rendertiddlers command So that we can test static html generation --- core/modules/commands/new_rendertiddlers.js | 66 +++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 core/modules/commands/new_rendertiddlers.js diff --git a/core/modules/commands/new_rendertiddlers.js b/core/modules/commands/new_rendertiddlers.js new file mode 100644 index 000000000..be513b0bb --- /dev/null +++ b/core/modules/commands/new_rendertiddlers.js @@ -0,0 +1,66 @@ +/*\ +title: $:/core/modules/commands/new_rendertiddlers.js +type: application/javascript +module-type: command + +Command to render several tiddlers to a folder of files + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var widget = require("$:/core/modules/new_widgets/widget.js"); + +exports.info = { + name: "new_rendertiddlers", + synchronous: true +}; + +var Command = function(params,commander,callback) { + this.params = params; + this.commander = commander; + this.callback = callback; +}; + +Command.prototype.execute = function() { + if(this.params.length < 2) { + return "Missing filename"; + } + var self = this, + fs = require("fs"), + path = require("path"), + wiki = this.commander.wiki, + filter = this.params[0], + template = this.params[1], + pathname = this.params[2], + type = this.params[3] || "text/html", + extension = this.params[4] || ".html", + tiddlers = wiki.filterTiddlers(filter); + $tw.utils.each(tiddlers,function(title) { + var parser = wiki.new_parseTiddler(template), + parseTreeNode = parser ? {type: "widget", children: [{ + type: "setvariable", + attributes: { + name: {type: "string", value: "tiddlerTitle"}, + value: {type: "string", value: title} + }, + children: parser.tree + }]} : undefined, + widgetNode = new widget.widget(parseTreeNode,{ + wiki: wiki, + document: $tw.document + }); + var container = $tw.document.createElement("div"); + widgetNode.render(container,null); + var text = type === "text/html" ? container.innerHTML : container.textContent; + fs.writeFileSync(path.resolve(pathname,encodeURIComponent(title) + extension),text,"utf8"); + }); + return null; +}; + +exports.Command = Command; + +})(); From c9668f93b5ea86fe7ee04d28e260ca78b3cbaa91 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:33:25 +0100 Subject: [PATCH 049/183] Test building static css and individual tiddlers --- nbld.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nbld.sh b/nbld.sh index 382d8c25d..52082a18f 100755 --- a/nbld.sh +++ b/nbld.sh @@ -24,6 +24,8 @@ node ./tiddlywiki.js \ --new_rendertiddler ReadMe ./readme.md text/html \ --new_rendertiddler ContributingTemplate ./contributing.md text/html \ --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 # Run tests From 79e7dc4f18b6cfd2243f2b828ce28cb035ba6f51 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:33:31 +0100 Subject: [PATCH 050/183] Content updates --- contributing.md | 30 +++++++------- readme.md | 104 ++++++++++++++++++++++++------------------------ 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/contributing.md b/contributing.md index 8df961a09..1632759db 100644 --- a/contributing.md +++ b/contributing.md @@ -1,8 +1,8 @@

-Contributing to +Contributing to TiddlyWiki5

- -TiddlyWiki5 welcomes contributions to its code and documentation via + +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:

    @@ -14,24 +14,24 @@ 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 -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 +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 +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
@@ -49,10 +49,10 @@ Go to your github repo and create a pull request.

Thank you!

Attribution

-The CLA documents used for this project where created using +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 +This file was automatically generated by TiddlyWiki5

\ No newline at end of file diff --git a/readme.md b/readme.md index bf179a546..cfe4bb609 100644 --- a/readme.md +++ b/readme.md @@ -1,101 +1,101 @@

-Welcome to +Welcome to TiddlyWiki5

-Welcome to -TiddlyWiki5, a reboot of -TiddlyWiki, the non-linear personal web notebook first released in 2004. It is a complete interactive wiki in -JavaScript that can be run from a single HTML file in the browser or as a powerful +Welcome to +TiddlyWiki5, a reboot of +TiddlyWiki, the non-linear personal web notebook first released in 2004. It is a complete interactive wiki in +JavaScript that can be run from a single HTML file in the browser or as a powerful node.js application.

- -TiddlyWiki5 is currently in alpha, meaning it is working but incomplete. It is a great time to get involved and support its + +TiddlyWiki5 is currently in alpha, meaning it is working but incomplete. It is a great time to get involved and support its future development:

- -TiddlyWiki is a free, open source project that depends on + +TiddlyWiki is a free, open source project that depends on your love and support for its survival

-Getting started with +Getting started with TiddlyWiki under node.js

- -TiddlyWiki5 can be used on the command line to perform an extensive set of operations based on tiddlers, -TiddlyWikiFolders, -TiddlerFiles and -TiddlyWikiFiles. For example, the following command loads the tiddlers from a + +TiddlyWiki5 can be used on the command line to perform an extensive set of operations based on tiddlers, +TiddlyWikiFolders, +TiddlerFiles and +TiddlyWikiFiles. For example, the following command loads the tiddlers from a TiddlyWiki HTML file and then saves one of them in HTML:

 node tiddlywiki.js --verbose --load mywiki.html --rendertiddler ReadMe ./readme.html

-In order to use -TiddlyWiki5 on the command line you must first install node.js from +In order to use +TiddlyWiki5 on the command line you must first install node.js from http://nodejs.org/

Usage

Running -tiddlywiki.js from the command line boots the +tiddlywiki.js from the command line boots the TiddlyWiki kernel, loads the core plugins and establishes an empty wiki store. It then sequentially processes the command line arguments from left to right. The arguments are separated with spaces.

-The first argument is the optional path to the +The first argument is the optional path to the TiddlyWikiFolder to be loaded. If not present, then the current directory is used.

The commands and their individual arguments follow, each command being identified by the prefix --.

 node tiddlywiki.js [<wikipath>] [--<command> [<arg>[,<arg>]]]

Batch Files

-For trying +For trying TiddlyWiki5 out under node.js, several batch files are provided:

bld.sh builds tw5.com

-This batch file builds several variants of +This batch file builds several variants of TiddlyWiki5 for deployment on tiddlywiki.com.

By default, files are output to a folder called -jermolene.github.com in the same directory as the main +jermolene.github.com in the same directory as the main TiddlyWiki5 directory. You will need to create the directory before running the batch file. For example:

 * /TiddlyWork/ - Directory for working with TiddlyWiki5
 * /TiddlyWork/TiddlyWiki5/ - Directory containing the TiddlyWiki5 repo from GitHub
 * /TiddlyWork/jermolene.github.com/ - Directory for output files

You can override the build output directory by defining the environment variable -TW5_BUILD_OUTPUT. The easiest way to do this is to create a personal batch file to invoke +TW5_BUILD_OUTPUT. The easiest way to do this is to create a personal batch file to invoke TiddlyWiki5 that first sets the environment variable and then invokes bld.sh.

The files output by bld.sh are:

-bld.sh also runs the -TiddlyWiki5 node.js-based test suite (see +bld.sh also runs the +TiddlyWiki5 node.js-based test suite (see TestingMechanism)

serve.sh serves tw5.com

-This batch file starts +This batch file starts TiddlyWiki5 running as an HTTP server with the content from the clientserver edition. By default, the script serves on port 8080.

 ./server.sh UserName

@@ -104,23 +104,23 @@ To experiment with this configuration, run the script and then visit http://0.0.0.0:8080 in a browser.

Changes made in the browser propagate to the server over HTTP (use the browser developer console to see these requests). The server then syncs changes to the file system (and logs each change to the screen).

-wbld.sh builds -TiddlyWiki5 for +wbld.sh builds +TiddlyWiki5 for TiddlyWeb

-This batch file builds and deploys the code for -TiddlyWiki5 in the Sky for TiddlyWeb. If you want to experiment with your own builds of -TiddlyWiki5 for +This batch file builds and deploys the code for +TiddlyWiki5 in the Sky for TiddlyWeb. If you want to experiment with your own builds of +TiddlyWiki5 for TiddlyWeb you could use this batch file as a base.

-2bld.sh builds +2bld.sh builds TiddlyWiki 2.6.5

-This batch file builds +This batch file builds TiddlyWiki 2.6.5 from the original source and then uses the opendiff program to display the differences between them.

Commands

The following commands are available:

LoadCommand

-Load tiddlers from 2.x.x +Load tiddlers from 2.x.x TiddlyWiki files ( .html), .tiddler, @@ -146,12 +146,12 @@ print config

Print the current core configuration

 --print config

RenderTiddlerCommand

-Render an individual tiddler as a specified +Render an individual tiddler as a specified ContentType, defaults to text/html and save it to the specified filename

 --rendertiddler <title> <filename> [<type>]

RenderTiddlersCommand

-Render a set of tiddlers matching a filter to separate files of a specified +Render a set of tiddlers matching a filter to separate files of a specified ContentType (defaults to text/html) and extension (defaults to .html).

@@ -159,9 +159,9 @@ text/html) and extension (defaults to 
 For example:

 --rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html ./static text/plain

ServerCommand

-The server built in to -TiddlyWiki5 is very simple. Although compatible with -TiddlyWeb it doesn't support many of the features needed for robust Internet-facing usage - in particular, +The server built in to +TiddlyWiki5 is very simple. Although compatible with +TiddlyWeb it doesn't support many of the features needed for robust Internet-facing usage - in particular, TiddlyWiki5 is an old-school wiki in the sense that it offers no authentication.

At the root, it serves a rendering of a specified tiddler. Away from the root, it serves individual tiddlers encoded in JSON, and supports the basic HTTP operations for GET, @@ -186,10 +186,10 @@ VerboseCommand

Triggers verbose output, useful for debugging

 --verbose

VersionCommand

-Displays the version number of +Displays the version number of TiddlyWiki.

 --version

-This readme file was automatically generated by +This readme file was automatically generated by TiddlyWiki5

\ No newline at end of file From ce1fb300f8e794e2c75887ad65fb1bfd1c4e976e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 18:38:20 +0100 Subject: [PATCH 051/183] Use the new widget mechanism for stylesheet parsing and rendering --- core/modules/utils/dom/styles.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/modules/utils/dom/styles.js b/core/modules/utils/dom/styles.js index 8390c0016..0f29a77db 100644 --- a/core/modules/utils/dom/styles.js +++ b/core/modules/utils/dom/styles.js @@ -34,12 +34,7 @@ StylesheetManager.prototype.addStylesheet = function(title) { // Record the stylesheet in the hashmap this.stylesheets[title] = true; // Parse the tiddler and render as plain text - var parser = this.wiki.parseTiddler(title), - renderTree = new $tw.WikiRenderTree(parser,{wiki: this.wiki, context: {tiddlerTitle: title}, document: $tw.document}); - renderTree.execute(); - var container = $tw.document.createElement("div"); - renderTree.renderInDom(container); - var text = container.textContent; + var text = this.wiki.new_renderTiddler("text/plain",title); // Create a style element and put it in the document var styleNode = document.createElement("style"); styleNode.setAttribute("type","text/css"); From 48dbacc88aaca21b0ad33c555b5e280f92ecde1e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 20:13:08 +0100 Subject: [PATCH 052/183] Add support for widget event listeners to be specified as functions --- core/modules/new_widgets/widget.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index 68307dcc1..09717482c 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -318,6 +318,11 @@ Widget.prototype.addEventListener = function(type,handler) { 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); + } + } }; From 2a571b4f5f7a9942f1fd0ca1867996bbcf665b48 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 20:14:01 +0100 Subject: [PATCH 053/183] Switch the notifier and modal mechanisms to use the new widget mechanism Note the use of the `$tw.rootWidget` to collect together the root event handlers --- core/modules/startup.js | 44 +++++++++++-------------- core/modules/utils/dom/modal.js | 52 +++++++++++++++++++----------- core/modules/utils/dom/notifier.js | 16 ++++++--- 3 files changed, 64 insertions(+), 48 deletions(-) diff --git a/core/modules/startup.js b/core/modules/startup.js index 484475c13..606cb15d6 100755 --- a/core/modules/startup.js +++ b/core/modules/startup.js @@ -63,29 +63,38 @@ exports.startup = function() { $tw.popup = new $tw.utils.Popup({ rootElement: document.body }); + // Install the animator + $tw.anim = new $tw.utils.Animator(); + // Kick off the stylesheet manager + $tw.stylesheetManager = new $tw.utils.StylesheetManager($tw.wiki); + // Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers + $tw.rootWidget = new widget.widget({type: "widget", children: []},{ + wiki: $tw.wiki, + document: document + }); // Install the modal message mechanism $tw.modal = new $tw.utils.Modal($tw.wiki); - document.addEventListener("tw-modal",function(event) { + $tw.rootWidget.addEventListener("tw-modal",function(event) { $tw.modal.display(event.param); - },false); + }); // Install the notification mechanism $tw.notifier = new $tw.utils.Notifier($tw.wiki); - document.addEventListener("tw-notify",function(event) { + $tw.rootWidget.addEventListener("tw-notify",function(event) { $tw.notifier.display(event.param); - },false); + }); // Install the scroller $tw.pageScroller = new $tw.utils.PageScroller(); - document.addEventListener("tw-scroll",$tw.pageScroller,false); + $tw.rootWidget.addEventListener("tw-scroll",$tw.pageScroller); // Install the save action handler $tw.wiki.initSavers(); - document.addEventListener("tw-save-wiki",function(event) { + $tw.rootWidget.addEventListener("tw-save-wiki",function(event) { $tw.wiki.saveWiki({ template: event.param, downloadType: "text/plain" }); - },false); + }); // Install the crypto event handlers - document.addEventListener("tw-set-password",function(event) { + $tw.rootWidget.addEventListener("tw-set-password",function(event) { $tw.passwordPrompt.createPrompt({ serviceName: "Set a new password for this TiddlyWiki", noUserName: true, @@ -96,19 +105,16 @@ exports.startup = function() { } }); }); - document.addEventListener("tw-clear-password",function(event) { + $tw.rootWidget.addEventListener("tw-clear-password",function(event) { $tw.crypto.setPassword(null); }); - // Install the animator - $tw.anim = new $tw.utils.Animator(); - // Kick off the stylesheet manager - $tw.stylesheetManager = new $tw.utils.StylesheetManager($tw.wiki); // Display the PageTemplate var templateTitle = "$:/core/ui/PageTemplate", parser = $tw.wiki.new_parseTiddler(templateTitle), parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined, widgetNode = new widget.widget(parseTreeNode,{ wiki: $tw.wiki, + parentWidget: $tw.rootWidget, document: document }); $tw.new_pageContainer = document.createElement("div"); @@ -118,18 +124,6 @@ exports.startup = function() { $tw.wiki.addEventListener("change",function(changes) { widgetNode.refresh(changes,$tw.new_pageContainer,null); }); - // // Display the old PageTemplate - // var old_templateTitle = "$:/core/ui/PageTemplate", - // old_parser = $tw.wiki.parseTiddler(old_templateTitle), - // renderTree = new $tw.WikiRenderTree(old_parser,{wiki: $tw.wiki, context: {tiddlerTitle: old_templateTitle}, document: document}); - // renderTree.execute(); - // $tw.pageContainer = document.createElement("div"); - // $tw.utils.addClass($tw.pageContainer,"tw-page-container"); - // document.body.insertBefore($tw.pageContainer,document.body.firstChild); - // renderTree.renderInDom($tw.pageContainer); - // $tw.wiki.addEventListener("change",function(changes) { - // renderTree.refreshInDom(changes); - // }); // If we're being viewed on a data: URI then give instructions for how to save if(document.location.protocol === "data:") { $tw.utils.dispatchCustomEvent(document,"tw-modal",{ diff --git a/core/modules/utils/dom/modal.js b/core/modules/utils/dom/modal.js index aeca69a1b..641f68938 100644 --- a/core/modules/utils/dom/modal.js +++ b/core/modules/utils/dom/modal.js @@ -12,6 +12,8 @@ Modal message mechanism /*global $tw: false */ "use strict"; +var widget = require("$:/core/modules/new_widgets/widget.js"); + var Modal = function(wiki) { this.wiki = wiki; this.modalCount = 0; @@ -71,20 +73,28 @@ Modal.prototype.display = function(title,options) { } else { titleText = title; } - var headerParser = this.wiki.parseText("text/vnd.tiddlywiki",titleText,{parseAsInline: true}), - headerRenderTree = new $tw.WikiRenderTree(headerParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document}); - headerRenderTree.execute(); - headerRenderTree.renderInDom(headerTitle); + var headerParser = this.wiki.new_parseText("text/vnd.tiddlywiki",titleText,{parseAsInline: true}), + headerParseTreeNode = headerParser ? {type: "widget", children: headerParser.tree} : undefined, + headerWidgetNode = new widget.widget(headerParseTreeNode,{ + wiki: this.wiki, + parentWidget: $tw.rootWidget, + document: document + }); + headerWidgetNode.render(modalHeader,null); this.wiki.addEventListener("change",function(changes) { - headerRenderTree.refreshInDom(changes); + headerWidgetNode.refresh(changes,modalHeader,null); }); // Render the body of the message - var bodyParser = this.wiki.parseTiddler(title), - bodyRenderTree = new $tw.WikiRenderTree(bodyParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document}); - bodyRenderTree.execute(); - bodyRenderTree.renderInDom(modalBody); + var bodyParser = this.wiki.new_parseTiddler(title), + bodyParseTreeNode = bodyParser ? {type: "widget", children: bodyParser.tree} : undefined, + bodyWidgetNode = new widget.widget(bodyParseTreeNode,{ + wiki: this.wiki, + parentWidget: $tw.rootWidget, + document: document + }); + bodyWidgetNode.render(modalBody,null); this.wiki.addEventListener("change",function(changes) { - bodyRenderTree.refreshInDom(changes); + bodyWidgetNode.refresh(changes,modalBody,null); }); // Setup the link if present if(options.downloadLink) { @@ -107,15 +117,19 @@ Modal.prototype.display = function(title,options) { } else { footerText = '<$button message="tw-close-tiddler" class="btn btn-primary">Close'; } - var footerParser = this.wiki.parseText("text/vnd.tiddlywiki",footerText,{parseAsInline: true}), - footerRenderTree = new $tw.WikiRenderTree(footerParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document}); - footerRenderTree.execute(); - footerRenderTree.renderInDom(modalFooterButtons); + var footerParser = this.wiki.new_parseText("text/vnd.tiddlywiki",footerText,{parseAsInline: true}), + footerParseTreeNode = footerParser ? {type: "widget", children: footerParser.tree} : undefined, + footerWidgetNode = new widget.widget(footerParseTreeNode,{ + wiki: this.wiki, + parentWidget: $tw.rootWidget, + document: document + }); + footerWidgetNode.render(modalFooterButtons,null); this.wiki.addEventListener("change",function(changes) { - footerRenderTree.refreshInDom(changes); + footerWidgetNode.refresh(changes,modalFooterButtons,null); }); // Add the close event handler - wrapper.addEventListener("tw-close-tiddler",function(event) { + var closeHandler = function(event) { // Decrease the modal count and adjust the body class self.modalCount--; self.adjustPageClass(); @@ -136,9 +150,11 @@ Modal.prototype.display = function(title,options) { } },duration); // Don't let anyone else handle the tw-close-tiddler message - event.stopPropagation(); return false; - },false); + }; + headerWidgetNode.addEventListener("tw-close-tiddler",closeHandler,false); + bodyWidgetNode.addEventListener("tw-close-tiddler",closeHandler,false); + footerWidgetNode.addEventListener("tw-close-tiddler",closeHandler,false); // Set the initial styles for the message $tw.utils.setStyle(modalBackdrop,[ {opacity: "0"} diff --git a/core/modules/utils/dom/notifier.js b/core/modules/utils/dom/notifier.js index 9f2fb3588..ad9aab86e 100644 --- a/core/modules/utils/dom/notifier.js +++ b/core/modules/utils/dom/notifier.js @@ -12,6 +12,8 @@ Notifier mechanism /*global $tw: false */ "use strict"; +var widget = require("$:/core/modules/new_widgets/widget.js"); + var Notifier = function(wiki) { this.wiki = wiki; }; @@ -35,12 +37,16 @@ Notifier.prototype.display = function(title,options) { // Add classes $tw.utils.addClass(notification,"tw-notification"); // Render the body of the notification - var bodyParser = this.wiki.parseTiddler(title), - bodyRenderTree = new $tw.WikiRenderTree(bodyParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document}); - bodyRenderTree.execute(); - bodyRenderTree.renderInDom(notification); + var parser = this.wiki.new_parseTiddler(title), + parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined, + widgetNode = new widget.widget(parseTreeNode,{ + wiki: this.wiki, + parentWidget: $tw.rootWidget, + document: document + }); + widgetNode.render(notification,null); this.wiki.addEventListener("change",function(changes) { - bodyRenderTree.refreshInDom(changes); + widgetNode.refresh(changes,notification,null); }); // Set the initial styles for the notification $tw.utils.setStyle(notification,[ From a99f912badaae6a951987d37447bf74be52978e7 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 20:20:32 +0100 Subject: [PATCH 054/183] Fix typo --- core/modules/new_widgets/link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/new_widgets/link.js b/core/modules/new_widgets/link.js index 39c531d83..cf8c81a25 100755 --- a/core/modules/new_widgets/link.js +++ b/core/modules/new_widgets/link.js @@ -27,7 +27,6 @@ LinkWidget.prototype = new Widget(); Render this widget into the DOM */ LinkWidget.prototype.render = function(parent,nextSibling) { - var self = this; // Save the parent dom node this.parentDomNode = parent; // Compute our attributes @@ -53,6 +52,7 @@ LinkWidget.prototype.render = function(parent,nextSibling) { Render this widget into the DOM */ LinkWidget.prototype.renderLink = function(parent,nextSibling) { + var self = this; // Create our element var domNode = this.document.createElement("a"); // Assign classes From c96cb6e95659631ab3fdf5141b5aa81e70511f4d Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 20:20:44 +0100 Subject: [PATCH 055/183] Parse typed blocks with the new parser --- core/modules/parsers/wikiparser/rules/typedblock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/parsers/wikiparser/rules/typedblock.js b/core/modules/parsers/wikiparser/rules/typedblock.js index 0355b1f90..6af2742f6 100644 --- a/core/modules/parsers/wikiparser/rules/typedblock.js +++ b/core/modules/parsers/wikiparser/rules/typedblock.js @@ -57,7 +57,7 @@ exports.parse = function() { this.parser.pos = this.parser.sourceLength; } // Parse the block according to the specified type - var parser = this.parser.wiki.parseText(parseType,text,{defaultType: "text/plain"}); + var parser = this.parser.wiki.new_parseText(parseType,text,{defaultType: "text/plain"}); // If there's no render type, just return the parse tree if(!renderType) { return parser.tree; From 409152afb89018574f4baa0c54c65378bbf07142 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 22:26:36 +0100 Subject: [PATCH 056/183] Make the default list widget template include a div or span Otherwise writing this makes a list on one line: ``` {{{ [tag[done]] }}} ``` --- core/modules/new_widgets/list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/modules/new_widgets/list.js b/core/modules/new_widgets/list.js index 21e6096bd..93b8005e3 100755 --- a/core/modules/new_widgets/list.js +++ b/core/modules/new_widgets/list.js @@ -99,9 +99,9 @@ ListWidget.prototype.makeItemTemplate = function(title) { templateTree = this.parseTreeNode.children; } else { // Default template is a link to the title - templateTree = [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ + templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ {type: "text", text: title} - ]}]; + ]}]}]; } } if(!this.hasAttribute("hackCurrentTiddler")) { From e2b8249a1b5493818743db6d57a954af26203db7 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 22:27:12 +0100 Subject: [PATCH 057/183] Fix problem with findNextSibling() --- core/modules/new_widgets/widget.js | 6 +- editions/test/tiddlers/tests/test-widget.js | 83 +++++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index 09717482c..93ed058ca 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -391,9 +391,9 @@ if(index === -1) { } } // Go back and look for later siblings of our parent if it has the same parent dom node - parent = parent.parentWidget; - if(parent && parent.parentWidget && parent.parentWidget.parentDomNode === this.parentDomNode) { - index = parent.parentWidget.children.indexOf(parent); + var grandParent = parent.parentWidget; + if(grandParent && parent.parentDomNode === this.parentDomNode) { + index = grandParent.children.indexOf(parent); return parent.findNextSibling(index); } return null; diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js index 0b1180754..7e010c880 100755 --- a/editions/test/tiddlers/tests/test-widget.js +++ b/editions/test/tiddlers/tests/test-widget.js @@ -362,6 +362,89 @@ describe("Widget module", function() { }); }); + it("should deal with the list widget followed by other widgets", function() { + var wiki = new $tw.Wiki(); + // Add some tiddlers + wiki.addTiddlers([ + {title: "TiddlerOne", text: "Jolly Old World"}, + {title: "TiddlerTwo", text: "Worldly Old Jelly"}, + {title: "TiddlerThree", text: "Golly Gosh"}, + {title: "TiddlerFour", text: "Lemon Squash"} + ]); + // Construct the widget node + var text = "<$list><$view field='title'/>Something"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("

\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwoSomething

"); + // Check the next siblings of each of the list elements + var listWidget = widgetNode.children[0].children[0]; + // Add another tiddler + wiki.addTiddler({title: "TiddlerFive", text: "Jalapeno Peppers"}); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["TiddlerFive"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwoSomething

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

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerTwoSomething

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

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwoSomething

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(7); + expect(wrapper.children[0].children[1].sequenceNumber).toBe(2); + expect(wrapper.children[0].children[2].sequenceNumber).toBe(3); + expect(wrapper.children[0].children[3].sequenceNumber).toBe(8); + expect(wrapper.children[0].children[4].sequenceNumber).toBe(5); + }); + // Add another a tiddler to the end of the list + wiki.addTiddler({title: "YetAnotherTiddler", text: "Something"}); + // Refresh + refreshWidgetNode(widgetNode,wrapper,["YetAnotherTiddler"]); + describe("should refresh", function() { + // Test the refreshing + expect(wrapper.innerHTML).toBe("

\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwoYetAnotherTiddlerSomething

"); + // Test the sequence numbers in the DOM + expect(wrapper.sequenceNumber).toBe(0); + expect(wrapper.children[0].sequenceNumber).toBe(1); + expect(wrapper.children[0].children[0].sequenceNumber).toBe(7); + expect(wrapper.children[0].children[1].sequenceNumber).toBe(2); + expect(wrapper.children[0].children[2].sequenceNumber).toBe(3); + expect(wrapper.children[0].children[3].sequenceNumber).toBe(8); + expect(wrapper.children[0].children[4].sequenceNumber).toBe(5); + }); + }); + it("should deal with the list widget and external templates", function() { var wiki = new $tw.Wiki(); // Add some tiddlers From 06af26b1c814cf0db924218ddf80ccd019cab5ad Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 22:54:48 +0100 Subject: [PATCH 058/183] Give the edit-text widget support for the placeholder attribute --- core/modules/new_widgets/edit-text.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index d961f2d33..4e4bb6fde 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -41,6 +41,9 @@ EditTextWidget.prototype.render = function(parent,nextSibling) { 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 @@ -104,6 +107,7 @@ EditTextWidget.prototype.execute = function() { this.editIndex = this.getAttribute("index"); this.editDefault = this.getAttribute("default",""); this.editClass = this.getAttribute("class"); + this.editPlaceholder = this.getAttribute("placeholder"); // Get the editor element tag and type var tag,type; if(this.editField === "text") { From 7393a296dd9f40b028bd7066a436784655eae304 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 22:54:58 +0100 Subject: [PATCH 059/183] Add a fieldmangler widget --- core/modules/new_widgets/fieldmangler.js | 110 +++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 core/modules/new_widgets/fieldmangler.js diff --git a/core/modules/new_widgets/fieldmangler.js b/core/modules/new_widgets/fieldmangler.js new file mode 100644 index 000000000..040404bf5 --- /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("title",this.getVariable("tiddlerTitle")); + // 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; + +})(); From 8ee2695c81a1ac651202b80d634b60dd38aedd67 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 22:55:43 +0100 Subject: [PATCH 060/183] Fix up the tags editor The dropdown doesn't yet appear when the text input is focussed --- core/ui/TagsEditor.tid | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/ui/TagsEditor.tid b/core/ui/TagsEditor.tid index a215be504..97bbb3e24 100644 --- a/core/ui/TagsEditor.tid +++ b/core/ui/TagsEditor.tid @@ -1,16 +1,20 @@ title: $:/core/ui/TagsEditor -<$fieldmangler>
<$list filter="[is[current]tags[]sort[title]]" listview="pop" itemClass="tw-tag-editor-label"><$setstyle name="background-color" value={{!!color}} class="tw-tag-label"><$view field="title" format="text" /><$button message="tw-remove-tag" param={{!!title}} class="btn-invisible tw-remove-tag-button">× +\define tag-styles() +background-color:$(backgroundColor)$; +\end +<$fieldmangler>
<$list filter="[is[current]tags[]sort[title]]" listview="pop" itemClass="tw-tag-editor-label"><$setvariable name="backgroundColor" value={{!!color}}>> class="tw-tag-label"><$view field="title" format="text" /><$button message="tw-remove-tag" param={{!!title}} class="btn-invisible tw-remove-tag-button">×
-
Add a new tag: <$edit tiddler="$:/NewTagName" type="input" default="" placeholder="tag name" focusSet="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes"/> <$button popup="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="btn-invisible">{{$:/core/images/down-arrow}} <$button message="tw-add-tag" param={{$:/NewTagName}} set="$:/NewTagName" setTo="" class="">add
+
Add a new tag: <$edit-text title="$:/NewTagName" tag="input" default="" placeholder="tag name" focusSet="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="tw-edit-texteditor"/> <$button popup="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="btn-invisible">{{$:/core/images/down-arrow}} <$button message="tw-add-tag" param={{$:/NewTagName}} set="$:/NewTagName" setTo="" class="">add
-<$reveal state="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" type="nomatch" text="" default="" class="tw-tags-autocomplete"> - -<$linkcatcher set="$:/NewTagName" setTo="" message="tw-add-tag"><$list filter="[!is[shadow]tags[]search{$:/NewTagName}sort[title]]"><$link><$setstyle name="background-color" value={{!!color}} class="tw-tag-label"><$view field="title" format="text"/> +<$reveal state="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" type="nomatch" text="" default=""> +
+<$linkcatcher set="$:/NewTagName" setTo="" message="tw-add-tag"><$list filter="[!is[shadow]tags[]search{$:/NewTagName}sort[title]]"><$link><$setvariable name="backgroundColor" value={{!!color}}>> class="tw-tag-label"><$view field="title" format="text"/> +
From 40c6b6cb8f950393ff08f56df5460ee1989218e2 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 21 Oct 2013 23:10:20 +0100 Subject: [PATCH 061/183] Guts of the field editor Styling is a bit squiffy --- core/ui/FieldEditor.tid | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/ui/FieldEditor.tid b/core/ui/FieldEditor.tid index 03af4a349..b28bb3cfd 100644 --- a/core/ui/FieldEditor.tid +++ b/core/ui/FieldEditor.tid @@ -1,12 +1,14 @@ title: $:/core/ui/FieldEditor -\define renderfield(title) -
$title$: <$edit field="$title$" placeholder="field value"/> <$button message="tw-remove-field" param="$title$" class="btn-invisible">{{$:/core/images/delete-button}}
-\end -<$fieldmangler>
-<$list filter="[is[current]fields[]] -title -tags -text -creator -created -modified -modifier -[[draft.title]] -[[draft.of]]" macro="renderfield" itemClass="tw-fieldlist"/> +<$fieldmangler><$setvariable name="targetTiddler" value=<>>
+<$list filter="[is[current]fields[]] -title -tags -text -creator -created -modified -modifier -[[draft.title]] -[[draft.of]]"> + + + +
<><$edit-text title=<> field=<> placeholder="field value"/><$button message="tw-remove-field" param=<> class="btn-invisible">{{$:/core/images/delete-button}}
+ -
Add a new field: <$edit tiddler="$:/NewFieldName" type="input" default="" placeholder="field name"/> <$button message="tw-add-field" param={{$:/NewFieldName}} set="$:/NewFieldName" setTo="" class="">add
+
Add a new field: <$edit-text title="$:/NewFieldName" tag="input" default="" placeholder="field name" class="tw-edit-texteditor"/> <$button message="tw-add-field" param={{$:/NewFieldName}} set="$:/NewFieldName" setTo="" class="">add
From 6f2dbd0978b89570852b76a2de1aff20c60b2a62 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 22 Oct 2013 18:14:16 +0100 Subject: [PATCH 062/183] Refactor name of "findNextSiblingDomNode()" method for consistency --- core/modules/new_widgets/list.js | 6 +++--- core/modules/new_widgets/widget.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/modules/new_widgets/list.js b/core/modules/new_widgets/list.js index 93b8005e3..58d9ae6fc 100755 --- a/core/modules/new_widgets/list.js +++ b/core/modules/new_widgets/list.js @@ -142,7 +142,7 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) { return this.refreshChildren(changedTiddlers); } else { // Replace the previous content with the empty message - var nextSibling = this.findNextSibling(); + var nextSibling = this.findNextSiblingDomNode(); this.removeChildDomNodes(); this.makeChildWidgets(this.getEmptyMessage()); this.renderChildren(this.parentDomNode,nextSibling); @@ -200,9 +200,9 @@ Insert a new list item at the specified index */ ListWidget.prototype.insertListItem = function(index,title) { var newItem = this.makeChildWidget(this.makeItemTemplate(title)); - newItem.parentDomNode = this.parentDomNode; // Hack to enable findNextSibling() to work + newItem.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work this.children.splice(index,0,newItem); - var nextSibling = newItem.findNextSibling(); + var nextSibling = newItem.findNextSiblingDomNode(); newItem.render(this.parentDomNode,nextSibling); return true; }; diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index 93ed058ca..eb8b90cf9 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -356,7 +356,7 @@ Widget.prototype.refresh = function(changedTiddlers) { Rebuild a previously rendered widget */ Widget.prototype.refreshSelf = function() { - var nextSibling = this.findNextSibling(); + var nextSibling = this.findNextSiblingDomNode(); this.removeChildDomNodes(); this.render(this.parentDomNode,nextSibling); }; @@ -376,7 +376,7 @@ Widget.prototype.refreshChildren = function(changedTiddlers) { /* 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.findNextSibling = function(startIndex) { +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); @@ -394,7 +394,7 @@ if(index === -1) { var grandParent = parent.parentWidget; if(grandParent && parent.parentDomNode === this.parentDomNode) { index = grandParent.children.indexOf(parent); - return parent.findNextSibling(index); + return parent.findNextSiblingDomNode(index); } return null; }; From dfdb34a5cc5fb4623a472496ab6bec753ea94a4d Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 17:04:10 +0100 Subject: [PATCH 063/183] Better styling for the fields editor --- core/ui/FieldEditor.tid | 2 +- themes/tiddlywiki/snowwhite/base.tid | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/core/ui/FieldEditor.tid b/core/ui/FieldEditor.tid index b28bb3cfd..4e448e2c8 100644 --- a/core/ui/FieldEditor.tid +++ b/core/ui/FieldEditor.tid @@ -1,7 +1,7 @@ title: $:/core/ui/FieldEditor <$fieldmangler><$setvariable name="targetTiddler" value=<>>
-<$list filter="[is[current]fields[]] -title -tags -text -creator -created -modified -modifier -[[draft.title]] -[[draft.of]]"> +
<><$edit-text title=<> field=<> placeholder="field value"/><$button message="tw-remove-field" param=<> class="btn-invisible">{{$:/core/images/delete-button}}
<$list filter="[is[current]fields[]] -title -tags -text -creator -created -modified -modifier -[[draft.title]] -[[draft.of]]"> diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index c31d336e2..83e89b240 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -630,28 +630,33 @@ canvas.tw-edit-bitmapeditor { width: 48%; } -.tw-edit-fields > .tw-list-frame > div:nth-child(odd) { +.tw-edit-fields { + width: 100%; +} + + +.tw-edit-fields table, .tw-edit-fields tr, .tw-edit-fields td { + border: none; + padding: 4px; +} + +.tw-edit-fields > tbody > .tw-edit-field:nth-child(odd) { background-color: #f0f4f0; } -.tw-edit-fields > .tw-list-frame > div:nth-child(even) { +.tw-edit-fields > tbody > .tw-edit-field:nth-child(even) { background-color: #e0e8e0; } .tw-edit-field-name { - display: inline-block; - width: 15%; text-align: right; } -.tw-edit-field-value { - display: inline-block; - width: 75%; +.tw-edit-field-value input { + width: 100%; } .tw-edit-field-remove { - display: inline-block; - width: 5%; } .tw-edit-field-remove svg { From f2ac04943fbf8343dfafcbb4a110d71fec01dd47 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 17:41:12 +0100 Subject: [PATCH 064/183] Fixed tag autocomplete popup on focus --- core/modules/new_widgets/edit-text.js | 34 ++++++++++++++++++++++++--- core/modules/utils/dom/popup.js | 2 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index 4e4bb6fde..c4bd373f3 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -54,9 +54,10 @@ EditTextWidget.prototype.render = function(parent,nextSibling) { domNode.setAttribute("value",editInfo.value) } // Add an input event handler - domNode.addEventListener("input",function (event) { - return self.handleInputEvent(event); - },false); + $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); @@ -108,6 +109,15 @@ EditTextWidget.prototype.execute = function() { this.editDefault = this.getAttribute("default",""); this.editClass = this.getAttribute("class"); this.editPlaceholder = this.getAttribute("placeholder"); + this.editFocusSet = this.getAttribute("focusSet"); + this.qualifyTiddlerTitles = this.getAttribute("qualifyTiddlerTitles");; + // Qualify tiddler titles if required + if(this.qualifyTiddlerTitles) { + var qualifier = this.getStateQualifier(); + if(this.editFocusSet) { + this.editFocusSet = this.editFocusSet + "-" + qualifier; + } + } // Get the editor element tag and type var tag,type; if(this.editField === "text") { @@ -186,6 +196,24 @@ EditTextWidget.prototype.handleInputEvent = function(event) { return true; }; +EditTextWidget.prototype.handleFocusEvent = function(event) { + if(this.editFocusSet) { +console.log("Focus",{ + domNode: this.domNodes[0], + title: this.editFocusSet, + wiki: this.wiki, + force: true + }) + $tw.popup.triggerPopup({ + domNode: this.domNodes[0], + title: this.editFocusSet, + wiki: this.wiki, + force: true + }); + } + return true; +}; + EditTextWidget.prototype.saveChanges = function() { var text = this.domNodes[0].value if(this.editField) { diff --git a/core/modules/utils/dom/popup.js b/core/modules/utils/dom/popup.js index 2626fff0f..f79e76120 100644 --- a/core/modules/utils/dom/popup.js +++ b/core/modules/utils/dom/popup.js @@ -30,7 +30,7 @@ Popup.prototype.show = function(options) { }; Popup.prototype.handleEvent = function(event) { - if(event.type === "click" && !$tw.utils.domContains(this.anchorDomNode,event.target)) { + if(event.type === "click" && this.anchorDomNode !== event.target && !$tw.utils.domContains(this.anchorDomNode,event.target)) { this.cancel(); } }; From 67490cbb74af6113c17584e75a5e812675fae9ab Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 17:50:51 +0100 Subject: [PATCH 065/183] Whoops console.log.alypse --- core/modules/new_widgets/edit-text.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index c4bd373f3..039f71f91 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -198,12 +198,6 @@ EditTextWidget.prototype.handleInputEvent = function(event) { EditTextWidget.prototype.handleFocusEvent = function(event) { if(this.editFocusSet) { -console.log("Focus",{ - domNode: this.domNodes[0], - title: this.editFocusSet, - wiki: this.wiki, - force: true - }) $tw.popup.triggerPopup({ domNode: this.domNodes[0], title: this.editFocusSet, From 4e713baeeeecfe1423910ac096168a973fbb6166 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 19:38:36 +0100 Subject: [PATCH 066/183] Add checkbox widget and fix up task management example --- core/modules/new_widgets/checkbox.js | 113 ++++++++++++++++++ .../samples/Tasks/TaskManagementExample.tid | 4 +- 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 core/modules/new_widgets/checkbox.js diff --git a/core/modules/new_widgets/checkbox.js b/core/modules/new_widgets/checkbox.js new file mode 100644 index 000000000..8cf05b9bf --- /dev/null +++ b/core/modules/new_widgets/checkbox.js @@ -0,0 +1,113 @@ +/*\ +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})); + } +}; + +/* +Compute the internal state of the widget +*/ +CheckboxWidget.prototype.execute = function() { + // Get the parameters from the attributes + this.checkboxTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.checkboxTag = this.getAttribute("tag"); + this.checkboxClass = this.getAttribute("class"); + // 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.title || changedAttributes.tag || changedAttributes["class"] || changedTiddlers[this.checkboxTitle]) { + this.refreshSelf(); + return true; + } + return this.refreshChildren(changedTiddlers); +}; + +/* +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/editions/tw5.com/tiddlers/samples/Tasks/TaskManagementExample.tid b/editions/tw5.com/tiddlers/samples/Tasks/TaskManagementExample.tid index e28c7c074..4c5a649cc 100644 --- a/editions/tw5.com/tiddlers/samples/Tasks/TaskManagementExample.tid +++ b/editions/tw5.com/tiddlers/samples/Tasks/TaskManagementExample.tid @@ -10,7 +10,7 @@ TiddlyWiki5 can be used as a simple task management system without further custo <$list filter="[!has[draft.of]tag[task]!tag[done]sort[created]]"> -<$checkbox tag="done"> <$view field="title" format="link"/> +<$checkbox tag="done"> <$link to={{!!title}}><$view field="title"/> @@ -18,6 +18,6 @@ TiddlyWiki5 can be used as a simple task management system without further custo <$list filter="[!has[draft.of]tag[task]tag[done]sort[created]]"> -<$checkbox tag="done"> ~~<$view field="title" format="link"/>~~ +<$checkbox tag="done"> ~~<$link to={{!!title}}><$view field="title"/>~~ From 54bed81ab9ace1df949c8c6b6b7756267d45c578 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 19:46:31 +0100 Subject: [PATCH 067/183] Missed off some of the refresh logic --- core/modules/new_widgets/checkbox.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/modules/new_widgets/checkbox.js b/core/modules/new_widgets/checkbox.js index 8cf05b9bf..d4f562daf 100644 --- a/core/modules/new_widgets/checkbox.js +++ b/core/modules/new_widgets/checkbox.js @@ -91,11 +91,15 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ CheckboxWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.title || changedAttributes.tag || changedAttributes["class"] || changedTiddlers[this.checkboxTitle]) { + if(changedAttributes.title || changedAttributes.tag || changedAttributes["class"]) { this.refreshSelf(); return true; + } else { + if(changedTiddlers[this.checkboxTitle]) { + this.inputDomNode.checked = this.getValue(); + } + return this.refreshChildren(changedTiddlers); } - return this.refreshChildren(changedTiddlers); }; /* From 3241c2b76a609a962e39c2319b07ef89511d62b9 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 20:24:45 +0100 Subject: [PATCH 068/183] Added password widget --- core/modules/new_widgets/password.js | 92 ++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 core/modules/new_widgets/password.js diff --git a/core/modules/new_widgets/password.js b/core/modules/new_widgets/password.js new file mode 100644 index 000000000..15434d916 --- /dev/null +++ b/core/modules/new_widgets/password.js @@ -0,0 +1,92 @@ +/*\ +title: $:/core/modules/new_widgets/password.js +type: application/javascript +module-type: new_widget + +Password widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var PasswordWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +PasswordWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +PasswordWidget.prototype.render = function(parent,nextSibling) { + // Save the parent dom node + this.parentDomNode = parent; + // Compute our attributes + this.computeAttributes(); + // Execute our logic + this.execute(); + // Get the current password + var password = $tw.browser ? $tw.utils.getPassword(this.passwordName) : ""; + // Create our element + var domNode = this.document.createElement("input"); + domNode.setAttribute("type","password"); + domNode.setAttribute("value",password); + // Add a click event handler + $tw.utils.addEventListeners(domNode,[ + {name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"} + ]); + // Insert the label into the DOM and render any children + parent.insertBefore(domNode,nextSibling); + this.renderChildren(domNode,null); + this.domNodes.push(domNode); +}; + +PasswordWidget.prototype.handleChangeEvent = function(event) { + var password = this.domNodes[0].value; + return $tw.utils.savePassword(this.passwordName,password); +}; + +/* +Compute the internal state of the widget +*/ +PasswordWidget.prototype.execute = function() { + // Get the parameters from the attributes + this.passwordName = this.getAttribute("name",""); + // 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 +*/ +PasswordWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.name) { + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +PasswordWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +exports.password = PasswordWidget; + +})(); From 33505105161fe7d6d9f572bfded851291f07c5af Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 23:11:41 +0100 Subject: [PATCH 069/183] Add new modules and moduletypes filter operators --- core/modules/filters/modules.js | 39 +++++++++++++++++++++++++++++ core/modules/widgets/moduletypes.js | 27 ++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 core/modules/filters/modules.js create mode 100644 core/modules/widgets/moduletypes.js 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/widgets/moduletypes.js b/core/modules/widgets/moduletypes.js new file mode 100644 index 000000000..67321caea --- /dev/null +++ b/core/modules/widgets/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; +}; + +})(); From c95ce5ff4c1f9fdbed99b3cf48c4bce3702485a0 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 23:12:07 +0100 Subject: [PATCH 070/183] Fix up the $:/ConfigInfo tiddler --- core/wiki/ConfigInfo.tid | 3 +-- core/wiki/allfields.tid | 5 +++-- core/wiki/modules.tid | 13 +++++++++++++ themes/tiddlywiki/snowwhite/base.tid | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 core/wiki/modules.tid diff --git a/core/wiki/ConfigInfo.tid b/core/wiki/ConfigInfo.tid index 249339ed4..db85ba9f9 100644 --- a/core/wiki/ConfigInfo.tid +++ b/core/wiki/ConfigInfo.tid @@ -12,5 +12,4 @@ This is the full set of TiddlerFields in use in this wiki (including system tidd These are the currently loaded tiddler modules linked to their source tiddlers. Any italicised modules lack a source tiddler, typically because they were setup during the boot process. -<$info type="modules"/> - +{{$:/snippets/modules}} diff --git a/core/wiki/allfields.tid b/core/wiki/allfields.tid index 25ea4177c..515cbf42e 100644 --- a/core/wiki/allfields.tid +++ b/core/wiki/allfields.tid @@ -1,6 +1,7 @@ title: $:/snippets/allfields \define renderfield(title) -
''$title$'': //{{$:/docs/fields/$title$}}//
+
\end -<$list filter="[fields[]sort[title]]" macro="renderfield"/> +
<>:<$edit-text title=<> field=<> placeholder="field value"/><$button message="tw-remove-field" param=<> class="btn-invisible">{{$:/core/images/delete-button}}
''$title$''://{{$:/docs/fields/$title$}}//
<$list filter="[fields[]sort[title]]"><$macrocall $name="renderfield" title=<>/> +
diff --git a/core/wiki/modules.tid b/core/wiki/modules.tid new file mode 100644 index 000000000..1f6bc69de --- /dev/null +++ b/core/wiki/modules.tid @@ -0,0 +1,13 @@ +title: $:/snippets/modules + +\define describeModuleType(type) +{{$:/docs/moduletypes/$type$}} +\end +<$list filter="[moduletypes[]]"> +!! <> +<$macrocall $name="describeModuleType" type=<>/> +
    <$list filter="[is[current]modules[]]">
  • <$link><> +
  • + +
+ diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 83e89b240..5c3fd9a70 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -425,7 +425,7 @@ a.tw-tiddlylink-external { <> } -.tw-view-field-table { +.tw-view-fields-table { width: 100%; } From d3df2c5860541dd85034a88a915c9cbcc149370b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 23 Oct 2013 23:12:19 +0100 Subject: [PATCH 071/183] Docs updates --- .../tw5.com/tiddlers/widgets/GridWidget.tid | 6 ++- .../tw5.com/tiddlers/widgets/LinkWidget.tid | 45 +++---------------- .../tw5.com/tiddlers/widgets/VideoWidget.tid | 4 ++ 3 files changed, 15 insertions(+), 40 deletions(-) diff --git a/editions/tw5.com/tiddlers/widgets/GridWidget.tid b/editions/tw5.com/tiddlers/widgets/GridWidget.tid index 956947584..bdd15f822 100644 --- a/editions/tw5.com/tiddlers/widgets/GridWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/GridWidget.tid @@ -1,11 +1,12 @@ title: GridWidget tags: widget -The grid widget assembles tiddlers into a tabular grid based on their titles. For example: +The video widget is not yet (re)implemented. ``` +The grid widget assembles tiddlers into a tabular grid based on their titles. For example: + <$grid prefix="GridDemo" rows=3 cols=3/> -``` In this case, the following tiddlers will be rendered: @@ -16,3 +17,4 @@ In this case, the following tiddlers will be rendered: The result is: <$grid prefix="GridDemo" rows=3 cols=3/> +``` diff --git a/editions/tw5.com/tiddlers/widgets/LinkWidget.tid b/editions/tw5.com/tiddlers/widgets/LinkWidget.tid index 3640a71ce..9f167e493 100644 --- a/editions/tw5.com/tiddlers/widgets/LinkWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/LinkWidget.tid @@ -1,47 +1,14 @@ title: LinkWidget tags: widget -The `link` widget generates links to tiddlers and external URIs. Optionally, hovering over a link can trigger the display of another tiddler as a popup. +The `link` widget generates links to tiddlers. -! Attributes +! Content and Attributes -* `to` - link target can be a URL -* `hover` - the title of a tiddler containing the popup state to set when hovering over the link -* `qualifyHoverTitles` - if this attribute is present then the title of the hover state tiddler is qualified as described in the PopupMechanism +|!Attribute |!Description | +|to |The title of the target tiddler for the link | -The `to` attribute is interpreted as an external link if it matches this regular expression: - -``` -/(?:file|http|https|mailto|ftp|irc|news|data):[^\s'"]+(?:\/|\b)/i -``` - -! Examples - -The `hover` attribute is part of the PopupMechanism. - -For example: - -``` -`$:/MyHover` is {{$:/MyHover}} - -<$link to="HelloThere" hover="$:/MyHover">Hover me to see ~HelloThere! -<$reveal state="$:/MyHover" type="popup"> -
-{{HelloThere}} -
- -``` - -Renders as: - -`$:/MyHover` is {{$:/MyHover}} - -<$link to="HelloThere" hover="$:/MyHover">Hover me to see ~HelloThere! -<$reveal state="$:/MyHover" type="popup"> -
-{{HelloThere}} -
- +The content of the link widget is rendered within the `` tag. ! CSS Classes @@ -70,3 +37,5 @@ Link targets default to the URL encoded title of the tiddler. The `href` can be ``` \define tw-wikilink-template() http://tiddlywiki.com/#$uri_encoded$ ``` + +Note that in the browser the `` element generated by the link widget has a JavaScript event handler that navigates directly to the target tiddler, ignoring the `href` attribute. diff --git a/editions/tw5.com/tiddlers/widgets/VideoWidget.tid b/editions/tw5.com/tiddlers/widgets/VideoWidget.tid index aab5eb1e3..17a21e702 100644 --- a/editions/tw5.com/tiddlers/widgets/VideoWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/VideoWidget.tid @@ -1,6 +1,9 @@ title: VideoWidget tags: widget +The video widget is not yet (re)implemented. + +``` Here is a collection of embedded videos. Try opening and closing tiddlers while the videos are playing; they should not be affected. <$video src="32001208" type="vimeo" /> @@ -8,3 +11,4 @@ Here is a collection of embedded videos. Try opening and closing tiddlers while <$video src="wvWHnK2FiCk" type="youtube" /> <$video src="santa_claus_conquers_the_martians_ipod" type="archiveorg" /> +``` \ No newline at end of file From 632970cd86e66c5b210523cd677de8012f1de0c8 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 24 Oct 2013 11:54:54 +0100 Subject: [PATCH 072/183] Get external links working again We no longer use the `<$link>` widget for external links. Instead the parser generates `` elements. This makes things simpler, but does mean that the `target=_blank` behaviour is baked into the parser. Probably we should introduce a new `<$extlink>` widget that generates an `` element with a configurable `target` attribute --- contributing.md | 12 +++--- .../parsers/wikiparser/rules/extlink.js | 7 ++-- .../parsers/wikiparser/rules/prettylink.js | 39 ++++++++++++++----- readme.md | 10 ++--- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/contributing.md b/contributing.md index 1632759db..d7f48284a 100644 --- a/contributing.md +++ b/contributing.md @@ -2,7 +2,7 @@ Contributing to TiddlyWiki5

-TiddlyWiki5 welcomes contributions to its code and documentation via +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:

    @@ -14,9 +14,9 @@ Expected behaviour
  • Context (OS, browser etc.)

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

Pull Requests

Like other @@ -26,9 +26,9 @@ ContributorLicenseAgreement from individual contributors before contribution UnaMesa Association (the legal entity that owns TiddlyWiki on behalf of the community).

This is a first pass at a CLA for @@ -49,7 +49,7 @@ Go to your github repo and create a pull request.

Thank you!

Attribution

-The CLA documents used for this project where created using +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"

diff --git a/core/modules/parsers/wikiparser/rules/extlink.js b/core/modules/parsers/wikiparser/rules/extlink.js index 0203434f0..42c247d22 100644 --- a/core/modules/parsers/wikiparser/rules/extlink.js +++ b/core/modules/parsers/wikiparser/rules/extlink.js @@ -26,7 +26,7 @@ exports.types = {inline: true}; exports.init = function(parser) { this.parser = parser; // Regexp to match - this.matchRegExp = /~?(?:file|http|https|mailto|ftp|irc|news|data):[^\s'"<>]+(?:\/|\b)/mg; + this.matchRegExp = /~?(?:file|http|https|mailto|ftp|irc|news|data|skype):[^\s'"<>]+(?:\/|\b)/mg; }; exports.parse = function() { @@ -38,9 +38,10 @@ exports.parse = function() { } else { return [{ type: "element", - tag: "$link", + tag: "a", attributes: { - to: {type: "string", value: this.match[0]} + href: {type: "string", value: this.match[0]}, + target: {type: "string", value: "_blank"} }, children: [{ type: "text", text: this.match[0] diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index 2eb5eac98..0dffbf0f3 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -27,22 +27,41 @@ exports.init = function(parser) { this.matchRegExp = /\[\[(.*?)(?:\|(.*?))?\]\]/mg; }; +var isLinkExternal = function(to) { + var externalRegExp = /(?:file|http|https|mailto|ftp|irc|news|data|skype):[^\s'"]+(?:\/|\b)/i; + return externalRegExp.test(to); +}; + exports.parse = function() { // Move past the match this.parser.pos = this.matchRegExp.lastIndex; // Process the link var text = this.match[1], link = this.match[2] || text; - return [{ - type: "element", - tag: "$link", - attributes: { - to: {type: "string", value: link} - }, - children: [{ - type: "text", text: text - }] - }]; + if(isLinkExternal(link)) { + return [{ + type: "element", + tag: "a", + attributes: { + href: {type: "string", value: link}, + target: {type: "string", value: "_blank"} + }, + children: [{ + type: "text", text: text + }] + }]; + } else { + return [{ + type: "element", + tag: "$link", + attributes: { + to: {type: "string", value: link} + }, + children: [{ + type: "text", text: text + }] + }]; + } }; })(); diff --git a/readme.md b/readme.md index cfe4bb609..98cc0b8a0 100644 --- a/readme.md +++ b/readme.md @@ -10,13 +10,13 @@ node.js application.

TiddlyWiki5 is currently in alpha, meaning it is working but incomplete. It is a great time to get involved and support its future development:

@@ -33,7 +33,7 @@ TiddlyWikiFiles. For example, the following command loads the tiddlers from TiddlyWiki HTML file and then saves one of them in HTML:

 node tiddlywiki.js --verbose --load mywiki.html --rendertiddler ReadMe ./readme.html

In order to use -TiddlyWiki5 on the command line you must first install node.js from +TiddlyWiki5 on the command line you must first install node.js from http://nodejs.org/

Usage

Running From 56b2c255884139d8d144953923211492dbcaa705 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 24 Oct 2013 12:00:32 +0100 Subject: [PATCH 073/183] Whoops file in the wrong place --- core/modules/{widgets => filters}/moduletypes.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/modules/{widgets => filters}/moduletypes.js (100%) diff --git a/core/modules/widgets/moduletypes.js b/core/modules/filters/moduletypes.js similarity index 100% rename from core/modules/widgets/moduletypes.js rename to core/modules/filters/moduletypes.js From 3251aa191f7c245f8d67e0ba29e4cb18f047da68 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 24 Oct 2013 13:10:32 +0100 Subject: [PATCH 074/183] Fix up saving encrypted wikis --- core/modules/new_widgets/encrypt.js | 85 +++++++++++++++++++++++++++++ nbld.sh | 9 +++ 2 files changed, 94 insertions(+) create mode 100644 core/modules/new_widgets/encrypt.js 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/nbld.sh b/nbld.sh index 52082a18f..2ee7e4e08 100755 --- a/nbld.sh +++ b/nbld.sh @@ -28,6 +28,15 @@ node ./tiddlywiki.js \ --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" + +node ./tiddlywiki.js \ + ./editions/tw5.com \ + --verbose \ + --password password \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \ + || exit 1 + # Run tests ./test.sh From a5f5f1bd9c284c8c096768dba282ad2de61c3553 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 24 Oct 2013 20:45:53 +0100 Subject: [PATCH 075/183] Fixed up the D3 demo plugin --- nbld.sh | 8 ++ plugins/tiddlywiki/d3/barwidget.js | 169 +++++++++++++----------- plugins/tiddlywiki/d3/cloudwidget.js | 184 +++++++++++++++------------ 3 files changed, 207 insertions(+), 154 deletions(-) diff --git a/nbld.sh b/nbld.sh index 2ee7e4e08..53869018c 100755 --- a/nbld.sh +++ b/nbld.sh @@ -37,6 +37,14 @@ node ./tiddlywiki.js \ --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \ || exit 1 +# Fifth, d3demo.html: wiki to demo d3 plugin + +node ./tiddlywiki.js \ + ./editions/d3demo \ + --verbose \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/d3demo.html text/plain \ + || exit 1 + # Run tests ./test.sh diff --git a/plugins/tiddlywiki/d3/barwidget.js b/plugins/tiddlywiki/d3/barwidget.js index b2aff30ef..bb24e6205 100644 --- a/plugins/tiddlywiki/d3/barwidget.js +++ b/plugins/tiddlywiki/d3/barwidget.js @@ -1,7 +1,7 @@ /*\ title: $:/plugins/tiddlywiki/d3/barwidget.js type: application/javascript -module-type: widget +module-type: new_widget A widget for displaying stacked or grouped bar charts. Derived from http://bl.ocks.org/mbostock/3943967 @@ -12,104 +12,94 @@ A widget for displaying stacked or grouped bar charts. Derived from http://bl.oc /*global $tw: false */ "use strict"; -var d3 = require("$:/plugins/tiddlywiki/d3/d3.js").d3; +var Widget = require("$:/core/modules/new_widgets/widget.js").widget, + d3 = require("$:/plugins/tiddlywiki/d3/d3.js").d3; -var BarWidget = function(renderer) { - // Save state - this.renderer = renderer; - // Generate child nodes - this.generate(); +var BarWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); }; -BarWidget.prototype.generate = function() { - // Get the parameters - this.data = this.renderer.getAttribute("data"); - this.grouped = this.renderer.getAttribute("grouped","no"); - // Set the return element - this.tag = "div"; - this.attributes = { - "class": "tw-barwidget" - }; -}; +/* +Inherit from the base widget class +*/ +BarWidget.prototype = new Widget(); -BarWidget.prototype.postRenderInDom = function() { - this.updateChart = this.createChart(); -}; - -BarWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { - // Reexecute the widget if the data reference attributes have changed - if(changedAttributes.data || changedTiddlers[this.data]) { - // Regenerate and rerender the widget and replace the existing DOM node - this.generate(); - var oldDomNode = this.renderer.domNode, - newDomNode = this.renderer.renderInDom(); - oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); - } else if(changedAttributes.grouped) { - // Update the chart if the grouping setting has changed - this.grouped = this.renderer.getAttribute("grouped","no"); - if(this.updateChart) { - this.updateChart(); - } +/* +Render this widget into the DOM +*/ +BarWidget.prototype.render = function(parent,nextSibling) { + // Save the parent dom node + this.parentDomNode = parent; + // Compute our attributes + this.computeAttributes(); + // Execute our logic + this.execute(); + // Create the chart + var chart = this.createChart(parent,nextSibling); + this.updateChart = chart.updateChart; + if(this.updateChart) { + this.updateChart(); } + // Insert the chart into the DOM and render any children + parent.insertBefore(chart.domNode,nextSibling); + this.domNodes.push(chart.domNode); }; - -BarWidget.prototype.createChart = function() { - - var n,m,stack,layers; - +BarWidget.prototype.createChart = function(parent,nextSibling) { // Get the data we're plotting - var data = this.renderer.renderTree.wiki.getTiddlerData(this.data); + var data = this.wiki.getTiddlerData(this.barData), + n,m,stack,layers; if(data) { n = data.layers; m = data.samples; layers = data.data; - } else { + } else { // Use randomly generated data if we don't have any n = 4; // number of layers m = 58; // number of samples per layer stack = d3.layout.stack(); layers = stack(d3.range(n).map(function() { return bumpLayer(m, .1); })); } - + // Calculate the maximum data values var yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }), yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); }); - + // Calculate margins and width and height var margin = {top: 40, right: 10, bottom: 20, left: 10}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; - + // x-scale var x = d3.scale.ordinal() .domain(d3.range(m)) .rangeRoundBands([0, width], .08); - + // y-scale var y = d3.scale.linear() .domain([0, yStackMax]) .range([height, 0]); - + // Array of colour values var color = d3.scale.linear() .domain([0, n - 1]) .range(["#aad", "#556"]); - + // x-axis var xAxis = d3.svg.axis() .scale(x) .tickSize(0) .tickPadding(6) .orient("bottom"); - - var svg = d3.select(this.renderer.domNode).append("svg") + // Create SVG element + var svgElement = d3.select(parent).insert("svg",nextSibling) .attr("viewBox", "0 0 960 500") .attr("preserveAspectRatio", "xMinYMin meet") .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") + .attr("height", height + margin.top + margin.bottom); + // Create main group + var mainGroup = svgElement.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - var layer = svg.selectAll(".layer") + // Create the layers + var layer = mainGroup.selectAll(".layer") .data(layers) .enter().append("g") .attr("class", "layer") .style("fill", function(d, i) { return color(i); }); - + // Create the rectangles in each layer var rect = layer.selectAll("rect") .data(function(d) { return d; }) .enter().append("rect") @@ -117,33 +107,31 @@ BarWidget.prototype.createChart = function() { .attr("y", height) .attr("width", x.rangeBand()) .attr("height", 0); - + // Transition the rectangles to their final height rect.transition() .delay(function(d, i) { return i * 10; }) .attr("y", function(d) { return y(d.y0 + d.y); }) .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); }); - - svg.append("g") + // Add to the DOM + mainGroup.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); - - var self = this, - updateChart = function() { - if (self.grouped !== "no") { - transitionGrouped(); - } else { - transitionStacked(); - } + var self = this; + // Return the svg node + return { + domNode: svgElement[0][0], + updateChart: function() { + if (self.barGrouped !== "no") { + transitionGrouped(); + } else { + transitionStacked(); + } + } }; - // Update the chart according to the grouped setting - updateChart(); - // Return the update function - return updateChart; function transitionGrouped() { y.domain([0, yGroupMax]); - rect.transition() .duration(500) .delay(function(d, i) { return i * 10; }) @@ -156,7 +144,6 @@ BarWidget.prototype.createChart = function() { function transitionStacked() { y.domain([0, yStackMax]); - rect.transition() .duration(500) .delay(function(d, i) { return i * 10; }) @@ -169,7 +156,6 @@ BarWidget.prototype.createChart = function() { // Inspired by Lee Byron's test data generator. function bumpLayer(n, o) { - function bump(a) { var x = 1 / (.1 + Math.random()), y = 2 * Math.random() - .5, @@ -179,13 +165,48 @@ BarWidget.prototype.createChart = function() { a[i] += x * Math.exp(-w * w); } } - var a = [], i; for (i = 0; i < n; ++i) a[i] = o + o * Math.random(); for (i = 0; i < 5; ++i) bump(a); return a.map(function(d, i) { return {x: i, y: Math.max(0, d)}; }); } +}; +/* +Compute the internal state of the widget +*/ +BarWidget.prototype.execute = function() { + // Get the parameters from the attributes + this.barData = this.getAttribute("data"); + this.barGrouped = this.getAttribute("grouped","no"); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +BarWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.data || changedTiddlers[this.barData]) { + this.refreshSelf(); + return true; + } else if(changedAttributes.grouped) { + this.execute(); + if(this.updateChart) { + this.updateChart(); + } + return true; + } + return false; +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +BarWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; }; exports.d3bar = BarWidget; diff --git a/plugins/tiddlywiki/d3/cloudwidget.js b/plugins/tiddlywiki/d3/cloudwidget.js index fa2160754..e2bf1d7b6 100644 --- a/plugins/tiddlywiki/d3/cloudwidget.js +++ b/plugins/tiddlywiki/d3/cloudwidget.js @@ -1,7 +1,7 @@ /*\ title: $:/plugins/tiddlywiki/d3/cloudwidget.js type: application/javascript -module-type: widget +module-type: new_widget A widget for displaying word clouds. Derived from https://github.com/jasondavies/d3-cloud @@ -12,7 +12,8 @@ A widget for displaying word clouds. Derived from https://github.com/jasondavies /*global $tw: false */ "use strict"; -var d3 = require("$:/plugins/tiddlywiki/d3/d3.js").d3; +var Widget = require("$:/core/modules/new_widgets/widget.js").widget, + d3 = require("$:/plugins/tiddlywiki/d3/d3.js").d3; if($tw.browser) { // Frightful hack to give the cloud plugin the global d3 variable it needs @@ -20,98 +21,121 @@ if($tw.browser) { d3.layout.cloud = require("$:/plugins/tiddlywiki/d3/d3.layout.cloud.js").cloud; } -var CloudWidget = function(renderer) { - // Save state - this.renderer = renderer; - // Generate child nodes - this.generate(); +var CloudWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); }; -CloudWidget.prototype.generate = function() { - // Get the parameters - this.data = this.renderer.getAttribute("data"); - this.spiral = this.renderer.getAttribute("spiral","archimedean"); - // Set the return element - this.tag = "div"; - this.attributes = { - "class": "tw-cloudwidget" +/* +Inherit from the base widget class +*/ +CloudWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +CloudWidget.prototype.render = function(parent,nextSibling) { + // Save the parent dom node + this.parentDomNode = parent; + // Compute our attributes + this.computeAttributes(); + // Execute our logic + this.execute(); + // Create the chart + var chart = this.createChart(parent,nextSibling); + this.updateChart = chart.updateChart; + if(this.updateChart) { + this.updateChart(); + } + // Insert the chart into the DOM and render any children + parent.insertBefore(chart.domNode,nextSibling); + this.domNodes.push(chart.domNode); +}; + +CloudWidget.prototype.createChart = function(parent,nextSibling) { + var self = this, + fill = d3.scale.category20(), + data = this.wiki.getTiddlerData(this.cloudData); + // Use dummy data if none provided + if(!data) { + data = "This word cloud does not have any data in it".split(" ").map(function(d) { + return {text: d, size: 10 + Math.random() * 90}; + }); + } + // Create the svg element + var svgElement = d3.select(parent).insert("svg",nextSibling) + .attr("width", 600) + .attr("height", 400); + // Create the main group + var mainGroup = svgElement + .append("g") + .attr("transform", "translate(300,200)"); + // Create the layout + var layout = d3.layout.cloud().size([600, 400]) + .words(data) + .padding(5) + .rotate(function() { return ~~(Math.random() * 5) * 30 - 60; }) + .font("Impact") + .fontSize(function(d) { return d.size*2; }) + .on("end", draw) + .start(); + // Function to draw all the words + function draw(words) { + mainGroup.selectAll("text") + .data(words) + .enter().append("text") + .style("font-size", function(d) { return d.size + "px"; }) + .style("font-family", "Impact") + .style("fill", function(d, i) { return fill(i); }) + .attr("text-anchor", "middle") + .attr("transform", function(d) { + return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; + }) + .text(function(d) { return d.text; }); + } + function updateChart() { + layout.spiral(self.spiral); + } + return { + domNode: svgElement[0][0], + updateChart: updateChart }; }; -CloudWidget.prototype.postRenderInDom = function() { - this.updateChart = this.createChart(); +/* +Compute the internal state of the widget +*/ +CloudWidget.prototype.execute = function() { + // Get the parameters from the attributes + this.cloudData = this.getAttribute("data"); + this.cloudSpiral = this.getAttribute("spiral","archimedean"); }; -CloudWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { - // Reexecute the widget if the data reference attributes have changed - if(changedAttributes.data || changedTiddlers[this.data]) { - // Regenerate and rerender the widget and replace the existing DOM node - this.generate(); - var oldDomNode = this.renderer.domNode, - newDomNode = this.renderer.renderInDom(); - oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +CloudWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.data || changedTiddlers[this.cloudData]) { + this.refreshSelf(); + return true; } else if(changedAttributes.spiral) { - // Update the chart if the spiral setting has changed - this.spiral = this.renderer.getAttribute("spiral","archimedean"); + this.execute(); if(this.updateChart) { this.updateChart(); } + return true; } + return false; }; -CloudWidget.prototype.createChart = function() { - - var self = this; - - var domNode = this.renderer.domNode; - - var fill = d3.scale.category20(); - - - var data = this.renderer.renderTree.wiki.getTiddlerData(this.data); - - if(!data) { - // Use dummy data if none provided - data = "This word cloud does not have any data in it".split(" ").map(function(d) { - return {text: d, size: 10 + Math.random() * 90}; - }) - } - - var svg = d3.select(domNode).append("svg") - .attr("width", 600) - .attr("height", 400) - .append("g") - .attr("transform", "translate(300,200)"); - - var layout = d3.layout.cloud().size([600, 400]) - .words(data) - .padding(5) - .rotate(function() { return ~~(Math.random() * 5) * 30 - 60; }) - .font("Impact") - .fontSize(function(d) { return d.size*2; }) - .on("end", draw) - .start(); - - function draw(words) { - svg.selectAll("text") - .data(words) - .enter().append("text") - .style("font-size", function(d) { return d.size + "px"; }) - .style("font-family", "Impact") - .style("fill", function(d, i) { return fill(i); }) - .attr("text-anchor", "middle") - .attr("transform", function(d) { - return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; - }) - .text(function(d) { return d.text; }); - } - - function updateChart() { - layout.spiral(self.spiral); - } - - return updateChart; - +/* +Remove any DOM nodes created by this widget or its children +*/ +CloudWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; }; exports.d3cloud = CloudWidget; From 8a709a0b00b9088bbe013e19f8f782cd11d0f9b5 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 09:15:56 +0100 Subject: [PATCH 076/183] Give the edit-text widget some extensibility hooks So that the upcoming CodeMirror plugin can do its stuff --- core/modules/new_widgets/edit-text.js | 47 ++++++++++++++++++--------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index 039f71f91..bc7af6a2b 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -61,6 +61,9 @@ EditTextWidget.prototype.render = function(parent,nextSibling) { // Insert the element into the DOM parent.insertBefore(domNode,nextSibling); this.domNodes.push(domNode); + if(this.postRender) { + this.postRender(); + } // Fix height this.fixHeight(); }; @@ -141,24 +144,39 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of 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) { + if(changedAttributes.title || changedAttributes.field || changedAttributes.index) { this.refreshSelf(); return true; - } else if(changedTiddlers[this.editTitle]){ - // 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) { - var editInfo = this.getEditInfo(); - domNode.value = editInfo.value; - } - // Fix the height if needed - this.fixHeight(); - } + } 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 */ @@ -191,7 +209,7 @@ EditTextWidget.prototype.fixHeight = function() { Handle a dom "input" event */ EditTextWidget.prototype.handleInputEvent = function(event) { - this.saveChanges(); + this.saveChanges(this.domNodes[0].value); this.fixHeight(); return true; }; @@ -208,8 +226,7 @@ EditTextWidget.prototype.handleFocusEvent = function(event) { return true; }; -EditTextWidget.prototype.saveChanges = function() { - var text = this.domNodes[0].value +EditTextWidget.prototype.saveChanges = function(text) { if(this.editField) { var tiddler = this.wiki.getTiddler(this.editTitle); if(!tiddler) { From cfa68dade50e2d79d42a6414ca19189e5562b1fd Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 09:48:57 +0100 Subject: [PATCH 077/183] Update CodeMirror plugin to latest version and the new widget framework Now the CodeMirror plugin modifies the behaviour of the core edit-text widget. --- .../tiddlywiki/codemirror/codemirroreditor.js | 118 +- .../codemirror/files/codemirror.css | 56 +- .../tiddlywiki/codemirror/files/codemirror.js | 1714 +++++++++++------ plugins/tiddlywiki/codemirror/styles.tid | 1 + 4 files changed, 1190 insertions(+), 699 deletions(-) diff --git a/plugins/tiddlywiki/codemirror/codemirroreditor.js b/plugins/tiddlywiki/codemirror/codemirroreditor.js index 7b88ddec5..08ae8d71c 100644 --- a/plugins/tiddlywiki/codemirror/codemirroreditor.js +++ b/plugins/tiddlywiki/codemirror/codemirroreditor.js @@ -1,9 +1,9 @@ /*\ -title: $:/plugins/tiddlywiki/codemirror/codemirroreditor.js +title: $:/core/modules/new_widgets/edit-text-codemirror.js type: application/javascript -module-type: editor +module-type: new_widget -A Codemirror text editor +Extend the edit-text widget to use CodeMirror \*/ (function(){ @@ -13,100 +13,40 @@ A Codemirror text editor "use strict"; if($tw.browser) { - require("./codemirror.js") + require("$:/plugins/tiddlywiki/codemirror/codemirror.js"); } -var CodeMirrorEditor = function(editWidget,tiddlerTitle,fieldName) { - this.editWidget = editWidget; - this.tiddlerTitle = tiddlerTitle; - this.fieldName = fieldName; -}; +var EditTextWidget = require("$:/core/modules/new_widgets/edit-text.js")["edit-text"]; /* -Get the tiddler being edited and current value +The edit-text widget calls this method just after inserting its dom nodes */ -CodeMirrorEditor.prototype.getEditInfo = function() { - // Get the current tiddler and the field name - var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle), - value; - // If we've got a tiddler, the value to display is the field string value - if(tiddler) { - value = tiddler.getFieldString(this.fieldName); +EditTextWidget.prototype.postRender = function() { + var self = this, + cm; + if($tw.browser && window.CodeMirror && this.editTag === "textarea") { + cm = CodeMirror.fromTextArea(this.domNodes[0],{ + lineWrapping: true, + lineNumbers: true + }); + cm.on("change",function() { + self.saveChanges(cm.getValue()); + }); } else { - // Otherwise, we need to construct a default value for the editor - switch(this.fieldName) { - case "text": - value = "Type the text for the tiddler '" + this.tiddlerTitle + "'"; - break; - case "title": - value = this.tiddlerTitle; - break; - default: - value = ""; - break; + cm = undefined; + } + this.codemirrorInstance = cm; +}; + +EditTextWidget.prototype.updateEditor = function(text) { + // Replace the edit value if the tiddler we're editing has changed + if(this.codemirrorInstance) { + if(!this.codemirrorInstance.hasFocus()) { + this.codemirrorInstance.setValue(text); } - value = this.editWidget.renderer.getAttribute("default",value); - } - return {tiddler: tiddler, value: value}; -}; - -CodeMirrorEditor.prototype.render = function() { - // Get the initial value of the editor - var editInfo = this.getEditInfo(); - // Create the editor nodes - var node = { - type: "element", - attributes: {} - }; - this.type = this.editWidget.renderer.getAttribute("type",this.fieldName === "text" ? "textarea" : "input"); - switch(this.type) { - case "textarea": - node.tag = "textarea"; - node.children = [{ - type: "text", - text: editInfo.value - }]; - break; - case "search": - node.tag = "input"; - node.attributes.type = {type: "string", value: "search"}; - node.attributes.value = {type: "string", value: editInfo.value}; - break; - default: // "input" - node.tag = "input"; - node.attributes.type = {type: "string", value: "text"}; - node.attributes.value = {type: "string", value: editInfo.value}; - break; - } - // Set the element details - this.editWidget.tag = this.editWidget.renderer.parseTreeNode.isBlock ? "div" : "span"; - this.editWidget.attributes = { - "class": "tw-edit-CodeMirrorEditor" - }; - this.editWidget.children = this.editWidget.renderer.renderTree.createRenderers(this.editWidget.renderer,[node]); -}; - -CodeMirrorEditor.prototype.postRenderInDom = function() { - if(this.type === "textarea") { - var self = this; - // HACK: We use the timeout because postRenderInDom is called before the dom nodes have been added to the document - window.setTimeout(function() { - self.codemirrorInstance = CodeMirror.fromTextArea(self.editWidget.children[0].domNode,{ - lineWrapping: true, - lineNumbers: true - }); - },1); + } else { + this.updateEditorDomNode(); } }; -CodeMirrorEditor.prototype.refreshInDom = function() { - if(document.activeElement !== this.editWidget.children[0].domNode) { - var editInfo = this.getEditInfo(); - this.editWidget.children[0].domNode.value = editInfo.value; - } -}; - -exports["text/vnd.tiddlywiki"] = CodeMirrorEditor; -exports["text/plain"] = CodeMirrorEditor; - })(); diff --git a/plugins/tiddlywiki/codemirror/files/codemirror.css b/plugins/tiddlywiki/codemirror/files/codemirror.css index 8de0b1908..23eaf74d4 100644 --- a/plugins/tiddlywiki/codemirror/files/codemirror.css +++ b/plugins/tiddlywiki/codemirror/files/codemirror.css @@ -19,7 +19,7 @@ padding: 0 4px; /* Horizontal padding of content */ } -.CodeMirror-scrollbar-filler { +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { background-color: white; /* The little square between H and V scrollbars */ } @@ -28,6 +28,7 @@ .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: #f7f7f7; + white-space: nowrap; } .CodeMirror-linenumbers {} .CodeMirror-linenumber { @@ -41,6 +42,7 @@ .CodeMirror div.CodeMirror-cursor { border-left: 1px solid black; + z-index: 3; } /* Shown when moving in bi-directional text */ .CodeMirror div.CodeMirror-secondarycursor { @@ -49,17 +51,14 @@ .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { width: auto; border: 0; - background: transparent; - background: rgba(0, 200, 0, .4); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); -} -/* Kludge to turn off filter in ie9+, which also accepts rgba */ -.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor:not(#nonsense_id) { - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + background: #7e7; + z-index: 1; } /* Can style cursor different in overwrite (non-insert) mode */ .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} +.cm-tab { display: inline-block; } + /* DEFAULT THEME */ .cm-s-default .cm-keyword {color: #708;} @@ -75,7 +74,6 @@ .cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string-2 {color: #f50;} .cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-error {color: #f00;} .cm-s-default .cm-qualifier {color: #555;} .cm-s-default .cm-builtin {color: #30a;} .cm-s-default .cm-bracket {color: #997;} @@ -90,13 +88,14 @@ .cm-positive {color: #292;} .cm-header, .cm-strong {font-weight: bold;} .cm-em {font-style: italic;} -.cm-emstrong {font-style: italic; font-weight: bold;} .cm-link {text-decoration: underline;} +.cm-s-default .cm-error {color: #f00;} .cm-invalidchar {color: #f00;} div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-activeline-background {background: #e8f2ff;} /* STOP */ @@ -107,16 +106,20 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} line-height: 1; position: relative; overflow: hidden; + background: white; + color: black; } .CodeMirror-scroll { /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ + /* See overflow: hidden in .CodeMirror */ margin-bottom: -30px; margin-right: -30px; padding-bottom: 30px; padding-right: 30px; height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; + -moz-box-sizing: content-box; + box-sizing: content-box; } .CodeMirror-sizer { position: relative; @@ -125,7 +128,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} /* The fake, visible scrollbars. Used to force redraw during scrolling before actuall scrolling happens, thus preventing shaking and flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { position: absolute; z-index: 6; display: none; @@ -142,17 +145,23 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } .CodeMirror-scrollbar-filler { right: 0; bottom: 0; - z-index: 6; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; } .CodeMirror-gutters { position: absolute; left: 0; top: 0; - height: 100%; padding-bottom: 30px; z-index: 3; } .CodeMirror-gutter { + white-space: normal; height: 100%; + -moz-box-sizing: content-box; + box-sizing: content-box; + padding-bottom: 30px; + margin-bottom: -32px; display: inline-block; /* Hack to make IE7 behave */ *zoom:1; @@ -169,7 +178,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } .CodeMirror pre { /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; border-width: 0; background: transparent; font-family: inherit; @@ -188,6 +197,16 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} white-space: pre-wrap; word-break: normal; } +.CodeMirror-code pre { + border-right: 30px solid transparent; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; +} +.CodeMirror-wrap .CodeMirror-code pre { + border-right: none; + width: auto; +} .CodeMirror-linebackground { position: absolute; left: 0; right: 0; top: 0; bottom: 0; @@ -200,9 +219,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} overflow: auto; } -.CodeMirror-widget { - display: inline-block; -} +.CodeMirror-widget {} .CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; @@ -210,7 +227,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-measure { position: absolute; - width: 100%; height: 0px; + width: 100%; + height: 0; overflow: hidden; visibility: hidden; } diff --git a/plugins/tiddlywiki/codemirror/files/codemirror.js b/plugins/tiddlywiki/codemirror/files/codemirror.js index 87339d05f..9f35191bf 100644 --- a/plugins/tiddlywiki/codemirror/files/codemirror.js +++ b/plugins/tiddlywiki/codemirror/files/codemirror.js @@ -1,4 +1,4 @@ -// CodeMirror version 3.1 +// CodeMirror version 3.19.1 // // CodeMirror is the only global var we claim window.CodeMirror = (function() { @@ -26,10 +26,11 @@ window.CodeMirror = (function() { // This is woefully incomplete. Suggestions for alternative methods welcome. var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); var mac = ios || /Mac/.test(navigator.platform); - var windows = /windows/i.test(navigator.platform); + var windows = /win/i.test(navigator.platform); var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); if (opera_version) opera_version = Number(opera_version[1]); + if (opera_version && opera_version >= 15) { opera = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); var captureMiddleClick = gecko || (ie && !ie_lt9); @@ -41,7 +42,7 @@ window.CodeMirror = (function() { function CodeMirror(place, options) { if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); - + this.options = options = options || {}; // Determine effective options based on given values and defaults. for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) @@ -93,18 +94,23 @@ window.CodeMirror = (function() { function makeDisplay(place, docStart) { var d = {}; - var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;"); + + var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;"); if (webkit) input.style.width = "1000px"; else input.setAttribute("wrap", "off"); - input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); + // if border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) input.style.border = "1px solid black"; + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); + // Wraps and hides input textarea d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); // The actual fake scrollbars. d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); // DIVs containing the selection and the actual code - d.lineDiv = elt("div"); + d.lineDiv = elt("div", null, "CodeMirror-code"); d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); // Blinky cursor, and element used to ensure cursor fits at the end of a line d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); @@ -120,18 +126,16 @@ window.CodeMirror = (function() { // Set to the height of the text, causes scrolling d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers - d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px"); + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); // Will contain the gutters, if any d.gutters = elt("div", null, "CodeMirror-gutters"); d.lineGutter = null; - // Helper element to properly size the gutter backgrounds - var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%"); // Provides scrolling - d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll"); + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); d.scroller.setAttribute("tabIndex", "-1"); // The element in which the editor lives. d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, - d.scrollbarFiller, d.scroller], "CodeMirror"); + d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); // Work around IE7 z-index bug if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); @@ -162,8 +166,6 @@ window.CodeMirror = (function() { d.pollingFast = false; // Self-resetting timeout for the poller d.poll = new Delayed(); - // True when a drag from the editor is active - d.draggingText = false; d.cachedCharWidth = d.cachedTextHeight = null; d.measureLineCache = []; @@ -181,7 +183,7 @@ window.CodeMirror = (function() { // Used for measuring wheel scrolling granularity d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; - + return d; } @@ -212,7 +214,7 @@ window.CodeMirror = (function() { estimateLineHeights(cm); regChange(cm); clearCaches(cm); - setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100); + setTimeout(function(){updateScrollbars(cm);}, 100); } function estimateHeight(cm) { @@ -237,9 +239,10 @@ window.CodeMirror = (function() { } function keyMapChanged(cm) { - var style = keyMap[cm.options.keyMap].style; + var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); + cm.state.disableInput = map.disableInput; } function themeChanged(cm) { @@ -251,6 +254,7 @@ window.CodeMirror = (function() { function guttersChanged(cm) { updateGutters(cm); regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); } function updateGutters(cm) { @@ -302,46 +306,59 @@ window.CodeMirror = (function() { // Make sure the gutters options contains the element // "CodeMirror-linenumbers" when the lineNumbers option is true. function setGuttersForLineNumbers(options) { - var found = false; - for (var i = 0; i < options.gutters.length; ++i) { - if (options.gutters[i] == "CodeMirror-linenumbers") { - if (options.lineNumbers) found = true; - else options.gutters.splice(i--, 1); - } + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); } - if (!found && options.lineNumbers) - options.gutters.push("CodeMirror-linenumbers"); } // SCROLLBARS // Re-synchronize the fake scrollbars with the actual size of the // content. Optionally force a scrollTop. - function updateScrollbars(d /* display */, docHeight) { - var totalHeight = docHeight + 2 * paddingTop(d); + function updateScrollbars(cm) { + var d = cm.display, docHeight = cm.doc.height; + var totalHeight = docHeight + paddingVert(d); d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; + d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px"; var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); - var needsH = d.scroller.scrollWidth > d.scroller.clientWidth; - var needsV = scrollHeight > d.scroller.clientHeight; + var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1); + var needsV = scrollHeight > (d.scroller.clientHeight + 1); if (needsV) { d.scrollbarV.style.display = "block"; d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; - d.scrollbarV.firstChild.style.height = + d.scrollbarV.firstChild.style.height = (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; - } else d.scrollbarV.style.display = ""; + } else { + d.scrollbarV.style.display = ""; + d.scrollbarV.firstChild.style.height = "0"; + } if (needsH) { d.scrollbarH.style.display = "block"; d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; d.scrollbarH.firstChild.style.width = (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; - } else d.scrollbarH.style.display = ""; + } else { + d.scrollbarH.style.display = ""; + d.scrollbarH.firstChild.style.width = "0"; + } if (needsH && needsV) { d.scrollbarFiller.style.display = "block"; d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; } else d.scrollbarFiller.style.display = ""; + if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px"; + d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; + } else d.gutterFiller.style.display = ""; - if (mac_geLion && scrollbarWidth(d.measure) === 0) + if (mac_geLion && scrollbarWidth(d.measure) === 0) { d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; + d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none"; + } } function visibleLines(display, doc, viewPort) { @@ -393,24 +410,43 @@ window.CodeMirror = (function() { // DISPLAY DRAWING - function updateDisplay(cm, changes, viewPort) { - var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo; - var updated = updateDisplayInner(cm, changes, viewPort); + function updateDisplay(cm, changes, viewPort, forced) { + var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; + var visible = visibleLines(cm.display, cm.doc, viewPort); + for (var first = true;; first = false) { + var oldWidth = cm.display.scroller.clientWidth; + if (!updateDisplayInner(cm, changes, visible, forced)) break; + updated = true; + changes = []; + updateSelection(cm); + updateScrollbars(cm); + if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { + forced = true; + continue; + } + forced = false; + + // Clip forced viewport to actual scrollable area + if (viewPort) + viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, + typeof viewPort == "number" ? viewPort : viewPort.top); + visible = visibleLines(cm.display, cm.doc, viewPort); + if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) + break; + } + if (updated) { signalLater(cm, "update", cm); if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); } - updateSelection(cm); - updateScrollbars(cm.display, cm.doc.height); - return updated; } // Uses a set of changes plus the current scroll position to // determine which DOM updates have to be made, and makes the // updates. - function updateDisplayInner(cm, changes, viewPort) { + function updateDisplayInner(cm, changes, visible, forced) { var display = cm.display, doc = cm.doc; if (!display.wrapper.clientWidth) { display.showingFrom = display.showingTo = doc.first; @@ -418,12 +454,8 @@ window.CodeMirror = (function() { return; } - // Compute the new visible window - // If scrollTop is specified, use that to determine which lines - // to render instead of the current scrollbar position. - var visible = visibleLines(display, doc, viewPort); // Bail out if the visible area is already rendered and nothing changed. - if (changes.length == 0 && + if (!forced && changes.length == 0 && visible.from > display.showingFrom && visible.to < display.showingTo) return; @@ -436,7 +468,7 @@ window.CodeMirror = (function() { var positionsChangedFrom = Infinity; if (cm.options.lineNumbers) for (var i = 0; i < changes.length; ++i) - if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } + if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; } var end = doc.first + doc.size; var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); @@ -476,26 +508,39 @@ window.CodeMirror = (function() { if (range.from >= range.to) intact.splice(i--, 1); else intactLines += range.to - range.from; } - if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) { + if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) { updateViewOffset(cm); return; } intact.sort(function(a, b) {return a.from - b.from;}); - var focused = document.activeElement; + // Avoid crashing on IE's "unspecified error" when in iframes + try { + var focused = document.activeElement; + } catch(e) {} if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; patchDisplay(cm, from, to, intact, positionsChangedFrom); display.lineDiv.style.display = ""; - if (document.activeElement != focused && focused.offsetHeight) focused.focus(); + if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus(); var different = from != display.showingFrom || to != display.showingTo || display.lastSizeC != display.wrapper.clientHeight; // This is just a bogus formula that detects when the editor is // resized or the font size changes. - if (different) display.lastSizeC = display.wrapper.clientHeight; + if (different) { + display.lastSizeC = display.wrapper.clientHeight; + startWorker(cm, 400); + } display.showingFrom = from; display.showingTo = to; - startWorker(cm, 100); + updateHeightsInViewport(cm); + updateViewOffset(cm); + + return true; + } + + function updateHeightsInViewport(cm) { + var display = cm.display; var prevBottom = display.lineDiv.offsetTop; for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { if (ie_lt8) { @@ -515,11 +560,6 @@ window.CodeMirror = (function() { widgets[i].height = widgets[i].node.offsetHeight; } } - updateViewOffset(cm); - - if (visibleLines(display, doc, viewPort).to > to) - updateDisplayInner(cm, [], viewPort); - return true; } function updateViewOffset(cm) { @@ -585,8 +625,9 @@ window.CodeMirror = (function() { if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); if (lineIsHidden(cm.doc, line)) { if (line.height != 0) updateLineHeight(line, 0); - if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) - if (line.widgets[i].showIfHidden) { + if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i]; + if (w.showIfHidden) { var prev = cur.previousSibling; if (/pre/i.test(prev.nodeName)) { var wrap = elt("div", null, null, "position: relative"); @@ -594,9 +635,11 @@ window.CodeMirror = (function() { wrap.appendChild(prev); prev = wrap; } - var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget")); - positionLineWidget(line.widgets[i], wnode, prev, dims); + var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget")); + if (!w.handleMouseEvents) wnode.ignoreEvents = true; + positionLineWidget(w, wnode, prev, dims); } + } } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) { // This line is intact. Skip to the actual node. Update its // line number if needed. @@ -627,37 +670,38 @@ window.CodeMirror = (function() { } function buildLineElement(cm, line, lineNo, dims, reuse) { - var lineElement = lineContent(cm, line); + var built = buildLineContent(cm, line), lineElement = built.pre; var markers = line.gutterMarkers, display = cm.display, wrap; - if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets) + var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass; + if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets) return lineElement; // Lines with gutter elements, widgets or a background class need // to be wrapped again, and have the extra elements added to the // wrapper div - + if (reuse) { reuse.alignable = null; - var isOk = true, widgetsSeen = 0; + var isOk = true, widgetsSeen = 0, insertBefore = null; for (var n = reuse.firstChild, next; n; n = next) { next = n.nextSibling; if (!/\bCodeMirror-linewidget\b/.test(n.className)) { reuse.removeChild(n); } else { - for (var i = 0, first = true; i < line.widgets.length; ++i) { - var widget = line.widgets[i], isFirst = false; - if (!widget.above) { isFirst = first; first = false; } + for (var i = 0; i < line.widgets.length; ++i) { + var widget = line.widgets[i]; if (widget.node == n.firstChild) { + if (!widget.above && !insertBefore) insertBefore = n; positionLineWidget(widget, n, reuse, dims); ++widgetsSeen; - if (isFirst) reuse.insertBefore(lineElement, n); break; } } if (i == line.widgets.length) { isOk = false; break; } } } + reuse.insertBefore(lineElement, insertBefore); if (isOk && widgetsSeen == line.widgets.length) { wrap = reuse; reuse.className = line.wrapClass || ""; @@ -668,8 +712,8 @@ window.CodeMirror = (function() { wrap.appendChild(lineElement); } // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.bgClass) - wrap.insertBefore(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"), wrap.firstChild); + if (bgClass) + wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild); if (cm.options.lineNumbers || markers) { var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), @@ -692,6 +736,7 @@ window.CodeMirror = (function() { if (ie_lt8) wrap.style.zIndex = 2; if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.ignoreEvents = true; positionLineWidget(widget, node, wrap, dims); if (widget.above) wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); @@ -735,12 +780,14 @@ window.CodeMirror = (function() { display.selectionDiv.style.display = "none"; // Move the hidden textarea near the cursor to prevent scrolling artifacts - var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); - var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); - display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)) + "px"; - display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)) + "px"; + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); + var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); + display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)) + "px"; + display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)) + "px"; + } } // No selection, plain cursor @@ -772,67 +819,59 @@ window.CodeMirror = (function() { "px; height: " + (bottom - top) + "px")); } - function drawForLine(line, fromArg, toArg, retTop) { + function drawForLine(line, fromArg, toArg) { var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity; - function coords(ch) { - return charCoords(cm, Pos(line, ch), "div", lineObj); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); } iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(dir == "rtl" ? to - 1 : from); - var rightPos = coords(dir == "rtl" ? from : to - 1); - var left = leftPos.left, right = rightPos.right; + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = pl; if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part add(left, leftPos.top, null, leftPos.bottom); left = pl; if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); } if (toArg == null && to == lineLen) right = clientWidth; - if (fromArg == null && from == 0) left = pl; - rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal); + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; if (left < pl + 1) left = pl; add(left, rightPos.top, right - left, rightPos.bottom); }); - return rVal; + return {start: start, end: end}; } if (sel.from.line == sel.to.line) { drawForLine(sel.from.line, sel.from.ch, sel.to.ch); } else { - var fromObj = getLine(doc, sel.from.line); - var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(); - path.push(found.from.ch, found.to.line, found.to.ch); - if (found.to.line == sel.to.line) { - path.push(sel.to.ch); - singleLine = true; - break; + var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line); + var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine); + var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end; + var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(pl, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); } - cur = getLine(doc, found.to.line); - } - - // This is a single, merged line - if (singleLine) { - for (var i = 0; i < path.length; i += 3) - drawForLine(path[i], path[i+1], path[i+2]); - } else { - var middleTop, middleBot, toObj = getLine(doc, sel.to.line); - if (sel.from.ch) - // Draw the first line of selection. - middleTop = drawForLine(sel.from.line, sel.from.ch, null, false); - else - // Simply include it in the middle block. - middleTop = heightAtLine(cm, fromObj) - display.viewOffset; - - if (!sel.to.ch) - middleBot = heightAtLine(cm, toObj) - display.viewOffset; - else - middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true); - - if (middleTop < middleBot) add(pl, middleTop, null, middleBot); } + if (leftEnd.bottom < rightStart.top) + add(pl, leftEnd.bottom, null, rightStart.top); } removeChildrenAndAdd(display.selectionDiv, fragment); @@ -841,14 +880,15 @@ window.CodeMirror = (function() { // Cursor-blinking function restartBlink(cm) { + if (!cm.state.focused) return; var display = cm.display; clearInterval(display.blinker); var on = true; display.cursor.style.visibility = display.otherCursor.style.visibility = ""; - display.blinker = setInterval(function() { - if (!display.cursor.offsetHeight) return; - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); } // HIGHLIGHT WORKER @@ -898,12 +938,13 @@ window.CodeMirror = (function() { // valid state. If that fails, it returns the line with the // smallest indentation, which tends to need the least context to // parse correctly. - function findStartLine(cm, n) { + function findStartLine(cm, n, precise) { var minindent, minline, doc = cm.doc; - for (var search = n, lim = n - 100; search > lim; --search) { + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { if (search <= doc.first) return doc.first; var line = getLine(doc, search - 1); - if (line.stateAfter) return search; + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; var indented = countColumn(line.text, null, cm.options.tabSize); if (minline == null || minindent > indented) { minline = search - 1; @@ -913,10 +954,10 @@ window.CodeMirror = (function() { return minline; } - function getStateBefore(cm, n) { + function getStateBefore(cm, n, precise) { var doc = cm.doc, display = cm.display; - if (!doc.mode.startState) return true; - var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; if (!state) state = startState(doc.mode); else state = copyState(doc.mode, state); doc.iter(pos, n, function(line) { @@ -925,55 +966,79 @@ window.CodeMirror = (function() { line.stateAfter = save ? copyState(doc.mode, state) : null; ++pos; }); + if (precise) doc.frontier = pos; return state; } // POSITION MEASUREMENT - + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} function paddingLeft(display) { var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x")); return e.offsetLeft; } - function measureChar(cm, line, ch, data) { + function measureChar(cm, line, ch, data, bias) { var dir = -1; data = data || measureLine(cm, line); - + if (data.crude) { + var left = data.left + ch * data.width; + return {left: left, right: left + data.width, top: data.top, bottom: data.bottom}; + } + for (var pos = ch;; pos += dir) { var r = data[pos]; if (r) break; if (dir < 0 && pos == 0) dir = 1; } + bias = pos > ch ? "left" : pos < ch ? "right" : bias; + if (bias == "left" && r.leftSide) r = r.leftSide; + else if (bias == "right" && r.rightSide) r = r.rightSide; return {left: pos < ch ? r.right : r.left, right: pos > ch ? r.left : r.right, - top: r.top, bottom: r.bottom}; + top: r.top, + bottom: r.bottom}; + } + + function findCachedMeasurement(cm, line) { + var cache = cm.display.measureLineCache; + for (var i = 0; i < cache.length; ++i) { + var memo = cache[i]; + if (memo.text == line.text && memo.markedSpans == line.markedSpans && + cm.display.scroller.clientWidth == memo.width && + memo.classes == line.textClass + "|" + line.wrapClass) + return memo; + } + } + + function clearCachedMeasurement(cm, line) { + var exists = findCachedMeasurement(cm, line); + if (exists) exists.text = exists.measure = exists.markedSpans = null; } function measureLine(cm, line) { // First look in the cache - var display = cm.display, cache = cm.display.measureLineCache; - for (var i = 0; i < cache.length; ++i) { - var memo = cache[i]; - if (memo.text == line.text && memo.markedSpans == line.markedSpans && - display.scroller.clientWidth == memo.width && - memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) - return memo.measure; - } - + var cached = findCachedMeasurement(cm, line); + if (cached) return cached.measure; + + // Failing that, recompute and store result in cache var measure = measureLineInner(cm, line); - // Store result in the cache - var memo = {text: line.text, width: display.scroller.clientWidth, + var cache = cm.display.measureLineCache; + var memo = {text: line.text, width: cm.display.scroller.clientWidth, markedSpans: line.markedSpans, measure: measure, - classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; - if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo; + classes: line.textClass + "|" + line.wrapClass}; + if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; else cache.push(memo); return measure; } function measureLineInner(cm, line) { + if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom) + return crudelyMeasureLine(cm, line); + var display = cm.display, measure = emptyArray(line.text.length); - var pre = lineContent(cm, line, measure); + var pre = buildLineContent(cm, line, measure, true).pre; // IE does not cache element positions of inline elements between // calls to getBoundingClientRect. This makes the loop below, @@ -1009,45 +1074,90 @@ window.CodeMirror = (function() { if (ie_lt9 && display.measure.first != pre) removeChildrenAndAdd(display.measure, pre); - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { - var size = getRect(cur); - var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot); - for (var j = 0; j < vranges.length; j += 2) { - var rtop = vranges[j], rbot = vranges[j+1]; + function measureRect(rect) { + var top = rect.top - outer.top, bot = rect.bottom - outer.top; + if (bot > maxBot) bot = maxBot; + if (top < 0) top = 0; + for (var i = vranges.length - 2; i >= 0; i -= 2) { + var rtop = vranges[i], rbot = vranges[i+1]; if (rtop > bot || rbot < top) continue; if (rtop <= top && rbot >= bot || top <= rtop && bot >= rbot || Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { - vranges[j] = Math.min(top, rtop); - vranges[j+1] = Math.max(bot, rbot); + vranges[i] = Math.min(top, rtop); + vranges[i+1] = Math.max(bot, rbot); break; } } - if (j == vranges.length) vranges.push(top, bot); - var right = size.right; - if (cur.measureRight) right = getRect(cur.measureRight).left; - data[i] = {left: size.left - outer.left, right: right - outer.left, top: j}; + if (i < 0) { i = vranges.length; vranges.push(top, bot); } + return {left: rect.left - outer.left, + right: rect.right - outer.left, + top: i, bottom: null}; } - for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { - var vr = cur.top; - cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; - } - if (!cm.options.lineWrapping) { - var last = pre.lastChild; - if (last.nodeType == 3) last = pre.appendChild(elt("span", "\u200b")); - data.width = getRect(last).right - outer.left; + function finishRect(rect) { + rect.bottom = vranges[rect.top+1]; + rect.top = vranges[rect.top]; } + for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { + var node = cur, rect = null; + // A widget might wrap, needs special care + if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) { + if (cur.firstChild.nodeType == 1) node = cur.firstChild; + var rects = node.getClientRects(); + if (rects.length > 1) { + rect = data[i] = measureRect(rects[0]); + rect.rightSide = measureRect(rects[rects.length - 1]); + } + } + if (!rect) rect = data[i] = measureRect(getRect(node)); + if (cur.measureRight) rect.right = getRect(cur.measureRight).left; + if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide)); + } + removeChildren(cm.display.measure); + for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { + finishRect(cur); + if (cur.leftSide) finishRect(cur.leftSide); + if (cur.rightSide) finishRect(cur.rightSide); + } return data; } + function crudelyMeasureLine(cm, line) { + var copy = new Line(line.text.slice(0, 100), null); + if (line.textClass) copy.textClass = line.textClass; + var measure = measureLineInner(cm, copy); + var left = measureChar(cm, copy, 0, measure, "left"); + var right = measureChar(cm, copy, 99, measure, "right"); + return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100}; + } + + function measureLineWidth(cm, line) { + var hasBadSpan = false; + if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { + var sp = line.markedSpans[i]; + if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; + } + var cached = !hasBadSpan && findCachedMeasurement(cm, line); + if (cached || line.text.length >= cm.options.crudeMeasuringFrom) + return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right; + + var pre = buildLineContent(cm, line, null, true).pre; + var end = pre.appendChild(zeroWidthElement(cm.display.measure)); + removeChildrenAndAdd(cm.display.measure, pre); + return getRect(end).right - getRect(cm.display.lineDiv).left; + } + function clearCaches(cm) { cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; - cm.display.maxLineChanged = true; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; cm.display.lineNumChars = null; } + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" function intoCoordSystem(cm, lineObj, rect, context) { if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { @@ -1057,60 +1167,75 @@ window.CodeMirror = (function() { if (context == "line") return rect; if (!context) context = "local"; var yOff = heightAtLine(cm, lineObj); - if (context != "local") yOff -= cm.display.viewOffset; - if (context == "page") { + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { var lOff = getRect(cm.display.lineSpace); - yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); - var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); rect.left += xOff; rect.right += xOff; } rect.top += yOff; rect.bottom += yOff; return rect; } - function charCoords(cm, pos, context, lineObj) { + // Context may be "window", "page", "div", or "local"/null + // Result is in "div" coords + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = getRect(cm.display.sizer); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = getRect(cm.display.lineSpace); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { if (!lineObj) lineObj = getLine(cm.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context); } function cursorCoords(cm, pos, context, lineObj, measurement) { lineObj = lineObj || getLine(cm.doc, pos.line); if (!measurement) measurement = measureLine(cm, lineObj); function get(ch, right) { - var m = measureChar(cm, lineObj, ch, measurement); + var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left"); if (right) m.left = m.right; else m.right = m.left; return intoCoordSystem(cm, lineObj, m, context); } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } var order = getOrder(lineObj), ch = pos.ch; if (!order) return get(ch); - var main, other, linedir = order[0].level; - for (var i = 0; i < order.length; ++i) { - var part = order[i], rtl = part.level % 2, nb, here; - if (part.from < ch && part.to > ch) return get(ch, rtl); - var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to; - if (left == ch) { - // IE returns bogus offsets and widths for edges where the - // direction flips, but only for the side with the lower - // level. So we try to use the side with the higher level. - if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true); - else here = get(rtl && part.from != part.to ? ch - 1 : ch); - if (rtl == linedir) main = here; else other = here; - } else if (right == ch) { - var nb = i < order.length - 1 && order[i+1]; - if (!rtl && nb && nb.from == nb.to) continue; - if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from); - else here = get(rtl ? ch : ch - 1, true); - if (rtl == linedir) main = here; else other = here; - } - } - if (linedir && !ch) other = get(order[0].to - 1); - if (!main) return other; - if (other) main.other = other; - return main; + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; } - function PosMaybeOutside(line, ch, outside) { + function PosWithInfo(line, ch, outside, xRel) { var pos = new Pos(line, ch); + pos.xRel = xRel; if (outside) pos.outside = true; return pos; } @@ -1119,10 +1244,10 @@ window.CodeMirror = (function() { function coordsChar(cm, x, y) { var doc = cm.doc; y += cm.display.viewOffset; - if (y < 0) return PosMaybeOutside(doc.first, 0, true); + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1; if (lineNo > last) - return PosMaybeOutside(doc.first + doc.size - 1, getLine(doc, last).text.length, true); + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); if (x < 0) x = 0; for (;;) { @@ -1130,7 +1255,7 @@ window.CodeMirror = (function() { var found = coordsCharInner(cm, lineObj, lineNo, x, y); var merged = collapsedSpanAtEnd(lineObj); var mergedPos = merged && merged.find(); - if (merged && found.ch >= mergedPos.from.ch) + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) lineNo = mergedPos.to.line; else return found; @@ -1139,15 +1264,15 @@ window.CodeMirror = (function() { function coordsCharInner(cm, lineObj, lineNo, x, y) { var innerOff = y - heightAtLine(cm, lineObj); - var wrongLine = false, cWidth = cm.display.wrapper.clientWidth; + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; var measurement = measureLine(cm, lineObj); function getX(ch) { var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, measurement); wrongLine = true; - if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth); - else if (innerOff < sp.top) return sp.left + cWidth; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; else wrongLine = false; return sp.left; } @@ -1156,14 +1281,15 @@ window.CodeMirror = (function() { var from = lineLeft(lineObj), to = lineRight(lineObj); var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; - if (x > toX) return PosMaybeOutside(lineNo, to, toOutside); + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); // Do a binary search between these bounds. for (;;) { if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { - var after = x - fromX < toX - x, ch = after ? from : to; + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; - var pos = PosMaybeOutside(lineNo, ch, after ? fromOutside : toOutside); - pos.after = after; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < 0 ? -1 : xDiff ? 1 : 0); return pos; } var step = Math.ceil(dist / 2), middle = from + step; @@ -1172,8 +1298,8 @@ window.CodeMirror = (function() { for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); } var middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist -= step;} - else {from = middle; fromX = middleX; fromOutside = wrongLine; dist = step;} + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} } } @@ -1220,10 +1346,12 @@ window.CodeMirror = (function() { // An array of ranges of lines that have to be updated. See // updateDisplay. changes: [], + forceUpdate: false, updateInput: null, userSelChange: null, textChanged: null, selectionChanged: false, + cursorActivity: false, updateMaxLine: false, updateScrollPos: false, id: ++nextOpId @@ -1236,8 +1364,8 @@ window.CodeMirror = (function() { cm.curOp = null; if (op.updateMaxLine) computeMaxLength(cm); - if (display.maxLineChanged && !cm.options.lineWrapping) { - var width = measureLine(cm, display.maxLine).width; + if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) { + var width = measureLineWidth(cm, display.maxLine); display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px"; display.maxLineChanged = false; var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth); @@ -1251,13 +1379,18 @@ window.CodeMirror = (function() { var coords = cursorCoords(cm, doc.sel.head); newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); } - if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) - updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop); + if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) { + updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate); + if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; + } if (!updated && op.selectionChanged) updateSelection(cm); if (op.updateScrollPos) { display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop; display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft; alignHorizontally(cm); + if (op.scrollToPos) + scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), + clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); } else if (newScrollPos) { scrollCursorIntoView(cm); } @@ -1279,7 +1412,7 @@ window.CodeMirror = (function() { } if (op.textChanged) signal(cm, "change", cm, op.textChanged); - if (op.selectionChanged) signal(cm, "cursorActivity", cm); + if (op.cursorActivity) signal(cm, "cursorActivity", cm); if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); } @@ -1344,32 +1477,37 @@ window.CodeMirror = (function() { // supported or compatible enough yet to rely on.) function readInput(cm) { var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false; + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } var text = input.value; if (text == prevInput && posEq(sel.from, sel.to)) return false; - // IE enjoys randomly deselecting our input's text when - // re-focusing. If the selection is gone but the cursor is at the - // start of the input, that's probably what happened. - if (ie && text && input.selectionStart === 0) { + if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { resetInput(cm, true); return false; } + var withOp = !cm.curOp; if (withOp) startOperation(cm); sel.shift = false; var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput[same] == text[same]) ++same; + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; var from = sel.from, to = sel.to; if (same < prevInput.length) from = Pos(from.line, from.ch - (prevInput.length - same)); else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming) to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same))); + var updateInput = cm.curOp.updateInput; - makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)), - origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end"); - + var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)), + origin: cm.state.pasteIncoming ? "paste" : "+input"}; + makeChange(cm.doc, changeEvent, "end"); cm.curOp.updateInput = updateInput; - if (text.length > 1000) input.value = cm.display.prevInput = ""; + signalLater(cm, "inputRead", cm, changeEvent); + + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; else cm.display.prevInput = text; if (withOp) endOperation(cm); cm.state.pasteIncoming = false; @@ -1382,10 +1520,14 @@ window.CodeMirror = (function() { cm.display.prevInput = ""; minimal = hasCopyEvent && (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); - if (minimal) cm.display.input.value = "-"; - else cm.display.input.value = selected || cm.getSelection(); + var content = minimal ? "-" : selected || cm.getSelection(); + cm.display.input.value = content; if (cm.state.focused) selectInput(cm.display.input); - } else if (user) cm.display.prevInput = cm.display.input.value = ""; + if (ie && !ie_lt9) cm.display.inputHasSelection = content; + } else if (user) { + cm.display.prevInput = cm.display.input.value = ""; + if (ie && !ie_lt9) cm.display.inputHasSelection = null; + } cm.display.inaccurateSelection = minimal; } @@ -1403,7 +1545,17 @@ window.CodeMirror = (function() { function registerEventHandlers(cm) { var d = cm.display; on(d.scroller, "mousedown", operation(cm, onMouseDown)); - on(d.scroller, "dblclick", operation(cm, e_preventDefault)); + if (ie) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = findWordAt(getLine(cm.doc, pos.line).text, pos); + extendSelection(cm.doc, word.from, word.to); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); on(d.lineSpace, "selectstart", function(e) { if (!eventInWidget(d, e)) e_preventDefault(e); }); @@ -1413,15 +1565,17 @@ window.CodeMirror = (function() { if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); on(d.scroller, "scroll", function() { - setScrollTop(cm, d.scroller.scrollTop); - setScrollLeft(cm, d.scroller.scrollLeft, true); - signal(cm, "scroll", cm); + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } }); on(d.scrollbarV, "scroll", function() { - setScrollTop(cm, d.scrollbarV.scrollTop); + if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop); }); on(d.scrollbarH, "scroll", function() { - setScrollLeft(cm, d.scrollbarH.scrollLeft); + if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft); }); on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); @@ -1433,11 +1587,15 @@ window.CodeMirror = (function() { // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + var resizeTimer; function onResize() { - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = null; - clearCaches(cm); - runInOp(cm, bind(regChange, cm)); + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null; + clearCaches(cm); + runInOp(cm, bind(regChange, cm)); + }, 100); } on(window, "resize", onResize); // Above handler holds on to the editor and its data structures. @@ -1447,21 +1605,24 @@ window.CodeMirror = (function() { for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {} if (p) setTimeout(unregister, 5000); else off(window, "resize", onResize); - } + } setTimeout(unregister, 5000); on(d.input, "keyup", operation(cm, function(e) { - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; if (e.keyCode == 16) cm.doc.sel.shift = false; })); - on(d.input, "input", bind(fastPoll, cm)); + on(d.input, "input", function() { + if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; + fastPoll(cm); + }); on(d.input, "keydown", operation(cm, onKeyDown)); on(d.input, "keypress", operation(cm, onKeyPress)); on(d.input, "focus", bind(onFocus, cm)); on(d.input, "blur", bind(onBlur, cm)); function drag_(e) { - if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; e_stop(e); } if (cm.options.dragDrop) { @@ -1470,12 +1631,22 @@ window.CodeMirror = (function() { on(d.scroller, "dragover", drag_); on(d.scroller, "drop", operation(cm, onDrop)); } - on(d.scroller, "paste", function(e){ + on(d.scroller, "paste", function(e) { if (eventInWidget(d, e)) return; - focusInput(cm); + focusInput(cm); fastPoll(cm); }); on(d.input, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = d.input.selectionStart, end = d.input.selectionEnd; + d.input.value += "$"; + d.input.selectionStart = start; + d.input.selectionEnd = end; + cm.state.fakedLastChar = true; + } cm.state.pasteIncoming = true; fastPoll(cm); }); @@ -1500,9 +1671,7 @@ window.CodeMirror = (function() { function eventInWidget(display, e) { for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n) return true; - if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) || - n.parentNode == display.sizer && n != display.mover) return true; + if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; } } @@ -1512,7 +1681,7 @@ window.CodeMirror = (function() { var target = e_target(e); if (target == display.scrollbarH || target == display.scrollbarH.firstChild || target == display.scrollbarV || target == display.scrollbarV.firstChild || - target == display.scrollbarFiller) return null; + target == display.scrollbarFiller || target == display.gutterFiller) return null; } var x, y, space = getRect(display.lineSpace); // Fails unpredictably on IE[67] when mouse is dragged around quickly. @@ -1522,6 +1691,7 @@ window.CodeMirror = (function() { var lastClick, lastDoubleClick; function onMouseDown(e) { + if (signalDOMEvent(this, e)) return; var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel; sel.shift = e.shiftKey; @@ -1540,6 +1710,7 @@ window.CodeMirror = (function() { if (captureMiddleClick) onContextMenu.call(cm, cm, e); return; case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; if (start) extendSelection(cm.doc, start); setTimeout(bind(focusInput, cm), 20); e_preventDefault(e); @@ -1592,9 +1763,12 @@ window.CodeMirror = (function() { e_preventDefault(e); if (type == "single") extendSelection(cm.doc, clipPos(doc, start)); - var startstart = sel.from, startend = sel.to; + var startstart = sel.from, startend = sel.to, lastPos = start; function doSelect(cur) { + if (posEq(lastPos, cur)) return; + lastPos = cur; + if (type == "single") { extendSelection(cm.doc, clipPos(doc, start), cur); return; @@ -1642,8 +1816,6 @@ window.CodeMirror = (function() { function done(e) { counter = Infinity; - var cur = posFromMouse(cm, e); - if (cur) doSelect(cur); e_preventDefault(e); focusInput(cm); off(document, "mousemove", move); @@ -1659,11 +1831,48 @@ window.CodeMirror = (function() { on(document, "mouseup", up); } + function gutterEvent(cm, e, type, prevent, signalfn) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = getRect(display.lineDiv); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && getRect(g).right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + function onDrop(e) { var cm = this; - if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) return; e_preventDefault(e); + if (ie) lastDrop = +new Date; var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; if (!pos || isReadOnly(cm)) return; if (files && files.length && window.FileReader && window.File) { @@ -1674,7 +1883,7 @@ window.CodeMirror = (function() { text[i] = reader.result; if (++read == n) { pos = clipPos(cm.doc, pos); - replaceRange(cm.doc, text.join(""), pos, "around", "paste"); + makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around"); } }; reader.readAsText(file); @@ -1703,34 +1912,10 @@ window.CodeMirror = (function() { } } - function clickInGutter(cm, e) { - var display = cm.display; - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - - if (mX >= Math.floor(getRect(display.gutters).right)) return false; - e_preventDefault(e); - if (!hasHandler(cm, "gutterClick")) return true; - - var lineBox = getRect(display.lineDiv); - if (mY > lineBox.bottom) return true; - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && getRect(g).right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signalLater(cm, "gutterClick", cm, line, gutter, e); - break; - } - } - return true; - } - function onDragStart(cm, e) { - if (eventInWidget(cm.display, e)) return; - + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + var txt = cm.getSelection(); e.dataTransfer.setData("Text", txt); @@ -1738,6 +1923,7 @@ window.CodeMirror = (function() { // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. if (e.dataTransfer.setDragImage && !safari) { var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; if (opera) { img.width = img.height = 1; cm.display.wrapper.appendChild(img); @@ -1756,6 +1942,7 @@ window.CodeMirror = (function() { if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; if (gecko) updateDisplay(cm, []); + startWorker(cm, 100); } function setScrollLeft(cm, val, isScroller) { if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; @@ -1793,6 +1980,11 @@ window.CodeMirror = (function() { if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; else if (dy == null) dy = e.wheelDelta; + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + // Webkit browsers on OS X abort momentum scrolls when the target // of the scroll event is removed from the scrollable element. // This hack (see related code in patchDisplay) makes sure the @@ -1806,7 +1998,6 @@ window.CodeMirror = (function() { } } - var display = cm.display, scroll = display.scroller; // On some browsers, horizontal scrolling will cause redraws to // happen before the gutter has been realigned, causing it to // wriggle around in a most unseemly way. When we have an @@ -1873,8 +2064,8 @@ window.CodeMirror = (function() { function allKeyMaps(cm) { var maps = cm.state.keyMaps.slice(0); + if (cm.options.extraKeys) maps.push(cm.options.extraKeys); maps.push(cm.options.keyMap); - if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys); return maps; } @@ -1884,8 +2075,10 @@ window.CodeMirror = (function() { var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; clearTimeout(maybeTransition); if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { - if (getKeyMap(cm.options.keyMap) == startMap) + if (getKeyMap(cm.options.keyMap) == startMap) { cm.options.keyMap = (next.call ? next.call(null, cm) : next); + keyMapChanged(cm); + } }, 50); var name = keyName(e, true), handled = false; @@ -1898,17 +2091,18 @@ window.CodeMirror = (function() { // 'go') bound to the keyname without 'Shift-'. handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) || lookupKey(name, keymaps, function(b) { - if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b); + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); }); } else { handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); } - if (handled == "stop") handled = false; if (handled) { e_preventDefault(e); restartBlink(cm); if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + signalLater(cm, "keyHandled", cm, name, e); } return handled; } @@ -1919,6 +2113,7 @@ window.CodeMirror = (function() { if (handled) { e_preventDefault(e); restartBlink(cm); + signalLater(cm, "keyHandled", cm, "'" + ch + "'", e); } return handled; } @@ -1927,8 +2122,8 @@ window.CodeMirror = (function() { function onKeyDown(e) { var cm = this; if (!cm.state.focused) onFocus(cm); - if (ie && e.keyCode == 27) { e.returnValue = false; } - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (ie && e.keyCode == 27) e.returnValue = false; var code = e.keyCode; // IE does strange things with escape. cm.doc.sel.shift = code == 16 || e.shiftKey; @@ -1944,7 +2139,7 @@ window.CodeMirror = (function() { function onKeyPress(e) { var cm = this; - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; var keyCode = e.keyCode, charCode = e.charCode; if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; @@ -1954,6 +2149,7 @@ window.CodeMirror = (function() { this.doc.mode.electricChars.indexOf(ch) > -1) setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75); if (handleCharBinding(cm, e, ch)) return; + if (ie && !ie_lt9) cm.display.inputHasSelection = null; fastPoll(cm); } @@ -1964,7 +2160,10 @@ window.CodeMirror = (function() { cm.state.focused = true; if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1) cm.display.wrapper.className += " CodeMirror-focused"; - resetInput(cm, true); + if (!cm.curOp) { + resetInput(cm, true); + if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 + } } slowPoll(cm); restartBlink(cm); @@ -1981,12 +2180,17 @@ window.CodeMirror = (function() { var detectingSelectAll; function onContextMenu(cm, e) { + if (signalDOMEvent(cm, e, "contextmenu")) return; var display = cm.display, sel = cm.doc.sel; - if (eventInWidget(display, e)) return; + if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return; var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; if (!pos || opera) return; // Opera is difficult. - if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))) operation(cm, setSelection)(cm.doc, pos, pos); var oldCSS = display.input.style.cssText; @@ -1999,19 +2203,24 @@ window.CodeMirror = (function() { // Adds "Select all" to context menu in FF if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; + function prepareSelectAllHack() { + if (display.input.selectionStart != null) { + var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value); + display.prevInput = "\u200b"; + display.input.selectionStart = 1; display.input.selectionEnd = extval.length; + } + } function rehide() { display.inputDiv.style.position = "relative"; display.input.style.cssText = oldCSS; if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; slowPoll(cm); - // Try to detect the user choosing select-all - if (display.input.selectionStart != null && (!ie || ie_lt9)) { + // Try to detect the user choosing select-all + if (display.input.selectionStart != null) { + if (!ie || ie_lt9) prepareSelectAllHack(); clearTimeout(detectingSelectAll); - var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0; - display.prevInput = " "; - display.input.selectionStart = 1; display.input.selectionEnd = extval.length; - var poll = function(){ + var i = 0, poll = function(){ if (display.prevInput == " " && display.input.selectionStart == 0) operation(cm, commands.selectAll)(cm); else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); @@ -2021,6 +2230,7 @@ window.CodeMirror = (function() { } } + if (ie && !ie_lt9) prepareSelectAllHack(); if (captureMiddleClick) { e_stop(e); var mouseup = function() { @@ -2035,10 +2245,11 @@ window.CodeMirror = (function() { // UPDATING - function changeEnd(change) { + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; return Pos(change.from.line + change.text.length - 1, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); - } + }; // Make sure a position will be valid after the given change. function clipPostChange(doc, change, pos) { @@ -2063,7 +2274,7 @@ window.CodeMirror = (function() { head: clipPostChange(doc, change, hint.head)}; if (hint == "start") return {anchor: change.from, head: change.from}; - + var end = changeEnd(change); if (hint == "around") return {anchor: change.from, head: end}; if (hint == "end") return {anchor: end, head: end}; @@ -2080,21 +2291,21 @@ window.CodeMirror = (function() { return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)}; } - function filterChange(doc, change) { + function filterChange(doc, change, update) { var obj = { canceled: false, from: change.from, to: change.to, text: change.text, origin: change.origin, - update: function(from, to, text, origin) { - if (from) this.from = clipPos(doc, from); - if (to) this.to = clipPos(doc, to); - if (text) this.text = text; - if (origin !== undefined) this.origin = origin; - }, cancel: function() { this.canceled = true; } }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; signal(doc, "beforeChange", doc, obj); if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); @@ -2111,7 +2322,7 @@ window.CodeMirror = (function() { } if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change); + change = filterChange(doc, change, true); if (!change) return; } @@ -2129,6 +2340,7 @@ window.CodeMirror = (function() { } function makeChangeNoReadonly(doc, change, selUpdate) { + if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return; var selAfter = computeSelAfterChange(doc, change, selUpdate); addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); @@ -2145,18 +2357,28 @@ window.CodeMirror = (function() { } function makeChangeFromHistory(doc, type) { + if (doc.cm && doc.cm.state.suppressEdits) return; + var hist = doc.history; var event = (type == "undo" ? hist.done : hist.undone).pop(); if (!event) return; - hist.dirtyCounter += type == "undo" ? -1 : 1; var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter, - anchorAfter: event.anchorBefore, headAfter: event.headBefore}; + anchorAfter: event.anchorBefore, headAfter: event.headBefore, + generation: hist.generation}; (type == "undo" ? hist.undone : hist.done).push(anti); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); for (var i = event.changes.length - 1; i >= 0; --i) { var change = event.changes[i]; change.origin = type; + if (filter && !filterChange(doc, change, false)) { + (type == "undo" ? hist.done : hist.undone).length = 0; + return; + } + anti.changes.push(historyChangeFromChange(doc, change)); var after = i ? computeSelAfterChange(doc, change, null) @@ -2205,6 +2427,8 @@ window.CodeMirror = (function() { text: [change.text[0]], origin: change.origin}; } + change.removed = getBetween(doc, change.from, change.to); + if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter); else updateDoc(doc, change, spans, selAfter); @@ -2224,6 +2448,9 @@ window.CodeMirror = (function() { }); } + if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head)) + cm.curOp.cursorActivity = true; + updateDoc(doc, change, spans, selAfter, estimateHeight(cm)); if (!cm.options.lineWrapping) { @@ -2246,8 +2473,12 @@ window.CodeMirror = (function() { var lendiff = change.text.length - (to.line - from.line) - 1; // Remember that these lines changed, for updating the display regChange(cm, from.line, to.line + 1, lendiff); + if (hasHandler(cm, "change")) { - var changeObj = {from: from, to: to, text: change.text, origin: change.origin}; + var changeObj = {from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin}; if (cm.curOp.textChanged) { for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {} cur.next = changeObj; @@ -2332,6 +2563,7 @@ window.CodeMirror = (function() { var sel = doc.sel; sel.goalColumn = null; + if (bias == null) bias = posLess(head, sel.head) ? -1 : 1; // Skip over atomic spans. if (checkAtomic || !posEq(anchor, sel.anchor)) anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push"); @@ -2346,7 +2578,8 @@ window.CodeMirror = (function() { sel.to = inv ? anchor : head; if (doc.cm) - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = + doc.cm.curOp.cursorActivity = true; signalLater(doc, "cursorActivity", doc); } @@ -2360,16 +2593,20 @@ window.CodeMirror = (function() { var dir = bias || 1; doc.cantEdit = false; search: for (;;) { - var line = getLine(doc, curPos.line), toClear; + var line = getLine(doc, curPos.line); if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { var sp = line.markedSpans[i], m = sp.marker; if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { - if (mayClear && m.clearOnEnter) { - (toClear || (toClear = [])).push(m); - continue; - } else if (!m.atomic) continue; + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; var newPos = m.find()[dir < 0 ? "from" : "to"]; if (posEq(newPos, curPos)) { newPos.ch += dir; @@ -2396,7 +2633,6 @@ window.CodeMirror = (function() { continue search; } } - if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear(); } return curPos; } @@ -2405,7 +2641,7 @@ window.CodeMirror = (function() { // SCROLLING function scrollCursorIntoView(cm) { - var coords = scrollPosIntoView(cm, cm.doc.sel.head); + var coords = scrollPosIntoView(cm, cm.doc.sel.head, null, cm.options.cursorScrollMargin); if (!cm.state.focused) return; var display = cm.display, box = getRect(display.sizer), doScroll = null; if (coords.top + box.top < 0) doScroll = true; @@ -2422,10 +2658,15 @@ window.CodeMirror = (function() { } } - function scrollPosIntoView(cm, pos) { + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; for (;;) { var changed = false, coords = cursorCoords(cm, pos); - var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop); @@ -2446,13 +2687,17 @@ window.CodeMirror = (function() { } function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, pt = paddingTop(display); - y1 += pt; y2 += pt; + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; - var docBottom = cm.doc.height + 2 * pt; - var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; - if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); - else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; @@ -2467,11 +2712,23 @@ window.CodeMirror = (function() { return result; } + function updateScrollPos(cm, left, top) { + cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left, + scrollTop: top == null ? cm.doc.scrollTop : top}; + } + + function addToScrollPos(cm, left, top) { + var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop}); + var scroll = cm.display.scroller; + pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top)); + pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left)); + } + // API UTILITIES function indentLine(cm, n, how, aggressive) { var doc = cm.doc; - if (!how) how = "add"; + if (how == null) how = "add"; if (how == "smart") { if (!cm.doc.mode.indent) how = "prev"; else var state = getStateBefore(cm, n); @@ -2494,6 +2751,8 @@ window.CodeMirror = (function() { indentation = curSpace + cm.options.indentUnit; } else if (how == "subtract") { indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; } indentation = Math.max(0, indentation); @@ -2502,7 +2761,9 @@ window.CodeMirror = (function() { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} if (pos < indentation) indentString += spaceStr(indentation - pos); - if (indentString != curSpaceString) + if (indentString == curSpaceString) + setSelection(cm.doc, Pos(n, curSpaceString.length), Pos(n, curSpaceString.length)); + else replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); line.stateAfter = null; } @@ -2518,7 +2779,7 @@ window.CodeMirror = (function() { } function findPosH(doc, pos, dir, unit, visually) { - var line = pos.line, ch = pos.ch; + var line = pos.line, ch = pos.ch, origDir = dir; var lineObj = getLine(doc, line); var possible = true; function findNextLine() { @@ -2540,16 +2801,24 @@ window.CodeMirror = (function() { if (unit == "char") moveOnce(); else if (unit == "column") moveOnce(true); - else if (unit == "word") { - var sawWord = false; - for (;;) { - if (dir < 0) if (!moveOnce()) break; - if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; - else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} - if (dir > 0) if (!moveOnce()) break; + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur) ? "w" + : !group ? null + : /\s/.test(cur) ? null + : "p"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; } } - var result = skipAtomic(doc, Pos(line, ch), dir, true); + var result = skipAtomic(doc, Pos(line, ch), origDir, true); if (!possible) result.hitSide = true; return result; } @@ -2558,7 +2827,7 @@ window.CodeMirror = (function() { var doc = cm.doc, x = pos.left, y; if (unit == "page") { var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); - y = pos.top + dir * pageSize; + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); } else if (unit == "line") { y = dir > 0 ? pos.bottom + 3 : pos.top - 3; } @@ -2574,11 +2843,11 @@ window.CodeMirror = (function() { function findWordAt(line, pos) { var start = pos.ch, end = pos.ch; if (line) { - if (pos.after === false || end == line.length) --start; else ++end; + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; var startChar = line.charAt(start); - var check = isWordChar(startChar) ? isWordChar : - /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : - function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + var check = isWordChar(startChar) ? isWordChar + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; while (start > 0 && check(line.charAt(start - 1))) --start; while (end < line.length && check(line.charAt(end))) ++end; } @@ -2595,6 +2864,7 @@ window.CodeMirror = (function() { // 'wrap f in an operation, performed on its `this` parameter' CodeMirror.prototype = { + constructor: CodeMirror, focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, setOption: function(option, value) { @@ -2608,13 +2878,13 @@ window.CodeMirror = (function() { getOption: function(option) {return this.options[option];}, getDoc: function() {return this.doc;}, - addKeyMap: function(map) { - this.state.keyMaps.push(map); + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](map); }, removeKeyMap: function(map) { var maps = this.state.keyMaps; for (var i = 0; i < maps.length; ++i) - if ((typeof map == "string" ? maps[i].name : maps[i]) == map) { + if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) { maps.splice(i, 1); return true; } @@ -2630,7 +2900,8 @@ window.CodeMirror = (function() { removeOverlay: operation(null, function(spec) { var overlays = this.state.overlays; for (var i = 0; i < overlays.length; ++i) { - if (overlays[i].modeSpec == spec) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { overlays.splice(i, 1); this.state.modeGen++; regChange(this); @@ -2640,7 +2911,7 @@ window.CodeMirror = (function() { }), indentLine: operation(null, function(n, dir, aggressive) { - if (typeof dir != "string") { + if (typeof dir != "string" && typeof dir != "number") { if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; else dir = dir ? "add" : "subtract"; } @@ -2655,10 +2926,10 @@ window.CodeMirror = (function() { // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). - getTokenAt: function(pos) { + getTokenAt: function(pos, precise) { var doc = this.doc; pos = clipPos(doc, pos); - var state = getStateBefore(this, pos.line), mode = this.doc.mode; + var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode; var line = getLine(doc, pos.line); var stream = new StringStream(line.text, this.options.tabSize); while (stream.pos < pos.ch && !stream.eol()) { @@ -2673,10 +2944,37 @@ window.CodeMirror = (function() { state: state}; }, - getStateAfter: function(line) { + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + if (ch == 0) return styles[2]; + for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else return styles[mid * 2 + 2]; + } + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + if (!helpers.hasOwnProperty(type)) return; + var help = helpers[type], mode = this.getModeAt(pos); + return mode[type] && help[mode[type]] || + mode.helperType && help[mode.helperType] || + help[mode.name]; + }, + + getStateAfter: function(line, precise) { var doc = this.doc; line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); - return getStateBefore(this, line + 1); + return getStateBefore(this, line + 1, precise); }, cursorCoords: function(start, mode) { @@ -2691,14 +2989,26 @@ window.CodeMirror = (function() { return charCoords(this, clipPos(this.doc, pos), mode || "page"); }, - coordsChar: function(coords) { - var off = getRect(this.display.lineSpace); - var scrollY = window.pageYOffset || (document.documentElement || document.body).scrollTop; - var scrollX = window.pageXOffset || (document.documentElement || document.body).scrollLeft; - return coordsChar(this, coords.left - off.left - scrollX, coords.top - off.top - scrollY); + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + var lineObj = getLine(this.doc, line); + return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top + + (end ? lineObj.height : 0); }, defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, setGutterMarker: operation(null, function(line, gutterID, value) { return changeLine(this, line, function(line) { @@ -2725,7 +3035,7 @@ window.CodeMirror = (function() { return changeLine(this, handle, function(line) { var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; if (!line[prop]) line[prop] = cls; - else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false; + else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false; else line[prop] += " " + cls; return true; }); @@ -2738,9 +3048,10 @@ window.CodeMirror = (function() { if (!cur) return false; else if (cls == null) line[prop] = null; else { - var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), ""); - if (upd == cur) return false; - line[prop] = upd || null; + var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)")); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; } return true; }); @@ -2788,7 +3099,7 @@ window.CodeMirror = (function() { if (left + node.offsetWidth > hspace) left = hspace - node.offsetWidth; } - node.style.top = (top + paddingTop(display)) + "px"; + node.style.top = top + "px"; node.style.left = node.style.right = ""; if (horiz == "right") { left = display.sizer.clientWidth - node.offsetWidth; @@ -2851,21 +3162,22 @@ window.CodeMirror = (function() { if (sel.goalColumn != null) pos.left = sel.goalColumn; var target = findPosV(this, pos, dir, unit); - if (unit == "page") - this.display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top; + if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top); extendSelection(this.doc, target, target, dir); sel.goalColumn = pos.left; }), - toggleOverwrite: function() { + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; if (this.state.overwrite = !this.state.overwrite) this.display.cursor.className += " CodeMirror-overwrite"; else this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", ""); }, + hasFocus: function() { return this.state.focused; }, scrollTo: operation(null, function(x, y) { - this.curOp.updateScrollPos = {scrollLeft: x, scrollTop: y}; + updateScrollPos(this, x, y); }), getScrollInfo: function() { var scroller = this.display.scroller, co = scrollerCutOff; @@ -2874,34 +3186,45 @@ window.CodeMirror = (function() { clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; }, - scrollIntoView: function(pos) { - if (typeof pos == "number") pos = Pos(pos, 0); - if (!pos || pos.line != null) { - pos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; - scrollPosIntoView(this, pos); - } else { - scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom); - } - }, + scrollIntoView: operation(null, function(range, margin) { + if (range == null) range = {from: this.doc.sel.head, to: null}; + else if (typeof range == "number") range = {from: Pos(range, 0), to: null}; + else if (range.from == null) range = {from: range, to: null}; + if (!range.to) range.to = range.from; + if (!margin) margin = 0; - setSize: function(width, height) { + var coords = range; + if (range.from.line != null) { + this.curOp.scrollToPos = {from: range.from, to: range.to, margin: margin}; + coords = {from: cursorCoords(this, range.from), + to: cursorCoords(this, range.to)}; + } + var sPos = calculateScrollPos(this, Math.min(coords.from.left, coords.to.left), + Math.min(coords.from.top, coords.to.top) - margin, + Math.max(coords.from.right, coords.to.right), + Math.max(coords.from.bottom, coords.to.bottom) + margin); + updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); + }), + + setSize: operation(null, function(width, height) { function interpret(val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } if (width != null) this.display.wrapper.style.width = interpret(width); if (height != null) this.display.wrapper.style.height = interpret(height); - this.refresh(); - }, - - on: function(type, f) {on(this, type, f);}, - off: function(type, f) {off(this, type, f);}, + if (this.options.lineWrapping) + this.display.measureLineCache.length = this.display.measureLineCachePos = 0; + this.curOp.forceUpdate = true; + }), operation: function(f){return runInOp(this, f);}, refresh: operation(null, function() { + var badHeight = this.display.cachedTextHeight == null; clearCaches(this); - this.curOp.updateScrollPos = {scrollTop: this.doc.scrollTop, scrollLeft: this.doc.scrollLeft}; + updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop); regChange(this); + if (badHeight) estimateLineHeights(this); }), swapDoc: operation(null, function(doc) { @@ -2909,7 +3232,9 @@ window.CodeMirror = (function() { old.cm = null; attachDoc(this, doc); clearCaches(this); - this.curOp.updateScrollPos = {scrollTop: doc.scrollTop, scrollLeft: doc.scrollLeft}; + resetInput(this, true); + updateScrollPos(this, doc.scrollLeft, doc.scrollTop); + signalLater(this, "swapDoc", this, old); return old; }), @@ -2918,6 +3243,7 @@ window.CodeMirror = (function() { getScrollerElement: function(){return this.display.scroller;}, getGutterElement: function(){return this.display.gutters;} }; + eventMixin(CodeMirror); // OPTION DEFAULTS @@ -2974,6 +3300,7 @@ window.CodeMirror = (function() { cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; cm.refresh(); }, true); + option("coverGutterNextToScrollbar", false, updateScrollbars, true); option("lineNumbers", false, function(cm) { setGuttersForLineNumbers(cm.options); guttersChanged(cm); @@ -2981,7 +3308,9 @@ window.CodeMirror = (function() { option("firstLineNumber", 1, guttersChanged, true); option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); option("showCursorWhenSelecting", false, updateSelection, true); - + + option("resetSelectionOnContextMenu", true); + option("readOnly", false, function(cm, val) { if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} else if (!val) resetInput(cm, true); @@ -2989,13 +3318,20 @@ window.CodeMirror = (function() { option("dragDrop", true); option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); option("cursorHeight", 1); option("workTime", 100); option("workDelay", 100); option("flattenSpans", true); option("pollInterval", 100); option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 500); option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); + option("crudeMeasuringFrom", 10000); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; + }); option("tabindex", null, function(cm, val) { cm.display.input.tabIndex = val || ""; @@ -3021,16 +3357,21 @@ window.CodeMirror = (function() { }; CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { spec = mimeModes[spec]; - else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { return CodeMirror.resolveMode("application/xml"); + } if (typeof spec == "string") return {name: spec}; else return spec || {name: "null"}; }; CodeMirror.getMode = function(options, spec) { - spec = CodeMirror.resolveMode(spec); + var spec = CodeMirror.resolveMode(spec); var mfactory = modes[spec.name]; if (!mfactory) return CodeMirror.getMode(options, "text/plain"); var modeObj = mfactory(options, spec); @@ -3043,6 +3384,7 @@ window.CodeMirror = (function() { } } modeObj.name = spec.name; + return modeObj; }; @@ -3062,12 +3404,24 @@ window.CodeMirror = (function() { CodeMirror.defineExtension = function(name, func) { CodeMirror.prototype[name] = func; }; - + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; CodeMirror.defineOption = option; var initHooks = []; CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {}; + helpers[type][name] = value; + }; + + // UTILITIES + + CodeMirror.isWordChar = isWordChar; + // MODE STATE HANDLING // Utility functions for working with state. Exported because modes @@ -3093,6 +3447,7 @@ window.CodeMirror = (function() { CodeMirror.innerMode = function(mode, state) { while (mode.innerMode) { var info = mode.innerMode(state); + if (!info || info.mode == mode) break; state = info.state; mode = info.mode; } @@ -3113,6 +3468,10 @@ window.CodeMirror = (function() { var l = cm.getCursor().line; cm.replaceRange("", Pos(l, 0), Pos(l), "+delete"); }, + delLineLeft: function(cm) { + var cur = cm.getCursor(); + cm.replaceRange("", Pos(cur.line, 0), cur, "+delete"); + }, undo: function(cm) {cm.undo();}, redo: function(cm) {cm.redo();}, goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, @@ -3133,6 +3492,14 @@ window.CodeMirror = (function() { goLineEnd: function(cm) { cm.extendSelection(lineEnd(cm, cm.getCursor().line)); }, + goLineRight: function(cm) { + var top = cm.charCoords(cm.getCursor(), "div").top + 5; + cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")); + }, + goLineLeft: function(cm) { + var top = cm.charCoords(cm.getCursor(), "div").top + 5; + cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div")); + }, goLineUp: function(cm) {cm.moveV(-1, "line");}, goLineDown: function(cm) {cm.moveV(1, "line");}, goPageUp: function(cm) {cm.moveV(-1, "page");}, @@ -3142,11 +3509,15 @@ window.CodeMirror = (function() { goColumnLeft: function(cm) {cm.moveH(-1, "column");}, goColumnRight: function(cm) {cm.moveH(1, "column");}, goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, goWordRight: function(cm) {cm.moveH(1, "word");}, delCharBefore: function(cm) {cm.deleteH(-1, "char");}, delCharAfter: function(cm) {cm.deleteH(1, "char");}, delWordBefore: function(cm) {cm.deleteH(-1, "word");}, delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, indentAuto: function(cm) {cm.indentSelection("smart");}, indentMore: function(cm) {cm.indentSelection("add");}, indentLess: function(cm) {cm.indentSelection("subtract");}, @@ -3176,7 +3547,8 @@ window.CodeMirror = (function() { keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" }; // Note that the save and find-related commands aren't defined by @@ -3184,19 +3556,19 @@ window.CodeMirror = (function() { keyMap.pcDefault = { "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", - "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", - "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", fallthrough: "basic" }; keyMap.macDefault = { "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", - "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore", - "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", fallthrough: ["basic", "emacsy"] }; keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; @@ -3235,7 +3607,7 @@ window.CodeMirror = (function() { for (var i = 0; i < maps.length; ++i) { var done = lookup(maps[i]); - if (done) return done; + if (done) return done != "stop"; } } function isModifierKey(event) { @@ -3243,6 +3615,7 @@ window.CodeMirror = (function() { return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; } function keyName(event, noShift) { + if (opera && event.keyCode == 34 && event["char"]) return false; var name = keyNames[event.keyCode]; if (name == null || event.altGraphKey) return false; if (event.altKey) name = "Alt-" + name; @@ -3262,6 +3635,8 @@ window.CodeMirror = (function() { options.value = textarea.value; if (!options.tabindex && textarea.tabindex) options.tabindex = textarea.tabindex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; // Set autofocus to true if this textarea is focused, or if it has // autofocus and no other element is focused. if (options.autofocus == null) { @@ -3274,17 +3649,19 @@ window.CodeMirror = (function() { function save() {textarea.value = cm.getValue();} if (textarea.form) { - // Deplorable hack to make the submit method do the right thing. on(textarea.form, "submit", save); - var form = textarea.form, realSubmit = form.submit; - try { - var wrappedSubmit = form.submit = function() { - save(); - form.submit = realSubmit; - form.submit(); - form.submit = wrappedSubmit; - }; - } catch(e) {} + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } } textarea.style.display = "none"; @@ -3316,6 +3693,7 @@ window.CodeMirror = (function() { this.pos = this.start = 0; this.string = string; this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; } StringStream.prototype = { @@ -3348,12 +3726,19 @@ window.CodeMirror = (function() { if (found > -1) {this.pos = found; return true;} }, backUp: function(n) {this.pos -= n;}, - column: function() {return countColumn(this.string, this.start, this.tabSize);}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue; + }, indentation: function() {return countColumn(this.string, null, this.tabSize);}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { if (consume !== false) this.pos += pattern.length; return true; } @@ -3376,11 +3761,16 @@ window.CodeMirror = (function() { this.doc = doc; } CodeMirror.TextMarker = TextMarker; + eventMixin(TextMarker); TextMarker.prototype.clear = function() { if (this.explicitlyCleared) return; var cm = this.doc.cm, withOp = cm && !cm.curOp; if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } var min = null, max = null; for (var i = 0; i < this.lines.length; ++i) { var line = this.lines[i]; @@ -3404,12 +3794,11 @@ window.CodeMirror = (function() { if (min != null && cm) regChange(cm, min, max + 1); this.lines.length = 0; this.explicitlyCleared = true; - if (this.collapsed && this.doc.cantEdit) { + if (this.atomic && this.doc.cantEdit) { this.doc.cantEdit = false; if (cm) reCheckSelection(cm); } if (withOp) endOperation(cm); - signalLater(this, "clear"); }; TextMarker.prototype.find = function() { @@ -3427,16 +3816,21 @@ window.CodeMirror = (function() { return from && {from: from, to: to}; }; - TextMarker.prototype.getOptions = function(copyWidget) { - var repl = this.replacedWith; - return {className: this.className, - inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight, - atomic: this.atomic, - collapsed: this.collapsed, - clearOnEnter: this.clearOnEnter, - replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl, - readOnly: this.readOnly, - startStyle: this.startStyle, endStyle: this.endStyle}; + TextMarker.prototype.changed = function() { + var pos = this.find(), cm = this.doc.cm; + if (!pos || !cm) return; + if (this.type != "bookmark") pos = pos.from; + var line = getLine(this.doc, pos.line); + clearCachedMeasurement(cm, line); + if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) { + for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) { + if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight); + break; + } + runInOp(cm, function() { + cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true; + }); + } }; TextMarker.prototype.attachLine = function(line) { @@ -3460,14 +3854,21 @@ window.CodeMirror = (function() { if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); var marker = new TextMarker(doc, type); - if (type == "range" && !posLess(from, to)) return marker; + if (posLess(to, from) || posEq(from, to) && type == "range" && + !(options.inclusiveLeft && options.inclusiveRight)) + return marker; if (options) copyObj(options, marker); if (marker.replacedWith) { marker.collapsed = true; marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true; } if (marker.collapsed) sawCollapsedSpans = true; + if (marker.addToHistory) + addToHistory(doc, {from: from, to: to, origin: "markText"}, + {head: doc.sel.head, anchor: doc.sel.anchor}, NaN); + var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine; doc.iter(curLine, to.line + 1, function(line) { if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine) @@ -3488,6 +3889,8 @@ window.CodeMirror = (function() { if (lineIsHidden(doc, line)) updateLineHeight(line, 0); }); + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + if (marker.readOnly) { sawReadOnlySpans = true; if (doc.history.done.length || doc.history.undone.length) @@ -3501,7 +3904,7 @@ window.CodeMirror = (function() { } if (cm) { if (updateMaxLine) cm.curOp.updateMaxLine = true; - if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed) + if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed) regChange(cm, from.line, to.line + 1); if (marker.atomic) reCheckSelection(cm); } @@ -3519,6 +3922,7 @@ window.CodeMirror = (function() { } } CodeMirror.SharedTextMarker = SharedTextMarker; + eventMixin(SharedTextMarker); SharedTextMarker.prototype.clear = function() { if (this.explicitlyCleared) return; @@ -3530,17 +3934,14 @@ window.CodeMirror = (function() { SharedTextMarker.prototype.find = function() { return this.primary.find(); }; - SharedTextMarker.prototype.getOptions = function(copyWidget) { - var inner = this.primary.getOptions(copyWidget); - inner.shared = true; - return inner; - }; function markTextShared(doc, from, to, options, type) { options = copyObj(options); options.shared = false; var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.replacedWith; linkedDocs(doc, function(doc) { + if (widget) options.replacedWith = widget.cloneNode(true); markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); for (var i = 0; i < doc.linked.length; ++i) if (doc.linked[i].isParent) return; @@ -3571,7 +3972,9 @@ window.CodeMirror = (function() { if (old) for (var i = 0, nw; i < old.length; ++i) { var span = old[i], marker = span.marker; var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) { + if (startsBefore || + (marker.inclusiveLeft && marker.inclusiveRight || marker.type == "bookmark") && + span.from == startCh && (!isInsert || !span.marker.insertLeft)) { var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); (nw || (nw = [])).push({from: span.from, to: endsAfter ? null : span.to, @@ -3635,6 +4038,13 @@ window.CodeMirror = (function() { } } } + if (sameLine && first) { + // Make sure we didn't create any zero-length spans + for (var i = 0; i < first.length; ++i) + if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark") + first.splice(i--, 1); + if (!first.length) first = null; + } var newMarkers = [first]; if (!sameLine) { @@ -3729,6 +4139,7 @@ window.CodeMirror = (function() { sp = sps[i]; if (!sp.marker.collapsed) continue; if (sp.from == null) return true; + if (sp.marker.replacedWith) continue; if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) return true; } @@ -3742,7 +4153,7 @@ window.CodeMirror = (function() { return true; for (var sp, i = 0; i < line.markedSpans.length; ++i) { sp = line.markedSpans[i]; - if (sp.marker.collapsed && sp.from == span.to && + if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && lineIsHiddenInner(doc, line, sp)) return true; } @@ -3766,11 +4177,12 @@ window.CodeMirror = (function() { // LINE WIDGETS var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { - for (var opt in options) if (options.hasOwnProperty(opt)) + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) this[opt] = options[opt]; this.cm = cm; this.node = node; }; + eventMixin(LineWidget); function widgetOperation(f) { return function() { var withOp = !this.cm.curOp; @@ -3785,7 +4197,9 @@ window.CodeMirror = (function() { if (no == null || !ws) return; for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); if (!ws.length) this.line.widgets = null; + var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop; updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); + if (aboveVisible) addToScrollPos(this.cm, 0, -this.height); regChange(this.cm, no, no + 1); }); LineWidget.prototype.changed = widgetOperation(function() { @@ -3809,14 +4223,14 @@ window.CodeMirror = (function() { var widget = new LineWidget(cm, node, options); if (widget.noHScroll) cm.display.alignWidgets = true; changeLine(cm, handle, function(line) { - (line.widgets || (line.widgets = [])).push(widget); + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); widget.line = line; if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) { - var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop; + var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop; updateLineHeight(line, line.height + widgetHeight(widget)); - if (aboveVisible) - cm.curOp.updateScrollPos = {scrollTop: cm.doc.scrollTop + widget.height, - scrollLeft: cm.doc.scrollLeft}; + if (aboveVisible) addToScrollPos(cm, 0, widget.height); } return true; }); @@ -3827,12 +4241,12 @@ window.CodeMirror = (function() { // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function makeLine(text, markedSpans, estimateHeight) { - var line = {text: text}; - attachMarkedSpans(line, markedSpans); - line.height = estimateHeight ? estimateHeight(line) : 1; - return line; - } + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); function updateLine(line, text, markedSpans, estimateHeight) { line.text = text; @@ -3843,7 +4257,6 @@ window.CodeMirror = (function() { attachMarkedSpans(line, markedSpans); var estHeight = estimateHeight ? estimateHeight(line) : 1; if (estHeight != line.height) updateLineHeight(line, estHeight); - signalLater(line, "change"); } function cleanUpLine(line) { @@ -3855,26 +4268,31 @@ window.CodeMirror = (function() { // array, which contains alternating fragments of text and CSS // classes. function runMode(cm, text, mode, state, f) { - var flattenSpans = cm.options.flattenSpans; - var curText = "", curStyle = null; - var stream = new StringStream(text, cm.options.tabSize); + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; if (text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { - var style = mode.token(stream, state); - if (stream.pos > 5000) { + if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false; - // Webkit seems to refuse to render text nodes longer than 57444 characters - stream.pos = Math.min(text.length, stream.start + 50000); + stream.pos = text.length; style = null; + } else { + style = mode.token(stream, state); } - var substr = stream.current(); - stream.start = stream.pos; if (!flattenSpans || curStyle != style) { - if (curText) f(curText, curStyle); - curText = substr; curStyle = style; - } else curText = curText + substr; + if (curStart < stream.start) f(stream.start, curStyle); + curStart = stream.start; curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; } - if (curText) f(curText, curStyle); } function highlightLine(cm, line, state) { @@ -3882,27 +4300,24 @@ window.CodeMirror = (function() { // mode/overlays that it is based on (for easy invalidation). var st = [cm.state.modeGen]; // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);}); + runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);}); // Run overlays, adjust style array. for (var o = 0; o < cm.state.overlays.length; ++o) { - var overlay = cm.state.overlays[o], i = 1; - runMode(cm, line.text, overlay.mode, true, function(txt, style) { - var start = i, len = txt.length; + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; // Ensure there's a token end at the current position, and that i points at it - while (len) { - var cur = st[i], len_ = cur.length; - if (len_ <= len) { - len -= len_; - } else { - st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len)); - len = 0; - } + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); i += 2; + at = Math.min(end, i_end); } if (!style) return; if (overlay.opaque) { - st.splice(start, i - start, txt, style); + st.splice(start, i - start, end, style); i = start + 2; } else { for (; start < i; start += 2) { @@ -3928,49 +4343,55 @@ window.CodeMirror = (function() { var mode = cm.doc.mode; var stream = new StringStream(line.text, cm.options.tabSize); if (line.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol() && stream.pos <= 5000) { + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { mode.token(stream, state); stream.start = stream.pos; } } var styleToClassCache = {}; - function styleToClass(style) { + function interpretTokenStyle(style, builder) { if (!style) return null; + for (;;) { + var lineClass = style.match(/(?:^|\s)line-(background-)?(\S+)/); + if (!lineClass) break; + style = style.slice(0, lineClass.index) + style.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (builder[prop] == null) + builder[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop])) + builder[prop] += " " + lineClass[2]; + } return styleToClassCache[style] || (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); } - function lineContent(cm, realLine, measure) { - var merged, line = realLine, lineBefore, sawBefore, simple = true; - while (merged = collapsedSpanAtStart(line)) { - simple = false; + function buildLineContent(cm, realLine, measure, copyWidgets) { + var merged, line = realLine, empty = true; + while (merged = collapsedSpanAtStart(line)) line = getLine(cm.doc, merged.find().from.line); - if (!lineBefore) lineBefore = line; - } - var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure, - measure: null, addedOne: false, cm: cm}; - if (line.textClass) builder.pre.className = line.textClass; + var builder = {pre: elt("pre"), col: 0, pos: 0, + measure: null, measuredSomething: false, cm: cm, + copyWidgets: copyWidgets}; do { + if (line.text) empty = false; builder.measure = line == realLine && measure; builder.pos = 0; builder.addToken = builder.measure ? buildTokenMeasure : buildToken; - if (measure && sawBefore && line != realLine && !builder.addedOne) { - measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); - builder.addedOne = true; - } + if ((ie || webkit) && cm.getOption("lineWrapping")) + builder.addToken = buildTokenSplitSpaces(builder.addToken); var next = insertLineContent(line, builder, getLineStyles(cm, line)); - sawBefore = line == lineBefore; - if (next) { - line = getLine(cm.doc, next.to.line); - simple = false; + if (measure && line == realLine && !builder.measuredSomething) { + measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); + builder.measuredSomething = true; } + if (next) line = getLine(cm.doc, next.to.line); } while (next); - if (measure && !builder.addedOne) - measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); + if (measure && !builder.measuredSomething && !measure[0]) + measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine)) builder.pre.appendChild(document.createTextNode("\u00a0")); @@ -3989,11 +4410,15 @@ window.CodeMirror = (function() { } } - return builder.pre; + var textClass = builder.textClass ? builder.textClass + " " + (realLine.textClass || "") : realLine.textClass; + if (textClass) builder.pre.className = textClass; + + signal(cm, "renderLine", cm, realLine, builder.pre); + return builder; } - var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; - function buildToken(builder, text, style, startStyle, endStyle) { + var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; + function buildToken(builder, text, style, startStyle, endStyle, title) { if (!text) return; if (!tokenSpecialChars.test(text)) { builder.col += text.length; @@ -4026,36 +4451,69 @@ window.CodeMirror = (function() { var fullStyle = style || ""; if (startStyle) fullStyle += startStyle; if (endStyle) fullStyle += endStyle; - return builder.pre.appendChild(elt("span", [content], fullStyle)); + var token = elt("span", [content], fullStyle); + if (title) token.title = title; + return builder.pre.appendChild(token); } builder.pre.appendChild(content); } function buildTokenMeasure(builder, text, style, startStyle, endStyle) { + var wrapping = builder.cm.options.lineWrapping; for (var i = 0; i < text.length; ++i) { var ch = text.charAt(i), start = i == 0; if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { ch = text.slice(i, i + 2); ++i; - } else if (i && builder.cm.options.lineWrapping && - spanAffectsWrapping.test(text.slice(i - 1, i + 1))) { + } else if (i && wrapping && spanAffectsWrapping(text, i)) { builder.pre.appendChild(elt("wbr")); } - builder.measure[builder.pos] = + var old = builder.measure[builder.pos]; + var span = builder.measure[builder.pos] = buildToken(builder, ch, style, start && startStyle, i == text.length - 1 && endStyle); + if (old) span.leftSide = old.leftSide || old; + // In IE single-space nodes wrap differently than spaces + // embedded in larger text nodes, except when set to + // white-space: normal (issue #1268). + if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && + i < text.length - 1 && !/\s/.test(text.charAt(i + 1))) + span.style.whiteSpace = "normal"; builder.pos += ch.length; } - if (text.length) builder.addedOne = true; + if (text.length) builder.measuredSomething = true; } - function buildCollapsedSpan(builder, size, widget) { + function buildTokenSplitSpaces(inner) { + function split(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + return function(builder, text, style, startStyle, endStyle, title) { + return inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title); + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.replacedWith; if (widget) { - if (!builder.display) widget = widget.cloneNode(true); + if (builder.copyWidgets) widget = widget.cloneNode(true); builder.pre.appendChild(widget); - if (builder.measure && size) { - builder.measure[builder.pos] = widget; - builder.addedOne = true; + if (builder.measure) { + if (size) { + builder.measure[builder.pos] = widget; + } else { + var elt = zeroWidthElement(builder.cm.display.measure); + if (marker.type == "bookmark" && !marker.insertLeft) + builder.measure[builder.pos] = builder.pre.appendChild(elt); + else if (builder.measure[builder.pos]) + return; + else + builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget); + } + builder.measuredSomething = true; } } builder.pos += size; @@ -4064,21 +4522,20 @@ window.CodeMirror = (function() { // Outputs a number of spans to make up a line, taking highlighting // and marked text into account. function insertLineContent(line, builder, styles) { - var spans = line.markedSpans; + var spans = line.markedSpans, allText = line.text, at = 0; if (!spans) { for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, styles[i], styleToClass(styles[i+1])); + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder)); return; } - var allText = line.text, len = allText.length; - var pos = 0, i = 1, text = "", style; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; + var len = allText.length, pos = 0, i = 1, text = "", style; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; for (;;) { if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = ""; + spanStyle = spanEndStyle = spanStartStyle = title = ""; collapsed = null; nextChange = Infinity; - var foundBookmark = null; + var foundBookmarks = []; for (var j = 0; j < spans.length; ++j) { var sp = spans[j], m = sp.marker; if (sp.from <= pos && (sp.to == null || sp.to > pos)) { @@ -4086,20 +4543,21 @@ window.CodeMirror = (function() { if (m.className) spanStyle += " " + m.className; if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; - if (m.collapsed && (!collapsed || collapsed.marker.width < m.width)) + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || collapsed.marker.size < m.size)) collapsed = sp; } else if (sp.from > pos && nextChange > sp.from) { nextChange = sp.from; } - if (m.type == "bookmark" && sp.from == pos && m.replacedWith) - foundBookmark = m.replacedWith; + if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m); } if (collapsed && (collapsed.from || 0) == pos) { buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, - collapsed.from != null && collapsed.marker.replacedWith); + collapsed.marker, collapsed.from == null); if (collapsed.to == null) return collapsed.marker.find(); } - if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); } if (pos >= len) break; @@ -4110,13 +4568,14 @@ window.CodeMirror = (function() { if (!collapsed) { var tokenText = end > upto ? text.slice(0, upto - pos) : text; builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : ""); + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title); } if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; spanStartStyle = ""; } - text = styles[i++]; style = styleToClass(styles[i++]); + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder); } } } @@ -4125,6 +4584,10 @@ window.CodeMirror = (function() { function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) { function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } var from = change.from, to = change.to, text = change.text; var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); @@ -4135,30 +4598,28 @@ window.CodeMirror = (function() { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. for (var i = 0, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); - updateLine(lastLine, lastLine.text, lastSpans, estimateHeight); + added.push(new Line(text[i], spansFor(i), estimateHeight)); + update(lastLine, lastLine.text, lastSpans); if (nlines) doc.remove(from.line, nlines); if (added.length) doc.insert(from.line, added); } else if (firstLine == lastLine) { if (text.length == 1) { - updateLine(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), - lastSpans, estimateHeight); + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); } else { for (var added = [], i = 1, e = text.length - 1; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); - added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); - updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight); + added.push(new Line(text[i], spansFor(i), estimateHeight)); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); doc.insert(from.line + 1, added); } } else if (text.length == 1) { - updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), - spansFor(0), estimateHeight); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); doc.remove(from.line + 1, nlines); } else { - updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight); - updateLine(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans, estimateHeight); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); for (var i = 1, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); + added.push(new Line(text[i], spansFor(i), estimateHeight)); if (nlines > 1) doc.remove(from.line + 1, nlines - 1); doc.insert(from.line + 1, added); } @@ -4300,12 +4761,13 @@ window.CodeMirror = (function() { var Doc = CodeMirror.Doc = function(text, mode, firstLine) { if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); if (firstLine == null) firstLine = 0; - - BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]); + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); this.first = firstLine; this.scrollTop = this.scrollLeft = 0; this.cantEdit = false; this.history = makeHistory(); + this.cleanGeneration = 1; this.frontier = firstLine; var start = Pos(firstLine, 0); this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null}; @@ -4317,6 +4779,7 @@ window.CodeMirror = (function() { }; Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, iter: function(from, to, op) { if (op) this.iterN(from - this.first, to - from, op); else this.iterN(this.first, this.first + this.size, from); @@ -4357,13 +4820,18 @@ window.CodeMirror = (function() { replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line))); }, removeLine: function(line) { - if (isLine(this, line)) - replaceRange(this, "", Pos(line, 0), clipPos(this, Pos(line + 1, 0))); + if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line))); + else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0))); }, getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, getLineNumber: function(line) {return lineNo(line);}, + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(this, line); + }, + lineCount: function() {return this.size;}, firstLine: function() {return this.first;}, lastLine: function() {return this.first + this.size - 1;}, @@ -4385,11 +4853,11 @@ window.CodeMirror = (function() { if (extend) extendSelection(this, pos); else setSelection(this, pos, pos); }), - setSelection: docOperation(function(anchor, head) { - setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor)); + setSelection: docOperation(function(anchor, head, bias) { + setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias); }), - extendSelection: docOperation(function(from, to) { - extendSelection(this, clipPos(this, from), to && clipPos(this, to)); + extendSelection: docOperation(function(from, to, bias) { + extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias); }), getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);}, @@ -4405,20 +4873,25 @@ window.CodeMirror = (function() { var hist = this.history; return {undo: hist.done.length, redo: hist.undone.length}; }, - clearHistory: function() {this.history = makeHistory();}, + clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);}, markClean: function() { - this.history.dirtyCounter = 0; - this.history.lastOp = this.history.lastOrigin = null; + this.cleanGeneration = this.changeGeneration(); }, - isClean: function () {return this.history.dirtyCounter == 0;}, - + changeGeneration: function() { + this.history.lastOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + getHistory: function() { return {done: copyHistoryArray(this.history.done), undone: copyHistoryArray(this.history.undone)}; }, setHistory: function(histData) { - var hist = this.history = makeHistory(); + var hist = this.history = makeHistory(this.history.maxGeneration); hist.done = histData.done.slice(0); hist.undone = histData.undone.slice(0); }, @@ -4529,6 +5002,8 @@ window.CodeMirror = (function() { return function() {return method.apply(this.doc, arguments);}; })(Doc.prototype[prop]); + eventMixin(Doc); + function linkedDocs(doc, f, sharedHistOnly) { function propagate(doc, skip, sharedHist) { if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { @@ -4648,7 +5123,7 @@ window.CodeMirror = (function() { // HISTORY - function makeHistory() { + function makeHistory(startGen) { return { // Arrays of history events. Doing something adds an event to // done and clears undo. Undoing moves events from done to @@ -4658,7 +5133,7 @@ window.CodeMirror = (function() { // event lastTime: 0, lastOp: null, lastOrigin: null, // Used by the isClean() method - dirtyCounter: 0 + generation: startGen || 1, maxGeneration: startGen || 1 }; } @@ -4672,7 +5147,8 @@ window.CodeMirror = (function() { } function historyChangeFromChange(doc, change) { - var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + var from = { line: change.from.line, ch: change.from.ch }; + var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); return histChange; @@ -4686,7 +5162,8 @@ window.CodeMirror = (function() { if (cur && (hist.lastOp == opId || hist.lastOrigin == change.origin && change.origin && - ((change.origin.charAt(0) == "+" && hist.lastTime > time - 600) || change.origin.charAt(0) == "*"))) { + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*"))) { // Merge this change into the last event var last = lst(cur.changes); if (posEq(change.from, change.to) && posEq(change.from, last.to)) { @@ -4701,17 +5178,13 @@ window.CodeMirror = (function() { } else { // Can not be merged, start a new event. cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation, anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, anchorAfter: selAfter.anchor, headAfter: selAfter.head}; hist.done.push(cur); + hist.generation = ++hist.maxGeneration; while (hist.done.length > hist.undoDepth) hist.done.shift(); - if (hist.dirtyCounter < 0) - // The user has made a change after undoing past the last clean state. - // We can never get back to a clean state now until markClean() is called. - hist.dirtyCounter = NaN; - else - hist.dirtyCounter++; } hist.lastTime = time; hist.lastOp = opId; @@ -4826,6 +5299,9 @@ window.CodeMirror = (function() { if (e.stopPropagation) e.stopPropagation(); else e.cancelBubble = true; } + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} CodeMirror.e_stop = e_stop; CodeMirror.e_preventDefault = e_preventDefault; @@ -4892,6 +5368,11 @@ window.CodeMirror = (function() { delayedCallbacks.push(bnd(arr[i])); } + function signalDOMEvent(cm, e, override) { + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + function fireDelayed() { --delayedCallbackDepth; var delayed = delayedCallbacks; @@ -4906,6 +5387,11 @@ window.CodeMirror = (function() { CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + // MISC UTILITIES // Number of pixels added to scroller and sizer to hide scrollbar @@ -4920,12 +5406,12 @@ window.CodeMirror = (function() { // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. - function countColumn(string, end, tabSize) { + function countColumn(string, end, tabSize, startIndex, startValue) { if (end == null) { end = string.search(/[^\s\u00a0]/); if (end == -1) end = string.length; } - for (var i = 0, n = 0; i < end; ++i) { + for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) { if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); else ++n; } @@ -4946,7 +5432,11 @@ window.CodeMirror = (function() { if (ios) { // Mobile Safari apparently has a bug where select() is broken. node.selectionStart = 0; node.selectionEnd = node.value.length; - } else node.select(); + } else { + // Suppress mysterious IE10 errors + try { node.select(); } + catch(_e) {} + } } function indexOf(collection, elt) { @@ -4980,7 +5470,7 @@ window.CodeMirror = (function() { return function(){return f.apply(null, args);}; } - var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; + var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; function isWordChar(ch) { return /\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); @@ -5005,9 +5495,8 @@ window.CodeMirror = (function() { } function removeChildren(e) { - // IE will break all parent-child relations in subnodes when setting innerHTML - if (!ie) e.innerHTML = ""; - else while (e.firstChild) e.removeChild(e.firstChild); + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); return e; } @@ -5042,13 +5531,31 @@ window.CodeMirror = (function() { // word wrapping between certain characters *only* if a new inline // element is started between them. This makes it hard to reliably // measure the position of things, since that requires inserting an - // extra span. This terribly fragile set of regexps matches the + // extra span. This terribly fragile set of tests matches the // character combinations that suffer from this phenomenon on the // various browsers. - var spanAffectsWrapping = /^$/; // Won't match any two-character string - if (gecko) spanAffectsWrapping = /$'/; - else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; - else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; + function spanAffectsWrapping() { return false; } + if (gecko) // Only for "$'" + spanAffectsWrapping = function(str, i) { + return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39; + }; + else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1)); + }; + else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + var code = str.charCodeAt(i - 1); + return code >= 8208 && code <= 8212; + }; + else if (webkit) + spanAffectsWrapping = function(str, i) { + if (i > 1 && str.charCodeAt(i - 1) == 45) { + if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; + if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false; + } + return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); + }; var knownScrollbarWidth; function scrollbarWidth(measure) { @@ -5133,11 +5640,15 @@ window.CodeMirror = (function() { function iterateBidiSections(order, from, to, f) { if (!order) return f(from, to, "ltr"); + var found = false; for (var i = 0; i < order.length; ++i) { var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) + if (part.from < to && part.to > from || from == to && part.to == from) { f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } } + if (!found) f(from, to, "ltr"); } function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } @@ -5167,6 +5678,40 @@ window.CodeMirror = (function() { return Pos(lineN, ch); } + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; } + if (cur.from == pos || cur.to == pos) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + bidiOther = found; + return i; + } else { + bidiOther = i; + return found; + } + } + } + bidiOther = null; + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); + return pos; + } + // This is somewhat involved. It is needed in order to move // 'visually' through bi-directional text -- i.e., pressing left // should make the cursor go left, even when in RTL text. The @@ -5176,37 +5721,24 @@ window.CodeMirror = (function() { function moveVisually(line, start, dir, byUnit) { var bidi = getOrder(line); if (!bidi) return moveLogically(line, start, dir, byUnit); - var moveOneUnit = byUnit ? function(pos, dir) { - do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); - return pos; - } : function(pos, dir) { return pos + dir; }; - var linedir = bidi[0].level; - for (var i = 0; i < bidi.length; ++i) { - var part = bidi[i], sticky = part.level % 2 == linedir; - if ((part.from < start && part.to > start) || - (sticky && (part.from == start || part.to == start))) break; - } - var target = moveOneUnit(start, part.level % 2 ? -dir : dir); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); - while (target != null) { - if (part.level % 2 == linedir) { - if (target < part.from || target > part.to) { - part = bidi[i += dir]; - target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1)); - } else break; + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; } else { - if (target == bidiLeft(part)) { - part = bidi[--i]; - target = part && bidiRight(part); - } else if (target == bidiRight(part)) { - part = bidi[++i]; - target = part && bidiLeft(part); - } else break; + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); } } - - return target < 0 || target > line.text.length ? null : target; } function moveLogically(line, start, dir, byUnit) { @@ -5378,7 +5910,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.1"; + CodeMirror.version = "3.19.1"; return CodeMirror; })(); diff --git a/plugins/tiddlywiki/codemirror/styles.tid b/plugins/tiddlywiki/codemirror/styles.tid index 406492539..ec9d41458 100644 --- a/plugins/tiddlywiki/codemirror/styles.tid +++ b/plugins/tiddlywiki/codemirror/styles.tid @@ -5,6 +5,7 @@ tags: [[$:/tags/stylesheet]] .CodeMirror { height: auto; + border: 1px solid #ddd; } .CodeMirror-scroll { From 7dfea81cfaad3863c9052e9a0bb9da52371cd9d6 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 09:50:30 +0100 Subject: [PATCH 078/183] Add a demo edition for CodeMirror --- editions/codemirrordemo/tiddlers/DefaultTiddlers.tid | 4 ++++ .../codemirrordemo/tiddlers/Draft of LoremIpsum.tid | 9 +++++++++ editions/codemirrordemo/tiddlers/HelloThere.tid | 9 +++++++++ editions/codemirrordemo/tiddlers/LoremIpsum.tid | 7 +++++++ editions/codemirrordemo/tiddlers/SiteSubtitle.tid | 3 +++ editions/codemirrordemo/tiddlers/SiteTitle.tid | 3 +++ editions/codemirrordemo/tiddlywiki.info | 12 ++++++++++++ nbld.sh | 8 ++++++++ 8 files changed, 55 insertions(+) create mode 100644 editions/codemirrordemo/tiddlers/DefaultTiddlers.tid create mode 100644 editions/codemirrordemo/tiddlers/Draft of LoremIpsum.tid create mode 100644 editions/codemirrordemo/tiddlers/HelloThere.tid create mode 100644 editions/codemirrordemo/tiddlers/LoremIpsum.tid create mode 100644 editions/codemirrordemo/tiddlers/SiteSubtitle.tid create mode 100644 editions/codemirrordemo/tiddlers/SiteTitle.tid create mode 100644 editions/codemirrordemo/tiddlywiki.info diff --git a/editions/codemirrordemo/tiddlers/DefaultTiddlers.tid b/editions/codemirrordemo/tiddlers/DefaultTiddlers.tid new file mode 100644 index 000000000..7a76c9c79 --- /dev/null +++ b/editions/codemirrordemo/tiddlers/DefaultTiddlers.tid @@ -0,0 +1,4 @@ +title: $:/DefaultTiddlers + +[[HelloThere]] +[[Draft of 'LoremIpsum']] diff --git a/editions/codemirrordemo/tiddlers/Draft of LoremIpsum.tid b/editions/codemirrordemo/tiddlers/Draft of LoremIpsum.tid new file mode 100644 index 000000000..fff4597eb --- /dev/null +++ b/editions/codemirrordemo/tiddlers/Draft of LoremIpsum.tid @@ -0,0 +1,9 @@ +title: Draft of 'LoremIpsum' +draft.of: LoremIpsum +draft.title: LoremIpsum + +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. diff --git a/editions/codemirrordemo/tiddlers/HelloThere.tid b/editions/codemirrordemo/tiddlers/HelloThere.tid new file mode 100644 index 000000000..78c25c64f --- /dev/null +++ b/editions/codemirrordemo/tiddlers/HelloThere.tid @@ -0,0 +1,9 @@ +title: HelloThere + +This is a demo of TiddlyWiki5 incorporating a plugin for the [[CodeMirror|http://codemirror.net/]] text editor for the web. + +Try editing the tiddler LoremIpsum to try out the editor. + +To add the plugin to your own TiddlyWiki5, just drag this link to the browser window: + +[[$:/plugins/tiddlywiki/codemirror]] diff --git a/editions/codemirrordemo/tiddlers/LoremIpsum.tid b/editions/codemirrordemo/tiddlers/LoremIpsum.tid new file mode 100644 index 000000000..b84a52af2 --- /dev/null +++ b/editions/codemirrordemo/tiddlers/LoremIpsum.tid @@ -0,0 +1,7 @@ +title: LoremIpsum + +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. diff --git a/editions/codemirrordemo/tiddlers/SiteSubtitle.tid b/editions/codemirrordemo/tiddlers/SiteSubtitle.tid new file mode 100644 index 000000000..3c38618e6 --- /dev/null +++ b/editions/codemirrordemo/tiddlers/SiteSubtitle.tid @@ -0,0 +1,3 @@ +title: SiteSubtitle + +a demo of the CodeMirror plugin for TiddlyWiki5 \ No newline at end of file diff --git a/editions/codemirrordemo/tiddlers/SiteTitle.tid b/editions/codemirrordemo/tiddlers/SiteTitle.tid new file mode 100644 index 000000000..dde523483 --- /dev/null +++ b/editions/codemirrordemo/tiddlers/SiteTitle.tid @@ -0,0 +1,3 @@ +title: SiteTitle + +codemirrordemo \ No newline at end of file diff --git a/editions/codemirrordemo/tiddlywiki.info b/editions/codemirrordemo/tiddlywiki.info new file mode 100644 index 000000000..2f2095d60 --- /dev/null +++ b/editions/codemirrordemo/tiddlywiki.info @@ -0,0 +1,12 @@ +{ + "plugins": [ + "tiddlywiki/codemirror" + ], + "themes": [ + "tiddlywiki/snowwhite" + ], + "doNotSave": [ + ], + "includeWikis": [ + ] +} diff --git a/nbld.sh b/nbld.sh index 53869018c..46a4235c2 100755 --- a/nbld.sh +++ b/nbld.sh @@ -45,6 +45,14 @@ node ./tiddlywiki.js \ --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/d3demo.html text/plain \ || exit 1 +# Seventh, 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 + # Run tests ./test.sh From f9451b9d998f0fceb6fdf9f42cef3d076f4d2209 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 09:50:45 +0100 Subject: [PATCH 079/183] Update Features tiddler to point to CodeMirror demo --- editions/tw5.com/tiddlers/Features.tid | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/editions/tw5.com/tiddlers/Features.tid b/editions/tw5.com/tiddlers/Features.tid index 392ab4d90..1dc18a44d 100644 --- a/editions/tw5.com/tiddlers/Features.tid +++ b/editions/tw5.com/tiddlers/Features.tid @@ -1,6 +1,6 @@ created: 201308221728 creator: JeremyRuston -modified: 201309102146 +modified: 201310250846 modifier: JeremyRuston tags: introduction title: Features @@ -13,7 +13,8 @@ title: Features * Familiar user interface elements like <$button message="tw-modal" param="SampleWizard" class="btn btn-inverse">wizards and <$button message="tw-notify" param="SampleNotification" class="btn btn-inverse">notifications * Easily [[import|ImportTiddlers]] content via drag and drop, copy and paste, or browsing for local files * TiddlyWiki is [[surprisingly scalable|Scalability]] to many thousands of tiddlers and megabytes of content -* Explore the [[D3.js visualisation plugin|http://five.tiddlywiki.com/d3demo.html]] +* Explore the [[D3.js visualisation plugin|http://five.tiddlywiki.com/d3demo.html]] +* Try out the [[CodeMirror plugin|http://five.tiddlywiki.com/codemirrordemo.html]] * Many internal improvements: ** TiddlyWiki can now be run under [[node.js]] as well as in the browser, allowing it to be used as a personal web server ** An elegant [[microkernel architecture|PluginMechanism]], that allows infinite customisation by replacing and augmenting the core modules From 3d6165753b12b5bd80c778092d9b1a780cafe5a9 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 12:32:03 +0100 Subject: [PATCH 080/183] Save a copy of the old tw2 build for comparison purposes --- editions/tw2/target/pre-widgetredux2.html | 13211 ++++++++++++++++++++ 1 file changed, 13211 insertions(+) create mode 100644 editions/tw2/target/pre-widgetredux2.html diff --git a/editions/tw2/target/pre-widgetredux2.html b/editions/tw2/target/pre-widgetredux2.html new file mode 100644 index 000000000..920426b8c --- /dev/null +++ b/editions/tw2/target/pre-widgetredux2.html @@ -0,0 +1,13211 @@ + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
+
+
+
+
+
+
+
+
+
+
+
Background: #fff
+Foreground: #000
+PrimaryPale: #8cf
+PrimaryLight: #18f
+PrimaryMid: #04b
+PrimaryDark: #014
+SecondaryPale: #ffc
+SecondaryLight: #fe8
+SecondaryMid: #db4
+SecondaryDark: #841
+TertiaryPale: #eee
+TertiaryLight: #ccc
+TertiaryMid: #999
+TertiaryDark: #666
+Error: #f88
+
+
+
+
<!--{{{-->
+<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
+<div class='title' macro='view title'></div>
+<div class='editor' macro='edit title'></div>
+<div macro='annotations'></div>
+<div class='editor' macro='edit text'></div>
+<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser excludeLists'></span></div>
+<!--}}}-->
+
+
+
To get started with this blank [[TiddlyWiki]], you'll need to modify the following tiddlers:
+* [[SiteTitle]] & [[SiteSubtitle]]: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
+* [[MainMenu]]: The menu (usually on the left)
+* [[DefaultTiddlers]]: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
+You'll also need to enter your username for signing your edits: <<option txtUserName>>
+
+
+
<<importTiddlers>>
+
+
+
<!--{{{-->
+<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
+<!--}}}-->
+
+
+
+
These [[InterfaceOptions]] for customising [[TiddlyWiki]] are saved in your browser
+
+Your username for signing your edits. Write it as a [[WikiWord]] (eg [[JoeBloggs]])
+
+<<option txtUserName>>
+<<option chkSaveBackups>> [[SaveBackups]]
+<<option chkAutoSave>> [[AutoSave]]
+<<option chkRegExpSearch>> [[RegExpSearch]]
+<<option chkCaseSensitiveSearch>> [[CaseSensitiveSearch]]
+<<option chkAnimate>> [[EnableAnimations]]
+
+----
+Also see [[AdvancedOptions]]
+
+
+
<!--{{{-->
+<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
+<div class='headerShadow'>
+<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
+<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
+</div>
+<div class='headerForeground'>
+<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
+<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
+</div>
+</div>
+<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
+<div id='sidebar'>
+<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
+<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
+</div>
+<div id='displayArea'>
+<div id='messageArea'></div>
+<div id='tiddlerDisplay'></div>
+</div>
+<!--}}}-->
+
+
+
/*{{{*/
+body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
+
+a {color:[[ColorPalette::PrimaryMid]];}
+a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
+a img {border:0;}
+
+h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
+h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
+h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}
+
+.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
+.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
+.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}
+
+.header {background:[[ColorPalette::PrimaryMid]];}
+.headerShadow {color:[[ColorPalette::Foreground]];}
+.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
+.headerForeground {color:[[ColorPalette::Background]];}
+.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}
+
+.tabSelected {color:[[ColorPalette::PrimaryDark]];
+	background:[[ColorPalette::TertiaryPale]];
+	border-left:1px solid [[ColorPalette::TertiaryLight]];
+	border-top:1px solid [[ColorPalette::TertiaryLight]];
+	border-right:1px solid [[ColorPalette::TertiaryLight]];
+}
+.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
+.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
+.tabContents .button {border:0;}
+
+#sidebar {}
+#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
+#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
+#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
+#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
+#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}
+
+.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
+.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
+.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
+.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
+	border:1px solid [[ColorPalette::PrimaryMid]];}
+.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
+.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
+.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
+.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
+	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
+.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
+.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
+	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}
+
+.wizard .notChanged {background:transparent;}
+.wizard .changedLocally {background:#80ff80;}
+.wizard .changedServer {background:#8080ff;}
+.wizard .changedBoth {background:#ff8080;}
+.wizard .notFound {background:#ffff80;}
+.wizard .putToServer {background:#ff80ff;}
+.wizard .gotFromServer {background:#80ffff;}
+
+#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
+#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
+
+.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
+
+.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
+.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
+.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
+.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
+.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
+.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
+.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
+.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
+
+.tiddler .defaultCommand {font-weight:bold;}
+
+.shadow .title {color:[[ColorPalette::TertiaryDark]];}
+
+.title {color:[[ColorPalette::SecondaryDark]];}
+.subtitle {color:[[ColorPalette::TertiaryDark]];}
+
+.toolbar {color:[[ColorPalette::PrimaryMid]];}
+.toolbar a {color:[[ColorPalette::TertiaryLight]];}
+.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
+.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
+
+.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
+.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
+.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
+.tagging .button, .tagged .button {border:none;}
+
+.footer {color:[[ColorPalette::TertiaryLight]];}
+.selected .footer {color:[[ColorPalette::TertiaryMid]];}
+
+.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
+.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
+.lowlight {background:[[ColorPalette::TertiaryLight]];}
+
+.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
+
+.imageLink, #displayArea .imageLink {background:transparent;}
+
+.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
+
+.viewer .listTitle {list-style-type:none; margin-left:-2em;}
+.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
+.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
+
+.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
+.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
+.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
+
+.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
+.viewer code {color:[[ColorPalette::SecondaryDark]];}
+.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
+
+.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
+
+.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
+.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
+.editorFooter {color:[[ColorPalette::TertiaryMid]];}
+.readOnly {background:[[ColorPalette::TertiaryPale]];}
+
+#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
+#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
+#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
+#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
+#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
+#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
+#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
+.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
+.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
+#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:alpha(opacity=60);}
+/*}}}*/
+
+
+
/*{{{*/
+* html .tiddler {height:1%;}
+
+body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
+
+h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
+h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
+h4,h5,h6 {margin-top:1em;}
+h1 {font-size:1.35em;}
+h2 {font-size:1.25em;}
+h3 {font-size:1.1em;}
+h4 {font-size:1em;}
+h5 {font-size:.9em;}
+
+hr {height:1px;}
+
+a {text-decoration:none;}
+
+dt {font-weight:bold;}
+
+ol {list-style-type:decimal;}
+ol ol {list-style-type:lower-alpha;}
+ol ol ol {list-style-type:lower-roman;}
+ol ol ol ol {list-style-type:decimal;}
+ol ol ol ol ol {list-style-type:lower-alpha;}
+ol ol ol ol ol ol {list-style-type:lower-roman;}
+ol ol ol ol ol ol ol {list-style-type:decimal;}
+
+.txtOptionInput {width:11em;}
+
+#contentWrapper .chkOptionInput {border:0;}
+
+.externalLink {text-decoration:underline;}
+
+.indent {margin-left:3em;}
+.outdent {margin-left:3em; text-indent:-3em;}
+code.escaped {white-space:nowrap;}
+
+.tiddlyLinkExisting {font-weight:bold;}
+.tiddlyLinkNonExisting {font-style:italic;}
+
+/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
+a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
+
+#mainMenu .tiddlyLinkExisting,
+	#mainMenu .tiddlyLinkNonExisting,
+	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
+#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
+
+.header {position:relative;}
+.header a:hover {background:transparent;}
+.headerShadow {position:relative; padding:4.5em 0 1em 1em; left:-1px; top:-1px;}
+.headerForeground {position:absolute; padding:4.5em 0 1em 1em; left:0; top:0;}
+
+.siteTitle {font-size:3em;}
+.siteSubtitle {font-size:1.2em;}
+
+#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}
+
+#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
+#sidebarOptions {padding-top:0.3em;}
+#sidebarOptions a {margin:0 0.2em; padding:0.2em 0.3em; display:block;}
+#sidebarOptions input {margin:0.4em 0.5em;}
+#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
+#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
+#sidebarOptions .sliderPanel input {margin:0 0 0.3em 0;}
+#sidebarTabs .tabContents {width:15em; overflow:hidden;}
+
+.wizard {padding:0.1em 1em 0 2em;}
+.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
+.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
+.wizardStep {padding:1em 1em 1em 1em;}
+.wizard .button {margin:0.5em 0 0; font-size:1.2em;}
+.wizardFooter {padding:0.8em 0.4em 0.8em 0;}
+.wizardFooter .status {padding:0 0.4em; margin-left:1em;}
+.wizard .button {padding:0.1em 0.2em;}
+
+#messageArea {position:fixed; top:2em; right:0; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
+.messageToolbar {display:block; text-align:right; padding:0.2em;}
+#messageArea a {text-decoration:underline;}
+
+.tiddlerPopupButton {padding:0.2em;}
+.popupTiddler {position: absolute; z-index:300; padding:1em; margin:0;}
+
+.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
+.popup .popupMessage {padding:0.4em;}
+.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0;}
+.popup li.disabled {padding:0.4em;}
+.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
+.listBreak {font-size:1px; line-height:1px;}
+.listBreak div {margin:2px 0;}
+
+.tabset {padding:1em 0 0 0.5em;}
+.tab {margin:0 0 0 0.25em; padding:2px;}
+.tabContents {padding:0.5em;}
+.tabContents ul, .tabContents ol {margin:0; padding:0;}
+.txtMainTab .tabContents li {list-style:none;}
+.tabContents li.listLink { margin-left:.75em;}
+
+#contentWrapper {display:block;}
+#splashScreen {display:none;}
+
+#displayArea {margin:1em 17em 0 14em;}
+
+.toolbar {text-align:right; font-size:.9em;}
+
+.tiddler {padding:1em 1em 0;}
+
+.missing .viewer,.missing .title {font-style:italic;}
+
+.title {font-size:1.6em; font-weight:bold;}
+
+.missing .subtitle {display:none;}
+.subtitle {font-size:1.1em;}
+
+.tiddler .button {padding:0.2em 0.4em;}
+
+.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
+.isTag .tagging {display:block;}
+.tagged {margin:0.5em; float:right;}
+.tagging, .tagged {font-size:0.9em; padding:0.25em;}
+.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
+.tagClear {clear:both;}
+
+.footer {font-size:.9em;}
+.footer li {display:inline;}
+
+.annotation {padding:0.5em; margin:0.5em;}
+
+* html .viewer pre {width:99%; padding:0 0 1em 0;}
+.viewer {line-height:1.4em; padding-top:0.5em;}
+.viewer .button {margin:0 0.25em; padding:0 0.25em;}
+.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
+.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
+
+.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
+.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
+table.listView {font-size:0.85em; margin:0.8em 1.0em;}
+table.listView th, table.listView td, table.listView tr {padding:0 3px 0 3px;}
+
+.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
+.viewer code {font-size:1.2em; line-height:1.4em;}
+
+.editor {font-size:1.1em;}
+.editor input, .editor textarea {display:block; width:100%; font:inherit;}
+.editorFooter {padding:0.25em 0; font-size:.9em;}
+.editorFooter .button {padding-top:0; padding-bottom:0;}
+
+.fieldsetFix {border:0; padding:0; margin:1px 0px;}
+
+.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
+.zoomer div {padding:1em;}
+
+* html #backstage {width:99%;}
+* html #backstageArea {width:99%;}
+#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em;}
+#backstageToolbar {position:relative;}
+#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em;}
+#backstageButton {display:none; position:absolute; z-index:175; top:0; right:0;}
+#backstageButton a {padding:0.1em 0.4em; margin:0.1em;}
+#backstage {position:relative; width:100%; z-index:50;}
+#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin-left:3em; padding:1em;}
+.backstagePanelFooter {padding-top:0.2em; float:right;}
+.backstagePanelFooter a {padding:0.2em 0.4em;}
+#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
+
+.whenBackstage {display:none;}
+.backstageVisible .whenBackstage {display:block;}
+/*}}}*/
+
+
+
+
/***
+StyleSheet for use when a translation requires any css style changes.
+This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
+***/
+/*{{{*/
+body {font-size:0.8em;}
+#sidebarOptions {font-size:1.05em;}
+#sidebarOptions a {font-style:normal;}
+#sidebarOptions .sliderPanel {font-size:0.95em;}
+.subtitle {font-size:0.8em;}
+.viewer table.listView {font-size:0.95em;}
+/*}}}*/
+
+
+
/*{{{*/
+@media print {
+#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none !important;}
+#displayArea {margin: 1em 1em 0em;}
+noscript {display:none;} /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
+}
+/*}}}*/
+
+
+
<!--{{{-->
+<div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
+<div class='title' macro='view title'></div>
+<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
+<div class='tagging' macro='tagging'></div>
+<div class='tagged' macro='tags'></div>
+<div class='viewer' macro='view text wikified'></div>
+<div class='tagClear'></div>
+<!--}}}-->
+
+ +
+ +
+
+
|''URL:''|http://tiddlywiki.abego-software.de/|
+|''Description:''|UdoBorkowski's Extensions for TiddlyWiki|
+|''Author:''|UdoBorkowski|
+
+
+
+
When you click on the [[download|Download]] button, the website will detect your browser and operating system, and trigger a download for either an empty ~TiddlyWiki file or, if you're running [[Safari]] or [[Opera]], a zip file which also includes the TiddlySaver file. This is version <<version>> of ~TiddlyWiki.
+
+Advanced users may find these download options helpful (right click the link and select "Save as" to download):
+*[[An empty TiddlyWiki file|http://www.tiddlywiki.com/empty.html]]
+*[[A zip file containing an empty TiddlyWiki file plus a TiddlySaver file|http://www.tiddlywiki.com/empty.zip]]
+*[[A copy of this website (TiddlyWiki.com) minus images|#]]
+*[[The TiddlySaver.jar file on its own|http://www.tiddlywiki.com/TiddlySaver.jar]]
+Finally, these links may be useful:
+*[[Downloading]] instructions for the main browsers
+*A list of supported [[Browsers]]
+
+
+
+
This tiddler shows some more complex effects that can be obtained with cunning use of CSS. Not all of these will work properly on all browsers because of differences in CSS implementation, but they should fail gracefully.
+
+You can have special formatting for a specific, named tiddler like this:
+{{{
+#tiddlerHelloThere .title {
+background-color: #99aaee;
+}
+}}}
+
+Or for the first displayed tiddler:
+{{{
+div.tiddler:first-child .title {
+font-size: 28pt;
+}
+}}}
+
+Or just for the first line of every tiddler:
+{{{
+.viewer:first-line {
+background-color: #999999;
+}
+}}}
+
+Or just for the first letter of every tiddler:
+{{{
+.viewer:first-letter {
+float: left;
+font-size: 28pt;
+font-weight: bold;
+}
+}}}
+
+Or just for tiddlers tagged with a particular tag (note that this won't work for tags that contain spaces):
+{{{
+div[tags~="welcome"].tiddler .viewer {
+background-color: #ffccaa;
+}
+
+div[tags~="features"].tiddler .viewer {
+background-color: #88aaff;
+}
+}}}
+
+
+
+
If you check this box in the InterfaceOptions, TiddlyWiki will automatically SaveChanges every time you edit a tiddler. In that way there's a lot less chance of you losing any information. 
+
+However, if you also have the SaveBackups checkbox ticked, you'll end up with a lot of archived files. You may prefer to select either one or the other.
+
+
+
+
One of the great strengths of TiddlyWiki is the way that its interface can be customised by editting various shadow tiddlers. However, a problem that has consistently emerged is the difficulty of reconciling the needs of authors, who need access to the full array of TiddlyWiki features, and the needs of ordinary readers, who generally benefit from seeing a restricted set of functionality more suited to their needs.
+
+The new backstage area in release 2.2 offers a solution to this conundrum by providing a consistent way of accessing authoring functionality that is independent of the interface customisations (so, even if you blast away the contents of your PageTemplate, you can still access the backstage area).
+
+The backstage area is only available when a TiddlyWiki is edittable - typically meaning that it's been opened off of a {{{file://}}} URL. It appears as an unobtrusive link in the topright corner of the page. Clicking it reveals the backstage toolbar consisting of commands like {{{saveChanges}}} and drop-down tasks like ImportTiddlers, SyncMacro, PluginManager and Tweak (which provides access to the new [[options macro]]).
+
+
+
+
|Style|Formatting|h
+|''bold''|{{{''bold''}}} - two single-quotes, not a double-quote|
+|//italics//|{{{//italics//}}}|
+|''//bold italics//''|{{{''//bold italics//''}}}|
+|__underline__|{{{__underline__}}}|
+|--strikethrough--|{{{--Strikethrough--}}}|
+|super^^script^^|{{{super^^script^^}}}|
+|sub~~script~~|{{{sub~~script~~}}}|
+|@@Highlight@@|{{{@@Highlight@@}}}|
+|{{{plain text}}}|{{{ {{{PlainText No ''Formatting''}}} }}}|
+|/%this text will be invisible%/hidden text|{{{/%this text will be invisible%/}}}|
+
+
+
+
|''URL:''|http://tiddlywiki.bidix.info/|
+|''Description:''|Repository for BidiX's TiddlyWiki Extensions|
+|''Author:''|BidiX|
+
+
+
+
|''URL:''|http://bob.mcelrath.org/plugins.html|
+|''Description:''|Bob ~McElrath's Plugins|
+|''Author:''|~BobMcElrath|
+
+
+
+
Here is a table of web browsers that work with ~TiddlyWiki:
+
+|!Browser |!Version |!Allows changes to be saved locally?|
+|InternetExplorer |6.0+ |Yes |
+|FireFox |1.0+ |Yes |
+|[[Safari]] |1.0+ |Yes, using TiddlySaver plugin|
+|[[Opera]] |? |Yes, using TiddlySaver plugin|
+|Netscape Navigator |7.0+ |Yes |
+|[[Camino]] |1.0+ |Yes |
+|[[Chrome]] |All |Yes, using TiddlySaver plugin|
+|[[iPhone]] |All |Yes, using [[iTW|http://www.apple.com/webapps/productivity/itwatiddlywikiforiphone.html]]|
+|[[Wii]] |All |No |
+
+See also the [[TiddlyWiki apps available for smartphones|MobileDevices]].
+
+Please [[let us know|http://groups.google.com/group/TiddlyWiki]] of any additions or corrections.
+
+
+
+
!!Inline Styles
+Apply CSS properties inline:
+{{{
+@@color:#4bbbbb;Some random text@@
+}}}
+Displays as:
+@@color:#4bbbbb;Some random text@@
+!!CSS classes
+CSS classes can be applied to text blocks or runs. This form creates an HTML {{{<span>}}}:
+{{{
+{{customClassName{Some random text}}}
+}}}
+Displays as:
+{{customClassName{Some random text}}}
+This form generates an HTML {{{<div>}}}:
+{{{
+ {{customClassName{
+ Some random text
+ }}}
+}}}
+Displays as:
+{{customClassName{
+Some random text
+}}}
+
+
+
+
CamelCase (camel case or camel-case)—also known as medial capitals—is the practice of writing compound words or phrases in which the elements are joined without spaces.
+
+
+
+
The Mozilla-based [[Camino browser|http://www.caminobrowser.org/]] on Macintosh OS X works well with TiddlyWiki, including the ability to SaveChanges.
+
+
+
+
Abandons any pending edits to the current tiddler, and switches it the default view. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar cancelTiddler>>
+}}}
+
+
+
+
When you upload a TiddlyWiki to a web server, if doesn't load properly, it may be a CharacterEncoding issue.
+
+TiddlyWiki uses Unicode ~UTF-8 encoding and won't load properly if your host is serving it as ~ISO-8859-1. You should be able to check this by loading another page on the server in FireFox and selecting 'Page Info' on the 'Tools' menu.
+
+If this is the case, it should be reasonably easy to sort out. We recommend that you contact your server host and ask them to serve it in ~UTF-8 mode. If you are more technically hands-on, you may be able to [[solve the issue yourself|http://www.w3.org/International/questions/qa-htaccess-charset]].
+
+
+
+
~TiddlyWiki works well in Google Chrome and Chromium using [[TiddlySaver]].
+However, due to Chrome's [[lack of Java support on Mac OS and Linux|http://groups.google.com/group/tiddlywiki/browse_thread/thread/1f0e299198573e89]], it currently cannot save on those platforms.
+
+
+
+
Closes all other open tiddlers, except for any that are being editted. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar closeOthers>>
+}}}
+
+
+
+
Closes the current tiddler, regardless of whether it is being editted. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar closeTiddler>>
+}}}
+
+
+
+
Text such as computer code that should be displayed without wiki processing and preserving line breaks:
+&#123;&#123;&#123;
+Some plain text including WikiLinks
+&#125;&#125;&#125;
+Displays as:
+{{{
+Some plain text including WikiLinks
+}}}
+!See Also
+[[Suppressing Formatting]]
+
+
+
+
Background: #fff
+Foreground: #000
+PrimaryPale: #aef
+PrimaryLight: #8ad
+PrimaryMid: #38c
+PrimaryDark: #026
+SecondaryPale: #ffc
+SecondaryLight: #fe8
+SecondaryMid: #db4
+SecondaryDark: #841
+TertiaryPale: #eee
+TertiaryLight: #ccc
+TertiaryMid: #999
+TertiaryDark: #666
+QuaternaryPale: #cf8
+QuaternaryLight: #8f1
+QuaternaryMid: #4b0
+QuaternaryDark: #140
+Error: #f88
+
+
+
+
This tiddler determines the colour scheme used within the TiddlyWiki.  When a new space is created the random color palette macro determines the default, however these can be overwritten directly in the ColorPalette tiddler.
+
+{{{
+Background: #e0e3f5
+Foreground: #090d1e
+PrimaryPale: #b9c2e8
+PrimaryLight: #7485d2
+PrimaryMid: #384fb1
+PrimaryDark: #0c1126
+SecondaryPale: #cbe8b9
+SecondaryLight: #98d274
+SecondaryMid: #67b138
+SecondaryDark: #16260c
+TertiaryPale: #e8bab9
+TertiaryLight: #d27574
+TertiaryMid: #b13a38
+TertiaryDark: #260c0c
+Error: #f88
+ColorPaletteParameters: HSL([229|48], [0.5146822107288709],[0.1|0.8208696653333263])
+}}}
+
+
+
+
[>img[tiddlywiki.org logo|http://trac.tiddlywiki.org/chrome/site/tworg_logo_med.jpg][http://www.tiddlywiki.org]]~TiddlyWiki today is the result of the efforts of dozens of people around the world generously contributing their time and skill, and offering considerable help and support. 
+
+The community has several hubs for different activities:
+* The DiscussionForums on Google Groups
+* The ~TiddlyWiki IRC room at irc://irc.freenode.net/TiddlyWiki
+* TiddlyWiki code at http://github.com/TiddlyWiki/tiddlywiki
+* TiddlyWiki issue tracker at https://github.com/TiddlyWiki/tiddlywiki/issues
+* The community wiki at http://tiddlywiki.org/
+
+The community welcomes [[contributions|Contribute]].
+
+
+
+
PageTemplate
+|>|>|SiteTitle - SiteSubtitle|
+|MainMenu|DefaultTiddlers<br><br><br><br>ViewTemplate<br><br>EditTemplate|SideBarOptions|
+|~|~|OptionsPanel|
+|~|~|AdvancedOptions|
+|~|~|<<tiddler Configuration.SideBarTabs>>|
+
+''StyleSheet:'' StyleSheetColors - StyleSheetLayout - StyleSheetPrint
+
+InterfaceOptions
+
+SiteUrl
+
+Extensive StartupParameters to control the startup beahviour of ~TiddlyWiki through specially crafted ~URLs
+
+
+
+
SideBarTabs
+|[[Timeline|TabTimeline]]|[[All|TabAll]]|[[Tags|TabTags]]|>|>|[[More|TabMore]] |
+|>|>||[[Missing|TabMoreMissing]]|[[Orphans|TabMoreOrphans]]|[[Shadowed|TabMoreShadowed]]|
+
+
+
+
@@margin-left:.5em;<<slider chkContents SideBarTabs "contents »" "show
+lists of tiddlers contained in this document">>@@
+
+
+
+
~TiddlyWiki has an enthusiastic and friendly [[Community|Help and Support]] of people around the world helping to grow and improve it. But there's always more to do and we welcome any offers of assistance. There are many ways that you can help:
+* Testing and [[reporting bugs|http://trac.tiddlywiki.org/wiki/ReportingBugs]] against the core code. Clear, easily reproducible bug reports are incredibly useful and really help the team improve the quality of ~TiddlyWiki
+* [[Contributing code|http://trac.tiddlywiki.org/wiki/ContributingCode]]
+* [[Making translations|http://trac.tiddlywiki.org/wiki/Translations]]
+* Documentation needs planning, writing and editing, particularly user guide information at http://www.tiddlywiki.org
+If you would like to make a financial contribution, ~TiddlyWiki is owned by the not-for-profit [[UnaMesa Foundation|http://www.unamesa.org/]] which welcomes donations.
+
+
+
+
Sometimes it's necessary to include custom HTML markup in the {{{<head>}}} of a TiddlyWiki file - typically for compatibility with ad serving software, external libraries, or for custom meta tags. The CustomMarkup operation defines four shadow tiddlers whose contents are spliced into the saved HTML file. (If you do a view/source you'll see the markers referred to below).
+|!Title |!Location |!Marker |
+|MarkupPreHead |Start of the {{{<head>}}} section |{{{<!--PRE-HEAD-START-->}}} |
+|MarkupPostHead |End of the {{{<head>}}} section |{{{<!--POST-HEAD-START-->}}} |
+|MarkupPreBody |Start of the {{{<body>}}} section |{{{<!--PRE-BODY-START-->}}} |
+|MarkupPostBody |End of the {{{<body>}}} section |{{{<!--POST-BODY-START-->}}} |
+MarkupPreHead is the only one with shadow content: a link to the default location of the RSS feed.
+
+
+
+
You can skin TiddlyWiki with a special StyleSheet tiddler containing your own CSS style sheet declarations. Unlike hacking the HTML directly, the StyleSheet tiddler withstands upgrading to a new version of the TiddlyWiki code (see HowToUpgrade). You can also use the NestedStyleSheets feature to structure your CSS declarations.
+
+The ExampleStyleSheet shows some of the basic options you can control (see AnotherExampleStyleSheet for more complex examples). SaveChanges and then click refresh in your browser to see StyleSheet changes. Any errors in your CSS syntax will be caught and displayed, but they shouldn't stop TiddlyWiki from working.
+
+
+
+
You can customise the appearance and behaviour of TiddlyWiki to almost any degree you want:
+* Install [[Plugins]] to extend the core functionality
+* Use the ColorPalette to change the basic colour scheme
+* Use built in [[Formatting|Basic Formatting]] techniques
+* Create a CustomStyleSheet for finer grained control over the appearance
+* Customise the PageTemplate, ViewTemplate or EditTemplate to change the composition of the page and the layout of individual tiddlers
+* Further SpecialTiddlers
+* Use off-the-shelf themes from [[TiddlyThemes|http://tiddlythemes.com/]]
+* Visit the [[Configuration]] summary to see all the special configuration tiddlers
+* Use TiddlyBookmarklets for low level hacking of TiddlyWiki documents
+
+
+
+
Several [[Macros|MacrosContent]] including the [[Today Macro|today macro]] take a [[Date Format String|Date Formats]] as an optional argument. This string can be a combination of ordinary text, with some special characters that get substituted by parts of the date:
+* {{{DDD}}} - day of week in full (eg, "Monday")
+* {{{ddd}}} - short day of week (eg, "Mon")
+* {{{DD}}} - day of month
+* {{{0DD}}} - adds a leading zero
+* {{{DDth}}} - adds a suffix
+* {{{WW}}} - ~ISO-8601 week number of year
+* {{{0WW}}} - adds a leading zero
+* {{{MMM}}} - month in full (eg, "July")
+* {{{mmm}}} - short month (eg, "Jul")
+* {{{MM}}} - month number
+* {{{0MM}}} - adds leading zero
+* {{{YYYY}}} - full year
+* {{{YY}}} - two digit year
+* {{{wYYYY}}} - full year with respect to week number
+* {{{wYY}}} two digit year with respect to week number
+* {{{hh}}} - hours
+* {{{0hh}}} - adds a leading zero
+* {{{hh12}}} - hours in 12 hour clock
+* {{{0hh12}}} - hours in 12 hour clock with leading zero
+* {{{mm}}} - minutes
+* {{{0mm}}} - minutes with leading zero
+* {{{ss}}} - seconds
+* {{{0ss}}} - seconds with leading zero
+* {{{am}}} or {{{pm}}} - lower case AM/PM indicator
+* {{{AM}}} or {{{PM}}} - upper case AM/PM indicator
+
+!!!!Examples
+{{{DDth MMM YYYY}}} - 16th February 2011
+{{{DDth mmm hh:mm:ss}}} - 16th Feb 2011 11:38:42
+
+
+
+
HelloThere GettingStarted [[TiddlyWiki Browser Compatibility]] Raves Examples
+
+
+
+
This tiddler contains a list of tiddlers that are opened automatically when you load the tiddlywiki.
+The default is:
+{{{
+[[GettingStarted]]
+}}}
+
+An example could be:
+{{{
+[[HelloThere]]
+[[Reference]]
+[[Features]]
+}}}
+
+
+
+
Deletes the current tiddler. A confirmation dialogue is displayed unless disabled with the ConfirmBeforeDeleting checkbox in AdvancedOptions. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar deleteTiddler>>
+}}}
+
+
+
+
TiddlyWiki developer documentation is being built up at http://tiddlywikidev.tiddlyspace.com/.
+
+Michael Mahemoff has written a very helpful outline of the architecture of ~TiddlyWiki: [[Part 1|http://softwareas.com/tiddlywiki-internals-1-of-3-architectural-concepts]], [[Part 2|http://softwareas.com/tiddlywiki-internals-2-of-3-list-of-javascript-files]] and [[Part 3|http://softwareas.com/tiddlywiki-internals-3-of-3-key-javascript-classes-and-files]].
+
+
+
+
There are two Google Group discussion forums for discussions about ~TiddlyWiki, whether basic entry level questions or more complex development challenges. They are the best places to ask questions about ~TiddlyWiki, and to connect with other enthusiasts:
+* A ~TiddlyWiki group for general discussion, bug reports and announcements at http://groups.google.com/group/TiddlyWiki
+* A ~TiddlyWikiDev group for discussion about ~TiddlyWiki development at http://groups.google.com/group/TiddlyWikiDev
+!~Non-English resources
+There are a number of resources for non-English language speakers:
+* ~TiddlyWikiFR, in French, at http://groups.google.com/group/TiddlyWikiFR
+* ~TiddlyWiki 華語支援論壇, in Chinese, at http://groups.google.com/group/TiddlyWiki-zh
+* A guide to [[TiddlyWiki in Japanese|http://www.geocities.jp/potto372/tiddlywiki/tiddlywikinote.html]]
+
+
+
+
<<download>>
+
+
+
+
/***
+|''Name:''|DownloadTiddlyWikiPlugin|
+|''Description:''|Download TiddlyWiki according to browser type|
+|''Version:''|0.0.8|
+|''Date:''|Aug 26, 2008|
+|''Source:''|http://www.tiddlywiki.com/#DownloadTiddlyWikiPlugin|
+|''License:''|[[BSD open source license]]|
+|''~CoreVersion:''|2.4.1|
+***/
+
+//{{{
+if(!version.extensions.DownloadTiddlyWikiPlugin) {
+version.extensions.DownloadTiddlyWikiPlugin = {installed:true};
+
+config.macros.download = {};
+
+merge(config.macros.download,{
+	label: "download",
+	prompt: "Download TiddlyWiki",
+	className: "chunkyButton"});
+
+config.macros.download.handler = function(place,macroName,params,wikifier,paramString,tiddler)
+{
+	var span = createTiddlyElement(place,"span",null,this.className);
+	createTiddlyButton(span,params[0]||this.label,params[1]||this.prompt,this.onClick);
+};
+
+config.macros.download.onClick = function(ev)
+{
+	// display the tiddler containing the instructions
+	var e = ev || window.event;
+	var title = "Downloading";
+	var url = config.browser.isSafari || config.browser.isOpera ? 'http://www.tiddlywiki.com/empty.zip' :'http://www.tiddlywiki.com/empty.download';
+	if(config.browser.isOpera || config.browser.isWindows) {
+		story.displayTiddler(target,title);
+		window.setTimeout(function() {document.location.href = url;},300);
+	} else {
+		// put an iframe in the target instructions tiddler to start the download
+		var html = '<html><iframe src="' + url + '" style="display:none"></html>';
+		var tiddler = store.getTiddler(title);
+		var oldText = tiddler.text;
+		tiddler.text = html + tiddler.text;
+		var target = resolveTarget(e);
+		story.closeTiddler(title,true);
+		story.displayTiddler(target,title);
+		tiddler.text = oldText;
+	}
+	return false;
+};
+
+} //# end of 'install only once'
+//}}}
+
+
+
+
The following downloading guidelines have been created to cover the vast majority of browsers and operating systems.
+
+If you are still having trouble downloading ~TiddlyWiki after checking these guidelines, check the [[list of browsers|Browsers]] to make sure your browser and operating system are supported. And you can always [[turn to the community|Help and Support]] for help!
+!Downloading guidelines
+[[Firefox on Mac OS X|Downloading guidelines: Firefox on Mac OS X]]
+[[Firefox on Windows Vista|Downloading guidelines: Firefox on Windows Vista]]
+[[Firefox on Windows XP|Downloading guidelines: Firefox on Windows XP]]
+[[Firefox on Ubuntu|Downloading guidelines: Firefox on Ubuntu]]
+[[Internet Explorer on Windows Vista|Downloading guidelines: Internet Explorer on Windows Vista]]
+[[Internet Explorer on Windows XP|Downloading guidelines: Internet Explorer on Windows XP]]
+[[Safari on Mac OS X|Downloading guidelines: Safari on Mac OS X]]
+[[Opera on Mac OS X|Downloading guidelines: Opera on Mac OS X]]
+
+For unusual or custom deployments, have a look at [[advanced download options|Advanced download options]].
+
+
+
+
Follow these easy steps to get started. Note that these guidelines are for Firefox v3.0.1 running on Mac OS X 10.5.4.
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Download file
+You'll need to choose a name and location for your empty ~TiddlyWiki file - we recommend either your Desktop or Documents folder. Make sure you keep the .html file extension and leave the "Save As: Hypertext" default option as it is.
+[img[images/step1mac.jpg]]
+!Step 2 - Download complete
+Empty ~TiddlyWiki files are very small, and download should be completed quickly. You can now either launch the file from the download pane (shown below, double click on the file name to launch), or open your finder to open the file from there.
+[img[images/step2mac.jpg]]
+!Step 3 - Open file
+You'll be asked if you're sure you want to open the file - click "Open".
+[img[images/step3mac.jpg]]
+!Step 4 - Grant authority
+When you try to save your changes for the first time, Firefox may ask you to grant access to the file system. You will need to 'Allow' this for ~TiddlyWiki changes to be saved, and we recommend you tick the 'Remember this decision' checkbox so this dialogue box doesn't appear each time.
+
+[img[images/step4mac.jpg]]
+
+!Step 5 - You're ready to start!
+You're now ready to start playing with your ~TiddlyWiki file - be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
Follow these easy steps to get started! Note that these guidelines are for Firefox v3.0.1 running on Ubuntu version 8.0.4 (Hardy Heron).
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Download file
+You'll need to choose a name and location for your empty ~TiddlyWiki file - we recommend either your Desktop or Documents folder. Make sure you keep the file type as HTML Document.
+
+[img[images/step1ubuntu.png]]
+!Step 2 - Download complete
+Empty ~TiddlyWiki files are very small, and download should be completed quickly. You can now either launch the file from the download pane (shown below, double click on the file name to launch), or open your finder to open the file from there.
+
+[img[images/step2ubuntu.png]]
+
+!Step 3 - Grant authority
+When you try to save your changes for the first time, Firefox may ask you to grant access to the file system. You will need to 'Allow' this for ~TiddlyWiki changes to be saved, and we recommend you tick the 'Remember this decision' checkbox so this dialogue box doesn't appear each time.
+
+[img[images/step3ubuntu.png]]
+
+!Step 4 - You're ready to start!
+You're now ready to start playing with your ~TiddlyWiki file - be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
<<tiddler [[Downloading guidelines: Firefox on Windows Vista]]>>
+
+
+
+
Follow these steps to get started! These guidelines are for Firefox 3.0.1 running on Microsoft Windows Vista 6.0.0. Hopefully they will be helpful for your setup too.
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Download file
+You'll need to choose a name and location for your empty ~TiddlyWiki file - we recommend either your Desktop or Documents folder. Make sure you leave the "Save as type: HTML Document" default option as it is. Then click 'Save'.
+
+[img[images/step1vistaff.jpg]]
+!Step 2 - Open file
+Click on the file name in the Download dialogue box. This will open your ~TiddlyWiki file, and you'll be able to start editing immediately - but you'll need to complete Step 3 below to save your changes.
+
+[img[images/step2vistaff.jpg]]
+!Step 3 - Enable advanced features
+When you try to save changes for the first time in a browser session, this dialogue box will appear. These features will enable the saving mechanism. Tick the 'Remember this decision' checkbox and then click on 'Allow'.
+
+[img[images/step3vistaff.jpg]]
+!Step 4 - You're ready to start!
+You're now ready to start playing with your ~TiddlyWiki file - be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
Follow these steps to get started! These guidelines are for Firefox 3.0.1 running on Microsoft Windows XP, Service Pack 3. Hopefully they will be helpful for your setup too.
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Download file
+You'll need to choose a name and location for your empty ~TiddlyWiki file - we recommend either your Desktop or My Documents folder. Make sure you leave the "Save as type: Firefox Document" default option as it is. Then click 'Save'.
+
+[img[images/step1xpff.jpg]]
+!Step 2 - Open file
+Double click on the file name in the Download dialogue box, shown below. This will open your ~TiddlyWiki file, and you'll be able to start editing immediately - but you'll need to complete Step 3 below to save your changes.
+
+[img[images/step2xpff.jpg]]
+!Step 3 - Enable advanced features
+When you try to save changes for the first time in a browser session, this dialogue box will appear. These features will enable the saving mechanism. Tick the 'Remember this decision' checkbox and then click on 'Allow'.
+[img[images/step3xpff.jpg]]
+!Step 4 - You're ready to start!
+You're now ready to start playing with your ~TiddlyWiki file - be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
<<tiddler [[Downloading guidelines: Internet Explorer on Windows Vista]]>>
+
+
+
+
Follow these steps to get started! These guidelines are for Internet Explorer 7.0 running on Microsoft Windows Vista 6.0.0. Hopefully they will be helpful for your setup too.
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Download file
+You'll need to choose a name and location for your empty ~TiddlyWiki file - we recommend either your Desktop or Documents folder. Make sure you leave the "Save as type: HTML Document" default option as it is. Then click 'Save'.
+
+[img[images/step1vista.jpg]]
+!Step 2 - Open folder
+Once the download has been completed, ''don't open the file yet'' - a minor modification is required before ~TiddlyWiki will work! Click on 'Open Folder'.
+
+[img[images/step2vista.jpg]]
+!Step 3 - Modify properties
+In the folder, right click on the file name and select 'Properties' from the bottom of the contextual menu.
+
+[img[images/step3vista.jpg]]
+!Step 4 - Unblock file
+In order to allow saving, you need to Unblock the file. Select 'Unblock' near the bottom of the dialogue box, then click on 'OK'. If the Unblock button doesn't appear, then you don't need to worry about it - click on OK and proceed to step 5.
+
+[img[images/step4vista.jpg]]
+
+!Step 5 - Open file
+Double click on the file name to open the file. Internet Explorer takes special care to block javascript from untrusted sources, so a screen will appear with a yellow bar at the top and a warning in red. Click on the yellow bar (this will make it turn blue, as shown below), and select 'Allow Blocked Content...'.
+
+[img[images/step5vista.jpg]]
+
+!Step 6 - Allow active content
+~TiddlyWiki contains 'active content' that, for example, allows search and tagging to function. Approve this active content by clicking on 'Yes'.
+[img[images/step6vista.jpg]]
+!Step 7 - Enable saving mechanism
+One final hurdle! When you save for the first time, you'll be prompted to allow the activity. Click on 'Yes'.
+[img[images/step7vista.jpg]]
+!Step 8 - You're ready to start!
+You're now ready to start playing with your ~TiddlyWiki file - be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
Follow these steps to get started! These guidelines are for Internet Explorer 7.0 running on Microsoft Windows XP, Service Pack 3. Hopefully they will be helpful for your setup too.
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Download file
+You'll need to choose a name and location for your empty ~TiddlyWiki file - we recommend either your Desktop or My Documents folder. Make sure you leave the "Save as type: HTML Document" default option as it is. Then click 'Save'.
+
+[img[images/step1xpie.jpg]]
+!Step 2 - Open folder
+Once the download has been completed, ''don't open the file yet'' - a minor modification is required before ~TiddlyWiki will work! ''Click on 'Open Folder'''.
+
+[img[images/step2xpie.jpg]]
+!Step 3 - Modify properties
+In the folder, right click on the file name and select 'Properties' from the bottom of the contextual menu.
+
+[img[images/step3xpie.jpg]]
+!Step 4 - Unblock file
+In order to allow saving, you need to Unblock the file. Select 'Unblock' near the bottom of the dialogue box, then click on 'OK'. If the Unblock button doesn't appear, then you don't need to worry about it - click on OK and proceed to step 5.
+
+[img[images/step4xpie.jpg]]
+
+!Step 5 - Open file
+Now you've unblocked the file, you can double click on the file name to open the file. Internet Explorer takes special care to block javascript from untrusted sources, so a screen will appear with a yellow bar at the top and a warning in red. Click on the yellow bar (this will make it turn blue, as shown below), and select 'Allow Blocked Content...'.
+
+[img[images/step5xpie.jpg]]
+!Step 6 - Allow active content
+~TiddlyWiki contains 'active content' that, for example, allows animations to function. Approve this active content by clicking on 'Yes'.
+[img[images/step6xpie.jpg]]
+!Step 7 - Enable saving mechanism
+One final hurdle! When you save for the first time, you'll be prompted to allow the activity. Click on 'Yes'.
+[img[images/step7xpie.jpg]]
+!Step 8 - You're ready to start!
+You're now ready to start playing with your ~TiddlyWiki file - be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
Follow these easy steps to get started! Note that these guidelines are for Opera 9.5.1 running on Mac OS X 10.5.4, but will apply to several other configurations (including most other versions of [[Opera]])
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Download files
+Download the zip file to a location of your choice.
+
+[img[images/step1macopera.jpg]]
+!Step 2 - Check files
+Open the zip file, and then check in the resulting folder that you have automatically received two files:
+*empty.html (this is your ~TiddlyWiki file)
+*~TiddlySaver.jar (this is a java applet, which will make sure everything works in your chosen browser. Note a copy of this file ''must'' be kept in the same folder as any ~TiddlyWiki file you are using) 
+[img[images/step2macopera.jpg]]
+You can download the TiddlySaver file on it's own from [[here|http://www.tiddlywiki.com/TiddlySaver.jar]] if you need it.
+
+Open the empty.html file to get started.
+!Step 3 - Trust applet
+The ~TiddlySaver.jar applet has been signed by [[UnaMesa|http://www.unamesa.org]], who hold the rights to ~TiddlyWiki on behalf of the community. You can find out more about the applet itself [[here|TiddlySaver]], and you'll need to click on 'Trust' in order for the ~TiddlyWiki to work. If you've accidentally clicked on 'Don't Trust' previously, don't worry - just restart the browser and you should get asked again.
+
+[img[images/step3macopera.jpg]]
+!Step 4 - You're done!
+Thanks to the applet, you now have the full functionality of ~TiddlyWiki at your disposal. Be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
Follow these easy steps to get started! Note that these guidelines are for Safari 3.1.2 running on Mac OS X 10.5.4, but will apply to several other configurations (other versions of [[Safari]] and [[Opera]] too)
+
+''If these guidelines aren't appropriate, check the [[other browser-specific guidelines|Downloading]] to find some that are right for you.''
+!Step 1 - Check downloaded files
+After [[Download]], check first that you have automatically received a zip file containing two files (the contents of the zip file may have been extracted automatically - if not, open the zip file):
+*empty.html (this is your ~TiddlyWiki file)
+*~TiddlySaver.jar (this is a java applet, which will make sure everything works in your chosen browser. Note a copy of this file ''must'' be kept in the same folder as any ~TiddlyWiki file you are using) 
+[img[images/step1macsafari.jpg]]
+You can download the ~TiddlySaver.jar file on it's own from [[here|http://www.tiddlywiki.com/TiddlySaver.jar]] if you need it.
+
+Double click the empty.html file to get started.
+!Step 2 - Open file
+The system will check you're happy to open the ~TiddlyWiki file. Click on 'Open'.
+
+[img[images/step2macsafari.jpg]]
+!Step 3 - Trust applet
+The ~TiddlySaver.jar applet has been signed by [[UnaMesa|http://www.unamesa.org]], who hold the rights to ~TiddlyWiki on behalf of the community. You can find out more about the applet itself [[here|TiddlySaver]], and you'll need to click on 'Trust' in order for the ~TiddlyWiki to work. If you've accidentally clicked on 'Don't Trust' previously, don't worry - just restart the browser and you should get asked again.
+
+[img[images/step3macsafari.jpg]]
+!Step 4 - You're done!
+Thanks to the applet, you now have the full functionality of ~TiddlyWiki at your disposal. Be sure to check out the guidelines in [[Getting Started|GettingStarted]]!
+
+
+
+
This tiddler contains the markup used to display tiddlers in edit mode. It is designed to make it easy to change the layout/structure of the tiddler in edit mode.
+By default it contains the following markup:
+{{{
+<div class='toolbar'
+	macro='toolbar [[ToolbarCommands::EditToolbar]] icons:yes'>
+</div>
+<div class='heading editorHeading'>
+	<div class='editor title' macro='edit title'></div>
+	<div class='tagClear'></div>
+</div>
+<div class='annotationsBox' macro='annotations'>
+	<div class='editSpaceSiteIcon'
+		macro='tiddlerOrigin height:16 width:16 label:no interactive:no'>
+	</div>
+	<div class="privacyEdit" macro='setPrivacy label:no interactive:no'></div>
+	<div class='tagClear'></div>
+</div>
+<div class='editor' macro='edit text'></div>
+<div class='editorFooter'>
+	<div class='tagTitle'>tags</div>
+	<div class='editor' macro='edit tags'></div>
+	<div class='tagAnnotation'>
+		<span macro='message views.editor.tagPrompt'></span>
+		<span macro='tagChooser excludeLists'></span>
+	</div>
+</div>
+}}}
+
+
+
+
Switches the current tiddler to the current edit view. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar editTiddler>>
+}}}
+
+
+
+
~TiddlyWiki is made up of chunks of information called tiddlers. You're reading a tiddler called 'Editing' right now.
+
+When you open an empty ~TiddlyWiki file for the first time, you'll be presented with a tiddler called "~GettingStarted". This contains links to tiddlers to help you define the main attributes of your ~TiddlyWiki; the Title, SubTitle and MainMenu navigation (see [[Configuration]]).
+
+You will notice that each tiddler has a command menu in it's top right corner (''close'', ''edit'', and so on). You can use this to switch the tiddler into edit mode, or if you prefer you can simply double click the tiddler when it is in view mode. When you are finished, click on ''done'', or ''cancel'' to discard your changes.
+
+You can sign your edits with your username, which is currently set to <<option txtUserName>>. You can change it here, or in the right hand sidebar, where it can be found under [[options »|InterfaceOptions]].
+
+There are two ways to make links between tiddlers. If you are linking to a tiddler which already exists, put the tiddler name in double square brackets (Double click here, and you'll see double square brackets around the word [[example]]). You can use this technique to point to (and create) a new tiddler, or alternatively just write the tiddler name by joining words starting with capital letters, LikeThis. When you save the tiddler and click on the link, you can then add content to your new tiddler.
+
+
+
+
/***
+|''Name:''|ExamplePlugin|
+|''Description:''|To demonstrate how to write TiddlyWiki plugins|
+|''Version:''|2.0.3|
+|''Date:''|Sep 22, 2006|
+|''Source:''|http://www.tiddlywiki.com/#ExamplePlugin|
+|''Author:''|JeremyRuston (jeremy (at) osmosoft (dot) com)|
+|''License:''|[[BSD open source license]]|
+|''~CoreVersion:''|2.1.0|
+|''Browser:''|Firefox 1.0.4+; Firefox 1.5; InternetExplorer 6.0|
+***/
+
+//{{{
+
+// Uncomment the following line to see how the PluginManager deals with errors in plugins
+// deliberateError();
+
+// Log a message
+pluginInfo.log.push("This is a test message from " + tiddler.title);
+
+//}}}
+
+
+
+
#displayArea {background-color: #ffccff; }
+#mainMenu {border: 1px solid #ffff88; }
+#commandPanel {background-color: #008800; }
+
+
+
+
config.animFast = 0.12; // Speed for animations (lower == slower)
+config.animSlow = 0.01; // Speed for Easter Egg animations
+config.views.wikified.toolbarEdit.text = "Edit away, it won't get saved";
+
+
+
+
See Dave Gifford's incredibly detailed catalogue of TiddlyWiki being used in the wild in his [[TiddlyWikiShowcase|http://giffmex.tiddlyspot.com/]].
+
+Other notable or interesting examples:
+* Getting Things Done - [[MonkeyPirateTiddlyWiki|http://mptw.tiddlyspot.com/]],  [[mGSD|http://mgsd.tiddlyspot.com/#mGSD]],[[D-Cubed|http://www.dcubed.ca/]], [[TeamTasks|http://www.hawksworx.com/playground/TeamTasks/]], [[TiddlyPackingList|http://tiddlypacking.tiddlyspace.com/]], [[tbGTD|http://tbgtd.tiddlyspot.com/]]
+* [[Open Notebook Science|http://pineda-krch.com/2008/10/31/starting-an-open-notebook-science-project/]] - [[Garrett Lisi's|http://www.telegraph.co.uk/earth/main.jhtml?CMP=ILC-mostviewedbox&xml=/earth/2007/11/14/scisurf114.xml]] [[Deferential Geometry|http://deferentialgeometry.org/]], ([[video|http://broadcast.oreilly.com/2008/09/lisi-on-a-wiki.html]])
+* Novels and creative writing - [[TiddlyWikiWrite|http://www.ljcohen.net/resources-wiki.html]], [[Gimcrack'd|http://gimcrackd.com/]], [[Singularity!|http://www.antipope.org/charlie/toughguide.html]], [[Liberty Hall Writers Wiki|http://wiki.libertyhallwriters.org/doku.php?id=tiddlywiki]], [[Bibliotheca Caelestis. Tiddlywikiroman|http://bc.etkbooks.com/opac/]] from Hartmut Abendschein, [[Die, Vampire! Die!|http://www.davidvanwert.com/wiki/dievampiredie.html]] from David Van Wert, [[Rose|http://www.emacswiki.org/alex/rose.html]] from Alex Schroeder
+* Education - [[South Australia|https://secure.ait.org/wiki/background.htm]], [[maths in Massachusetts|http://luceatlux.net/mcasmath10/]], [[The NoteStorm Christian database|http://giffmex.org/nsdb.html]]
+* Factbooks - [[Web campaigning in the US|http://mitpress.mit.edu/books/0262062585/WebCampaigningDigitalSupplement.html]], [[Bolivian politics|http://www.centellas.org/politics/politiddly.html]], [[Prince2 project management|http://www.microupdate.net/cms/doc/PrinceII.html]], [[Python Grimoire|http://the.taoofmac.com/space/Python/Grimoire]], [[Marc Bolan and T-Rex|http://videodrama.tiddlyspot.com/]]
+* Teaching - [[An Introduction to Chain Indexing|http://informationr.net/tdw/publ/chain_indexing/chain_indexing.html]], [[Reasoning Well|http://reasoningwell.tiddlyspot.com/]]
+* Role Playing Games - [[Making kickass campaign sites with TiddlyWiki|http://www.encounteraday.com/2009/05/20/making-kickass-campaign-sites-with-mptw/]]
+* Medicine - [[TiddlyManuals|http://tiddlymanuals.tiddlyspace.com/]], [[AMBIT|http://ambit.tiddlyspace.com/]]
+* Law - [[TiddLegal|http://tiddlegal.tiddlyspot.com/]], [[Courtrules|http://www.michlaw.net/courtrules.html]]
+* Activism - [[Climate Change 2.0|http://www.climate-change-two.net/]], [[TiddlyWiki for Left Wing Journalists and Researchers|http://leftclickblog.blogspot.com/2008/02/tiddlywiki-for-left-journoes-and.html]]
+* Homepage/Portfolio - [[Digimask|http://digimask.com/]], [[American Cryptogram Association|http://msig.med.utah.edu/RunningUtes/cryptogram/aca.html]], [[Nykyri.net|http://www.nykyri.net/]]
+* Blog - [[UdiGrudi|http://udigrudi.net/]], [[LumpyMilk|http://lumpymilk.tiddlyspot.com/]], [[Veminra|http://veminra.tiddlyspace.com/]]
+* Mobile - [[iTW optimised for the iPhone|http://itw.bidix.info/]], [[iPhone|http://iphone.tiddlyspace.com/]], [[Android|http://android.tiddlyspace.com]]
+* Tools - [[TiddlyFolio|http://tiddlyfolio.tiddlyspot.com/]] (an electronic wallet), [[TiddlySlidy|http://tiddlyslidy.com/#TiddlySlidy]] (presentations using TiddlyWiki), [[TiddlyPocketBooks|http://tiddlypocketbook.com/]] (printable pocket books), [[TiddlyTweets|http://osmosoft.com/tiddlytweets/]] (a tweet archiver), [[Project Cecily|http://osmosoft.com/cecily/]] (a zooming user interface for TiddlyWiki)
+* As a component of other products:
+** [[Dido|http://projects.csail.mit.edu/exhibit/Dido/]] - a tiddlywikified database application
+** [[KNote|http://www.smartgoldfish.com/download.html]] - a notetakingprogram (desktopapplikation)  
+** [[Twine|http://gimcrackd.com/etc/src/]] - a program for creating interactive stories
+** [[CkEditor + TW|http://simonmcmanus.wordpress.com/2010/06/28/installing-ckeditor-with-standalone-tiddlywiki/]] - wysiwyg and TW on local disk (with an [[updated version from Måns Mårtensson|http://måns.dk/TW/tw265CKeditor.zip]])
+
+Also, see TiddlyWikiAdaptations
+
+
+
+
If you've any comments, corrections or observations about TiddlyWiki, the best way to get our attention is to post to the [[Community]] groups -- or write a blog entry.
+
+
+
+
~TiddlyWiki works well in ~FireFox v1.0 and above. If you're experiencing problems, make sure you've followed the [[downloading guidelines|Downloading]].
+
+When saving ~TiddlyWiki in ~FireFox for the first time, you can run into problems if you accidentally click 'Deny' on the dialog, and select //Remember this decision//.
+
+To reverse the effects, first locate the file {{{prefs.js}}} in your ~FireFox profile directory:
+* Under Windows you'll find it at something like {{{C:\Documents and Settings\Jeremy\Application Data\Mozilla\Firefox\Profiles\o3dhupu6.default\prefs.js}}}, where {{{Jeremy}}} is the name of your windows profile and {{{o3dhupu6}}} will be a similar string of gobbledegook. 
+* On the Mac it'll be at {{{~/Library/Application Support/Firefox/Profiles/o3dhupu6.default/prefs.js}}} 
+* On Linux you can find this file at {{{~/.mozilla/firefox/o3dhupu6.default/prefs.js}}}.
+
+Open the file in a text editor and find the line {{{user_pref("capability.principal.codebase.p0.denied", "UniversalXPConnect");}}} and simply replace the word {{{denied}}} with {{{granted}}}.
+//(Thanks to ~JonScully for figuring out this fix)//
+
+Firefox users should be aware that GreaseMonkey can break TiddlyWiki. There's also a rather unpredictable FireFoxKeyboardIssue.
+
+
+
+
Under FireFox, TiddlyWiki can get into a state where it ignores the arrow keys on the keyboard but accepts ordinary alphanumeric input. The triggers for this behaviour are unclear, but it doesn't just affect TiddlyWiki. The solution appears to be to locate the file {{{compreg.dat}}} in your FireFox profile folder and rename it to {{{compreg.dat.old}}}.
+
+
+
+
Here's one way to get a Flickr badge in TiddlyWiki:
+
+<html>
+<a href="http://www.flickr.com" style="text-align:center;">www.<strong style="color:#3993ff">flick<span style="color:#ff1c92">r</span></strong>.com</a><br>
+<iframe style="background-color:#ffffff; border-color:#ffffff; border:none;" width="113" height="151" frameborder="0" scrolling="no" src="http://www.flickr.com/apps/badge/badge_iframe.gne?zg_bg_color=ffffff&zg_person_id=35468148136%40N01" title="Flickr Badge"></iframe>
+</html>
+
+Here's the HTML code to insert in a tiddler:
+{{{
+<html>
+<a href="http://www.flickr.com" style="text-align:center;">www.<strong style="color:#3993ff">flick<span style="color:#ff1c92">r</span></strong>.com</a><br>
+<iframe style="background-color:#ffffff; border-color:#ffffff; border:none;" width="113" height="151" frameborder="0" scrolling="no" src="http://www.flickr.com/apps/badge/badge_iframe.gne?zg_bg_color=ffffff&zg_person_id=35468148136%40N01" title="Flickr Badge"></iframe>
+</html>
+}}}
+
+You'll need to know your Flickr person ID, which should replace the value "35468148136%40N01" in the HTML. There's a useful [[Flickr idGettr|http://eightface.com/code/idgettr/]] to help with this.
+
+
+
+
A new feature for the ThirdVersion of TiddlyWiki is the ability to generate an RssFeed of its content. You can flick it on with a new addition to the InterfaceOptions. If enabled, it automatically saves an RSS 2.0 format file with the last few changed tiddlers in it. It's given the same filename as the TiddlyWiki file but with the ".xml" extension. Like all TiddlyWiki features, it's experimental, and will probably be a bit temperamental in your feedreader until the bugs are ironed out.
+
+Note that you must set the tiddler SiteUrl to be the URL where your TiddlyWiki will be published. (Don't put leading spaces or line breaks before or after the URL).
+
+
+
+
Once you've successfully [[downloaded|Downloading]] ~TiddlyWiki, you can find out how to:
+* [[Edit your tiddlers|Editing]]
+* [[Save changes|SaveChanges]]
+* [[Search]] for content
+* Use [[tagging|Tags]] to organise your tiddlers
+* [[Customise|Customisation]] TiddlyWiki's appearance and behaviour
+* Use apps to run TiddlyWiki on a [[MobileDevice|MobileDevices]]
+See the full [[Reference Guide|Reference]]
+
+
+
+
The GettingStarted shadow tiddler contains information about how to start using your TiddlyWiki.
+You can change it to include anything you desire.
+
+For example a lot of tiddlywiki authors use it to explain what their wiki is about. This is a particularly useful approach because by default the GettingStarted tiddler is in the [[DefaultTiddlers|DefaultTiddlers shadows]] tiddler, thus opens automatically upon loading of the page.
+
+By default it is also part of the [[MainMenu|MainMenu shadows]] tiddler so is included on the left side of the menu bar above.
+
+
+
+
Several popular GreaseMonkey scripts can cause some or all features of TiddlyWiki to stop working - the default Linkify script seems to be particularly troublesome. There doesn't seem to be a //solid// way to disable GreaseMonkey from within TiddlyWiki (which is technically entirely understandable but does lead to a fairly grim user experience).
+
+
+
+
TiddlyWiki makes a great GuerillaWiki in situations where it's not practical to use a traditional wiki.
+
+For instance, in a corporate setting, persuading an over-worked IT department to install you a Wiki server for you isn't always going to be possible overnight. And your PC is locked down so you can't install a conventional Wiki yourself. But, equally, you can't go and use one of the public hosted Wiki services because your Information Security department would not allow all that corporate data to flow into an outside server.
+
+TiddlyWiki slices through those barriers by being usable on virtually all ~PCs.
+
+
+
+
HTML entities can be used to easily type special characters:
+{{{
+Here is a quote symbol: &quot;
+And a pound sign: &pound;
+}}}
+Displays as:
+Here is a quote symbol: &quot;
+And a pound sign: &pound;
+!Notes
+For a full list of available HTML references see:
+http://www.w3schools.com/tags/ref_entities.asp
+
+
+
+
Raw HTML text can be included in a tiddler:
+{{{
+<html>
+This is some <strong>HTML</strong> formatting
+</html>
+}}}
+<html>
+This is some <strong>HTML</strong> formatting
+</html>
+!Notes
+* only static HTML elements that are valid within a {{{<div>}}} work correctly
+* {{{document.write}}} cannot be used to generate dynamic content
+* External {{{<script>}}} elements cannot be used within {{{<html>}}} blocks
+
+
+
+
{{{
+!Heading Level 1
+!!Heading Level 2
+!!!Heading Level 3
+!!!!Heading Level 4
+!!!!!Heading Level 5
+!!!!!!Heading Level 6
+}}}
+Display as:
+!Heading Level 1
+!!Heading Level 2
+!!!Heading Level 3
+!!!!Heading Level 4
+!!!!!Heading Level 5
+!!!!!!Heading Level 6
+
+
+
+
Welcome to TiddlyWiki, a reusable non-linear personal web notebook.  It's a unique [[wiki|WikiWikiWeb]] that people [[love using|Raves]] to keep ideas and information organised. It was originally created by JeremyRuston and is now a thriving [[open source|OpenSourceLicense]] project with a busy [[Community]] of independent developers.
+
+TiddlyWiki is written in [[HTML]], [[CSS]] and JavaScript to run on any reasonably modern [[browser|Browsers]] without needing any ServerSide logic. It allows anyone to create personal SelfContained hypertext documents that can be published to a WebServer, sent by email, stored in a DropBox or kept on a USB thumb drive to make a WikiOnAStick. Because it doesn't need to be installed and configured it makes a great GuerillaWiki. This is revision <<version>> of TiddlyWiki, and is published under an OpenSourceLicense.
+
+Unlike most wikis, TiddlyWiki doesn't directly support group collaboration; it is a wiki in the sense of elevating linking be a part of the punctuation of writing. You can easily publish a TiddlyWiki you have created by placing the single file on a web server (for instance the homepage hosting provided by many ISPs). If you need full  group collaboration features, there are several HostedOptions to choose from.
+
+
+
+
TiddlyWiki is an [[OpenSourceLicense|open source]] product, with most help and support being provided by the [[Community]]. The best way to get help with a specific problem is the TiddlyWiki DiscussionForums (it's worth searching the group archives to see if anyone else has already solved the same problem).
+
+There are a number of other useful resources for learning about TiddlyWiki:
+
+* [[TiddlerToddler|http://tiddlertoddler.tiddlyspot.com/]] from Julie Starr
+* [[TwHelp|http://twhelp.tiddlyspot.com/]] from Morris Gray
+
+
+
+
+
+
This is an [[advanced option|AdvancedOptions]] that lets you choose whether editting features are shown when a TiddlyWiki file is viewed over {{{http:}}} (as opposed to {{{file:}}}).
+
+To publish a TiddlyWiki with the editting features hidden you'll need to create a tiddler tagged with 'systemConfig' and include in it the line:
+
+{{{
+config.options.chkHttpReadOnly = true;
+}}}
+
+End users can then override the setting using the AdvancedOptions panel.
+
+
+
+
Released in September 2004, the [[first version|firstversion.html]] was pretty basic, weighing in at 52KB.
+
+Released in December 2004, the [[second version|secondversion.html]] of TiddlyWiki grew 50% over the FirstVersion to 76KB. It added IncrementalSearch, the ReferencesButton, the PermalinkCommand, PermaView, [[closeAll macro]], smooth scrolling, an improved sidebar, an animation for the CloseTiddlerCommand and a tiny Easter egg in homage for Macintosh OS X. It also introduced a new site design.
+
+After that, there's a nearly complete archive of old versions of TiddlyWiki at:
+* http://www.tiddlywiki.com/archive/
+
+
+
+
Four dashes on a line by themselves are used to introduce a horizontal rule:
+{{{
+Before the rule
+----
+After the rule
+}}}
+Displays as:
+
+Before the rule
+----
+After the rule
+
+The HTML tag {{{<hr>}}} can be used as an alternative syntax:
+
+{{{
+Before the rule<hr>After the rule
+}}}
+Displays as:
+
+Before the rule<hr>After the rule
+
+
+
+
There are several ways that you can have your TiddlyWiki data hosted online, making it easier to access and share your information.
+
+!~TiddlySpot
+[<img[http://tiddlyspot.com/_ts/images/banner-logo.png][TiddlySpot|http://tiddlyspot.com]] ~TiddlySpot lets you put TiddlyWiki documents on the Web, and edit them directly in your browser. It doesn't yet offer full support for simultaneous editing by multiple users.
+
+!~DropBox
+Many people use [[DropBox|http://dropbox.com]] to synchronise their TiddlyWiki documents to make them available on multiple computers. The ~DropBox iPhone/iPad application also permits read only access to TiddlyWiki documents. See [[A Personal Knowledgebase Solution: Tiddlywiki and Dropbox|http://www.broowaha.com/articles/7671/a-personal-knowledgebase-solution-tiddlywiki-and-dropbox]]
+
+!~TiddlyWeb & ~TiddlySpace
+[<img[TiddlySpace|http://tiddlyspace.com/bags/frontpage_public/tiddlers/shinyspace.png][http://tiddlyspace.com]] [[TiddlySpace|http://tiddlyspace.com]] is a full hosted service for TiddlyWiki that allows multiple people to edit the same wiki at the same time. It is particularly focussed on sharing tiddlers.
+
+~TiddlySpace is built on top of [[TiddlyWeb|http://tiddlyweb.com]], a flexible architecture for putting tiddlers on the web.
+
+!~GieWiki
+[[GieWiki|http://code.google.com/p/giewiki/]] by Poul Staugaard is a new serverside for TiddlyWiki that focusses on making TiddlyWiki behave like a real wiki, with preview and versioning of tiddlers, any number of pages in any hierachy, an auto-generated sitemap, comments on tiddlers, fine-grained access control, image upload, recent changes etc. You can see a default instance at http://giewiki.appspot.com/.
+
+!ccTiddly
+[[ccTiddly|http://tiddlywiki.org/#ccTiddly]] is an older serverside written in PHP to use [[MySQL|http://en.wikipedia.org/wiki/MySQL]] for storage. It is no longer under active development, but remains a popular choice for basic TiddlyWiki hosting needs.
+
+!~TiddlyCouch
+[[TiddlyCouch|http://bijl.iriscouch.com/bijl-ctw/_design/tiddlycouch/_list/tiddlywiki/tiddlers]] is an experimental integration of TiddlyWiki with CouchDB.
+
+
+
+
The core TiddlyWiki code is regularly updated with bug fixes and new features. If you're using an earlier revision of TiddlyWiki, you should consider upgrading to the latest version.
+
+The steps you need to take depend on which version of TiddlyWiki you are upgrading from. From version 2.4.0 onwards, you can upgrade to the latest version using the 'Upgrade' tab in the BackstageArea, as described below. If you're using an earlier version of TiddlyWiki, you'll need to follow [[these instructions|HowToUpgradeOlderTiddlyWikis]] instead.
+
+! Upgrading from ~TiddlyWiki 2.4.0+ to the most recent version
+* Open the BackstageArea by clicking the 'backstage' button at the top right
+* Click on the 'Upgrade' tab
+* Click on the 'Upgrade' button, and follow the prompts
+
+
+
+
The steps you need to take depend on which version of TiddlyWiki you are upgrading from. From version 2.4.0 onwards, you can upgrade to the latest version using the 'Upgrade' tab in the BackstageArea, as described in HowToUpgrade. If you're using an earlier version of TiddlyWiki, you'll need to follow these instructions instead:
+! Upgrading from TiddlyWiki earlier than version 2.4.0 to the latest version
+* Download a fresh, empty version of TiddlyWiki by right-clicking on [[this link|http://www.tiddlywiki.com/empty.html]], selecting 'Save target' or 'Save link' and saving it in a convenient location as (say) "mynewtiddlywiki.html"
+* Open the new TiddlyWiki file in your browser
+* Choose ''import'' from the BackstageArea at the top of the window (you may need to click the 'backstage' button at the upper right to show the BackstageArea)
+* Click the ''browse'' button and select your original TiddlyWiki file (say, "mytiddlywiki.html") from the file browser
+* Click the ''open'' button on the import wizard; a list of all of your tiddlers is displayed
+* Click on the top-left checkbox to select all the tiddlers in the list
+* Scroll down to the bottom of the wizard and ensure that the checkbox labelled //Keep these tiddlers linked to this server...// is ''clear''
+* Click the ''import'' button
+The most likely cause of the upgrade process not working properly is that one of the [[Plugins]] you're using is not compatible with a change in the new release. If so, you can repeat the process omitting the troublesome plugins.
+
+
+
+
Entities in HTML documents allow characters to be entered that can't easily be typed on an ordinary keyboard. They take the form of an ampersand (&), an identifying string, and a terminating semi-colon (;). There's a complete reference [[here|http://www.htmlhelp.com/reference/html40/entities/]]; some of the more common and useful ones are shown below.
+
+|>|>|>|>|>|>| !HTML Entities |
+| &amp;nbsp; | &nbsp; | no-break space | &nbsp;&nbsp; | &amp;apos; | &apos; | single quote, apostrophe |
+| &amp;ndash; | &ndash; | en dash |~| &amp;quot; | " | quotation mark |
+| &amp;mdash; | &mdash; | em dash |~| &amp;prime; | &prime; | prime; minutes; feet |
+| &amp;hellip; | &hellip; |	horizontal ellipsis |~| &amp;Prime; | &Prime; | double prime; seconds; inches |
+| &amp;copy; | &copy; | Copyright symbol |~| &amp;lsquo; | &lsquo; | left single quote |
+| &amp;reg; | &reg; | Registered symbol |~| &amp;rsquo; | &rsquo; | right  single quote |
+| &amp;trade; | &trade; | Trademark symbol |~| &amp;ldquo; | &ldquo; | left double quote |
+| &amp;dagger; | &dagger; | dagger |~| &amp;rdquo; | &rdquo; | right double quote |
+| &amp;Dagger; | &Dagger; | double dagger |~| &amp;laquo; | &laquo; | left angle quote |
+| &amp;para; | &para; | paragraph sign |~| &amp;raquo; | &raquo; | right angle quote |
+| &amp;sect; | &sect; | section sign |~| &amp;times; | &times; | multiplication symbol |
+| &amp;uarr; | &uarr; | up arrow |~| &amp;darr; | &darr; | down arrow |
+| &amp;larr; | &larr; | left arrow |~| &amp;rarr; | &rarr; | right arrow |
+| &amp;lArr; | &lArr; | double left arrow |~| &amp;rArr; | &rArr; | double right arrow |
+| &amp;harr; | &harr; | left right arrow |~| &amp;hArr; | &hArr; | double left right arrow |
+
+The table below shows how accented characters can be built up by subsituting a base character into the various accent entities in place of the underscore ('_'):
+
+|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>| !Accented Characters |
+| grave accent | &amp;_grave; | &Agrave; | &agrave; | &Egrave; | &egrave; | &Igrave; | &igrave; | &Ograve; | &ograve; | &Ugrave; | &ugrave; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
+| acute accent | &amp;_acute; | &Aacute; | &aacute; | &Eacute; | &eacute; | &Iacute; | &iacute; | &Oacute; | &oacute; | &Uacute; | &uacute; | &nbsp; | &nbsp; | &Yacute; | &yacute; | &nbsp; | &nbsp; |
+| circumflex accent | &amp;_circ; | &Acirc; | &acirc; | &Ecirc; | &ecirc; | &Icirc; | &icirc; | &Ocirc; | &ocirc; | &Ucirc; | &ucirc; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
+| umlaut mark | &amp;_uml; | &Auml; | &auml; |  &Euml; | &euml; | &Iuml; | &iuml; | &Ouml; | &ouml; | &Uuml; | &uuml; | &nbsp; | &nbsp; | &Yuml; | &yuml; | &nbsp; | &nbsp; |
+| tilde | &amp;_tilde; | &Atilde; | &atilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Otilde; | &otilde; | &nbsp; | &nbsp; | &Ntilde; | &ntilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
+| ring | &amp;_ring; | &Aring; | &aring; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
+| slash | &amp;_slash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Oslash; | &oslash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
+| cedilla | &amp;_cedil; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Ccedil; | &ccedil; |
+
+
+
+
!Simple Images
+{{{
+[img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]
+}}}
+Displays as:
+[img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]
+!Tooltips for Images
+{{{
+[img[tooltip|http://wikitext.tiddlyspace.com/fractalveg.jpg]]
+}}}
+Displays as:
+[img[tooltip|http://wikitext.tiddlyspace.com/fractalveg.jpg]]
+!Image Links
+{{{
+[img[http://wikitext.tiddlyspace.com/fractalveg.jpg][http://www.flickr.com/photos/jermy/10134618/]]
+}}}
+Displays as:
+[img[http://wikitext.tiddlyspace.com/fractalveg.jpg][http://www.flickr.com/photos/jermy/10134618/]]
+!Floating Images with Text
+{{{
+[<img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]
+}}}
+[<img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]Displays as.
+{{{
+@@clear:both;display:block; @@
+}}}
+Will then clear the float.
+@@clear:both;display:block;Like this@@
+{{{
+[>img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]
+}}}
+[>img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]Displays as.@@clear:both;display:block; @@
+!See Also
+[[Image Macro]]
+
+
+
+
To import tiddlers from another ~TiddlyWiki file, or from an external website, follow these instructions.
+#If the tiddler is on an external site, first copy the URL of the site which includes the tiddler you'd like to download into your clipboard (eg http://www.tiddlytools.com)
+#Open your local ~TiddlyWiki file from your computer.
+#Click on the 'Backstage' link that you can see at the very top right hand side of the page.
+#In the menu, click on 'Import'. You will then see a window that looks like this - and you can follow the guidance from there. Note that you may need to grant authorisation to your ~TiddlyWiki file to import tiddlers - this is fine and expected.
+To import tiddlers from another ~TiddlyWiki file, or from an external website, follow these instructions.
+#If the tiddler is on an external site, first copy the URL of the site which includes the tiddler you'd like to download into your clipboard (eg http://www.tiddlytools.com)
+#Open your local ~TiddlyWiki file from your computer.
+#Click on the 'Backstage' link that you can see at the very top right hand side of the page.
+#In the menu, click on 'Import'. You will then see a window that looks like this - and you can follow the guidance from there. Note that you may need to grant authorisation to your ~TiddlyWiki file to import tiddlers - this is fine and expected.
+<<importTiddlers>>
+
+
+
+
+
When you type more than three characters in the search box at the upper right, any matching tiddlers are automatically displayed with the text highlighted. There's a couple of minor issues: the highlights don't get removed when you clear the search, and occasionally, on some browsers, keystrokes get missed if you type quickly so you may need to click the 'search' button to manually trigger the search.
+
+
+
+
[[Plugins]] are just tiddlers containing ~JavaScript code that is tagged with <<tag systemConfig>>. TiddlyWiki executes any [[Plugins]] as it loads; they can add [[Macros]] or otherwise extend and enhance the base code.
+
+The recommended way to install a plugin into your own copy of TiddlyWiki is to use ImportTiddlers (there are instructions for ManuallyInstallIngPlugins when required).
+
+
+
+
InterfaceOptions are displayed when you click the 'options' button on the right in your TiddlyWiki file. They are saved in a cookie on your browser, making them sticky between visits:
+<<<
+<<tiddler OptionsPanel>>
+<<<
+* The user name for edits should be set //before// starting to edit things
+* SaveBackups gives the option of whether to generate backup files 
+* AutoSave gives the option of automatically saving every time a change is made
+* RegExpSearch allows more complex search expressions
+* CaseSensitiveSearch does as its name implies
+
+
+
+
TiddlyWiki works on InternetExplorer 6.x and above under Windows. It also allows you to SaveChanges, albeit there are some annoying XP ServicePack2Problems and VistaIssues to work around. If you're having problems, make sure you've followed the appropriate [[downloading guidelines|Downloading]].
+
+Known problems with ~TiddlyWiki under InternetExplorer:
+* [[Gradient|GradientMacro]] fills sometimes appear in the wrong place until you move the mouse over the tiddler
+* Horizontal gradients don't work correctly
+* Links to tiddlers with multiple consecutive spaces in their titles is broken
+* Runs of spaces within tiddlers get conflated into a single space when you edit a tiddler. This is particularly annoying when using MonospacedText blocks
+
+
+
+
JavaScript is the programming language that powers the [[Web]]. In the browser it is used to script behaviours that breathe life into the static world of [[HTML]] and [[CSS]], and it is increasingly being selected as a ServerSide programming language.
+
+TiddlyWiki relies on JavaScript, and can't function properly without it. An alternative is to use a ServerSide adaptation like TiddlyWeb or TiddlySpace that can provide a static HTML view of TiddlyWiki content that doesn't require JavaScript in the browser.
+
+
+
+
Jeremy is the original creator of TiddlyWiki (nowadays, it's more of a [[group effort|Community]]). He works at [[BT|http://www.btplc.com/]] where he is Head of Open Source Innovation. He runs the [[Osmosoft]] team, and generally tries to help BT be better at understanding and participating in open source.
+
+I can be reached at {{{jeremy (at) osmosoft (dot) com}}}, and I regularly read and reply to messages on the [[TiddlyWiki Google Groups|Community]]. I (occasionally) write on my TiddlySpace at http://jermolene.com/ and you can also find me on [[Flickr|http://www.flickr.com/photos/jermy/]], [[del.icio.us|http://del.icio.us/jeremyruston]] and [[twitter|http://twitter.com/Jermolene]].
+
+
+
+
Offers a popup menu to jump directly to any of the currently open tiddlers. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar jump>>
+}}}
+
+
+
+
Access keys are shortcuts to common functions accessed by typing a letter with either the 'alt' (PC) or 'control-alt' (Mac) key:
+|!PC|!Mac|!Function|
+|~Alt-F|~Ctrl-Alt-F|Search|
+|~Alt-J|~Ctrl-Alt-J|NewJournal|
+|~Alt-N|~Ctrl-Alt-N|NewTiddler|
+|~Alt-S|~Ctrl-Alt-S|SaveChanges|
+These access keys are provided by the associated internal [[Macros]] for the functions above. The macro needs to be used in an open tiddler (or the MainMenu or SideBar) in order for the access keys to work.
+
+While editing a tiddler:
+* ~Control-Enter or ~Control-Return accepts your changes and switches out of editing mode (use ~Shift-Control-Enter or ~Shift-Control-Return to stop the date and time being updated for MinorChanges)
+* Escape abandons your changes and reverts the tiddler to its previous state
+
+In the search box:
+* Escape clears the search term
+
+
+
+
~TiddlyWiki is published under a BSD licence, and is owned by the not-for-profit [[UnaMesa Foundation|http://www.unamesa.org/]].
+
+
+
+
You've clicked on an example link! Click close to close this tiddler.
+
+
+
+
Line breaks can be forced explicitly:
+{{{
+Some text with a<br>line break in the middle
+}}}
+Displays as:
+Some text with a<br>line break in the middle
+
+
+
+
!Wiki Links
+Any words or phrases that are CamelCase or compound words - in which the elements are joined without spaces - will result in them becoming links to tiddlers with that name. 
+
+For example,
+{{{
+WikiWord
+}}}
+Displays as:
+WikiWord
+
+To stop this happening the words must be escaped:
+{{{
+~WikiWord
+}}}
+Displays as:
+~WikiWord
+
+Alternatively, a tiddler can be linked to using square brackets to encompass the whole tiddler title:
+{{{
+[[tiddler name]]
+}}}
+!Pretty Links
+Optionally, custom text can be added, separated by a pipe character (|)
+{{{
+[[alternative link text|tiddler name]]
+}}}
+Displays as:
+[[link to our WikiWord tiddler|WikiWord]]
+!External Links
+Writing the URL in the text results in a link to that external site:
+{{{
+http://osmosoft.com
+}}}
+Displays as:
+http://osmosoft.com
+Similar to pretty links alternative text can be used to link to external sites:
+{{{
+[[Visit the Osmosoft site|http://osmosoft.com]]
+}}}
+Displays as:
+[[Visit the Osmosoft site|http://osmosoft.com]]
+!Links to Other Spaces
+Link to a space by preceding it with {{{@}}}:
+{{{
+@about
+}}}
+Displays as:
+@about
+
+Suppress space linking with {{{~}}}:
+{{{
+~@about
+}}}
+~@about
+Link to a tiddler within another space:
+{{{
+TiddlyWiki@glossary
+[[TiddlySpace API]]@glossary
+[[Information about the HTTP interface|TiddlySpace API]]@glossary
+}}}
+Displays as:
+TiddlyWiki@glossary
+[[TiddlySpace API]]@glossary
+[[Information about the HTTP interface|TiddlySpace API]]@glossary
+
+
+
+
[[Link Formatting]] allows for links that open local or network folders. Depending on your browser and operating system, the folders are opened in Windows Explorer, the OS X Finder, or the browser itself.
+
+Edit this tiddler to see [[this link to a Windows network share|file://///server/share/folder/path/name]], [[this link to a Windows drive-mapped folder|file:///c:/folder/path/name]] and [[this link to a Unix-style folder|file:///folder/path/name]].
+
+
+
+
!Ordered Lists
+Lists can be ordered with numbers and letters:
+{{{
+#List item one
+##List item two
+###List item three
+}}}
+Displays as:
+#List item one
+##List item two
+###List item three
+!Unordered Lists
+Lists can be unordered:
+{{{
+*Unordered List Level 1
+**Unordered List Level 2
+***Unordered List Level 3
+}}}
+Displays as:
+*Unordered List Level 1
+**Unordered List Level 2
+***Unordered List Level 3
+!Definition Lists
+Definition lists can also be created:
+{{{
+;Title 1
+:Definition of title 1
+;Title 2
+:Definition of title 2
+}}}
+Displays as:
+;Title 1
+:Definition of title 1
+;Title 2
+:Definition of title 2
+
+See also [[Tagging Macro]]
+
+
+
+
[[allTags|allTags macro]]
+[[closeAll|closeAll macro]]
+[[list|list macro]]
+[[newJournal|newJournal macro]]
+[[newTiddler|newTiddler macro]]
+[[permaview|permaview macro]]
+[[saveChanges|saveChanges macro]]
+[[search|search macro]]
+[[slider|slider macro]]
+[[tabs|tabs macro]]
+[[tag|tag macro]]
+[[tagging|tagging macro]]
+[[tags|tags macro]]
+[[tiddler|tiddler macro]]
+[[timeline|timeline macro]]
+[[today|today macro]]
+[[version|version macro]]
+
+
+
+
HelloThere
+
+GettingStarted
+[[Customisation]]
+[[Examples]]
+
+[[Reference]]
+[[Help and Support]]
+[[Community]]
+[[RSS|RssFeed]]
+
+<<tiddler Download>>
+
+^^[img[favicon.ico]] TiddlyWiki <<version>>
+© 2011 [[UnaMesa|http://www.unamesa.org/]]^^
+
+
+
+
This tiddler contains a list of the tiddlers that appear in the MainMenu.  In the default TiddlySpace theme the MainMenu appears in the horizontal bar at the top of the page and in the default TiddlyWiki theme the MainMenu is on the left.
+
+
+
+
In some situations it can be useful to use the clipboard insead of InstallingPlugins using ImportTiddlers.
+
+# Open a new browser window and navigate to the TiddlyWiki site containing the macro you want
+# Double click the tiddler, or click the {{{source}}} button (on other sites it will sometimes be a {{{view}}} or {{{edit}}} button)
+# The entire text of the tiddler should be selected; if not select it manually with ~Control-A or ~Command-A
+# Copy the entire text of the tiddler to the clipboard
+# Open your TiddlyWiki file in a new browser window
+# Click {{{new tiddler}}} to create a new blank tiddler
+## Paste the contents of the clipboard into it's body
+## Set the title as appropriate
+## Add the tag {{{systemConfig}}}
+# Click {{{done}}} on the tiddler
+# SaveChanges
+# Reload your TiddlyWiki in the browser
+The plugin should now be available for use.
+
+
+
+
<!--{{{-->
+<link rel="shortcut icon" href="/recipes/tiddlywiki-com_public/tiddlers/favicon.ico" />
+<link href="/index.xml" rel="alternate"
+	type="application/rss+xml" title="tiddlywiki.com's RSS feed" />
+<!--}}}-->
+
+
+
+
|''URL:''|http://www.martinswiki.com/ |
+|''Description:''|Martin Buddens's Plugins |
+|''Author:''|MartinBudden |
+
+
+
+
There are several ways to incorporate mathematical formulae into TiddlyWiki, both of which let you to type ordinary LaTeX into a tiddler:
+1. Use the [[jsMath|http://www.math.union.edu/~dpvc/jsMath/]] library via Bob McElrath's [[TiddlyJsMath|http://bob.mcelrath.org/tiddlyjsmath.html]] plugin
+2. Use the [[MathJax|http://www.mathjax.org/]] library via [[PluginMathJax|http://math-template.tiddlyspace.com/#%5B%5BPluginMathJax%20v1.3%5D%5D]]
+
+An alternative is to use a service like [[CodeCogs|http://www.codecogs.com/latex/eqneditor.php]] that enables you to generate images of equations from LaTeX source that can be manually inserted into TiddlyWiki. Some examples of this technique are http://twmath.tiddlyspot.com/ and http://twequation.tiddlyspot.com/
+
+
+
+
MicroContent and MicroCopy being fashionable words for self-contained fragments of content that are smaller than an entire pages.
+
+Usually MicroContent is presented through some kind of aggregation that reduces the perceptual shock and resource cost of context switching (eg the way that blogs aggregating several entries onto a page, or Flickr presents a bunch of photos in an album). TiddlyWiki aggregates MicroContent items called [[tiddlers|Tiddler]] into pages that are loaded in one gulp and progressively displayed as the user clicks hypertext links to read them.
+
+
+
+
Sometimes it's useful to stop a minor change to a tiddler from causing it to rise to the top of the timeline. This can be done by pressing the Shift key while clicking the 'done' toolbar button, or with the ~Shift-Control-Enter key. This behaviour can be switched to become the default with one of the AdvancedOptions.
+
+
+
+
The 'Missing' option on the MoreTab shows you the names of tiddlers that you've referred to but not gone ahead to define. It can be useful during writing sessions to keep track of things you need to come back and fill out.
+
+
+
+
TiddlyWiki sites on the Internet work well on the iPhone, iPad and Android devices but the browsers on those devices do not allow documents to be saved.
+
+To work around this limitation, you can use these apps:
+
+* [[AndTidWiki|https://market.android.com/details?id=de.mgsimon.android.andtidwiki]] for Android Devices
+* [[TWMobile|http://itunes.apple.com/gb/app/twmobile/id381945222?mt=8]] for iPad
+* [[TWEdit|http://itunes.apple.com/gb/app/twedit/id409607956?mt=8]] for iPhone, iPad and iPod Touch
+* [[tiddlyNotes|http://itunes.apple.com/us/app/tiddlynotes-lite/id465933435?mt=8]] for iPhone, iPad and iPod Touch
+
+Note that these applications are produced by independent third parties, and are not associated with tiddlywiki.com or UnaMesa.
+
+
+
+
|''URL:''|http://mptw.tiddlyspot.com/|
+|''Description:''|a tiddlywiki distribution and plugins|
+|''Author:''|SimonBaird|
+
+
+
+
The functions of 'Timeline' and 'All' tabs have been around since the FirstVersion of TiddlyWiki. The purpose of the 'More' tab is to bring together some other, more specialised lists of tiddlers that can be useful during writing sessions. Currently, it offers lists of OrphanTiddlers and MissingTiddlers.
+
+
+
+
Within a CustomStyleSheet, you can include the text of another tiddler by including it in double square brackets. For example, if the tiddler MyFavouriteColour contains {{{#ff763e}}}, and the StyleSheet tiddler contained:
+
+{{{
+#mainMenu {background-color:[[MyFavouriteColour]];}
+}}}
+
+Then, the effect is that each CSS declaration will be set to {{{background-color: #ff763e;}}}.
+
+In practice, for small bits of text like a colour, it makes sense to use TiddlerSlicing format to reference a chunk of text within a tiddler. See ColorPalette and StyleSheetColors for an example.
+
+Of course, you can use this mechanism to redirect any part of a stylesheet, not just colours. And you can nest references for more complex effects.
+
+
+
+
A PageTemplate, ViewTemplate or EditTemplate can include the text of another tiddler by including it in double square brackets. For example:
+
+{{{
+<div>
+[[MyHeader]]
+</div>
+}}}
+
+You can also use TiddlerSlicing format to include a smaller chunk of the text of a tiddler.
+
+
+
+
TiddlyWiki is published under a BSD OpenSourceLicense that gives you the freedom to use it pretty much however you want, including for commercial purposes, as long as you keep the copyright notice. (You can see the full license text by doing a 'view source' in your browser).
+
+
+
+
~TiddlyWiki works with all recent versions of Opera, and can save changes using the TiddlySaver Java applet. If you're experiencing problems, make sure you've followed the appropriate [[downloading guidelines|Downloading]].
+
+
+
+
The 'Orphans' option on the MoreTab shows you the names of tiddlers that aren't linked to from any other tiddlers - in other words, tiddlers that there is no way for readers to find other than searching for them.
+
+
+
+
Osmosoft Limited was founded by JeremyRuston in 2005, and acquired by [[BT|http://btplc.com]] in 2007.
+
+Osmosoft is now a small team within [[BT|http://www.btplc.com/]] that focuses on showing the value of open source working methods. It does this through working with the community on the ongoing development of TiddlyWiki, TiddlyWeb and TiddlySpace, and applying them to solve problems within BT.
+
+See http://www.osmosoft.com/ for more details.
+
+
+
+
The ParameterParser is used in several places in TiddlyWiki:
+* to process the StartupParameters after the '#' in a TiddlyWiki URL
+* to process the DefaultTiddlers list
+* to process the parameters to Macros
+* to process tag lists when editing a tiddler
+It supports a list of parameters each of the form "name:value". For example:
+{{{
+name:John   location:"Isle of Wight"   [[dietary needs]]:none   really:'yes, really'
+}}}
+Names and values that need to contain spaces may be quoted with single- or double-quotes or double-square brackets. The parser is generally tolerant of additional spaces.
+
+When processing macro parameters, names and values may also be quoted with double-braces which causes them to be evaluated as a [[JavaScript|http://en.wikipedia.org/wiki/JavaScript]] expression. For example:
+{{{
+title:{{window.title}}
+}}}
+
+The ParameterParser will cope with either the name or the value being omitted, and will substitute a specified default. This is how the StartupParameters work; the default parameter name is specified as 'open'.
+
+
+
+
|''URL:''|http://bradleymeck.tiddlyspot.com/|
+|''Description:''|Resources that are Ripe for the Picking|
+|''Author:''|~BradleyMeck|
+
+
+
+
|Standard Periodic Table (ref. Wikipedia)|c
+|| !1 | !2 |!| !3 | !4 | !5 | !6 | !7 | !8 | !9 | !10 | !11 | !12 | !13 | !14 | !15 | !16 | !17 | !18 |
+|!1|bgcolor(#a0ffa0): @@color(red):H@@ |>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>||bgcolor(#c0ffff): @@color(red):He@@ |
+|!2|bgcolor(#ff6666): Li |bgcolor(#ffdead): Be |>|>|>|>|>|>|>|>|>|>||bgcolor(#cccc99): B |bgcolor(#a0ffa0): C |bgcolor(#a0ffa0): @@color(red):N@@ |bgcolor(#a0ffa0): @@color(red):O@@ |bgcolor(#ffff99): @@color(red):F@@ |bgcolor(#c0ffff): @@color(red):Ne@@ |
+|!3|bgcolor(#ff6666): Na |bgcolor(#ffdead): Mg |>|>|>|>|>|>|>|>|>|>||bgcolor(#cccccc): Al |bgcolor(#cccc99): Si |bgcolor(#a0ffa0): P |bgcolor(#a0ffa0): S |bgcolor(#ffff99): @@color(red):Cl@@ |bgcolor(#c0ffff): @@color(red):Ar@@ |
+|!4|bgcolor(#ff6666): K |bgcolor(#ffdead): Ca ||bgcolor(#ffc0c0): Sc |bgcolor(#ffc0c0): Ti |bgcolor(#ffc0c0): V |bgcolor(#ffc0c0): Cr |bgcolor(#ffc0c0): Mn |bgcolor(#ffc0c0): Fe |bgcolor(#ffc0c0): Co |bgcolor(#ffc0c0): Ni |bgcolor(#ffc0c0): Cu |bgcolor(#ffc0c0): Zn |bgcolor(#cccccc): Ga |bgcolor(#cccc99): Ge |bgcolor(#cccc99): As |bgcolor(#a0ffa0): Se |bgcolor(#ffff99): @@color(green):Br@@ |bgcolor(#c0ffff): @@color(red):Kr@@ |
+|!5|bgcolor(#ff6666): Rb |bgcolor(#ffdead): Sr ||bgcolor(#ffc0c0): Y |bgcolor(#ffc0c0): Zr |bgcolor(#ffc0c0): Nb |bgcolor(#ffc0c0): Mo |bgcolor(#ffc0c0): Tc |bgcolor(#ffc0c0): Ru |bgcolor(#ffc0c0): Rh |bgcolor(#ffc0c0): Pd |bgcolor(#ffc0c0): Ag |bgcolor(#ffc0c0): Cd |bgcolor(#cccccc): In |bgcolor(#cccccc): Sn |bgcolor(#cccc99): Sb |bgcolor(#cccc99): Te |bgcolor(#ffff99): I |bgcolor(#c0ffff): @@color(red):Xe@@ |
+|!6|bgcolor(#ff6666): Cs |bgcolor(#ffdead): Ba |bgcolor(#ffbfff):^^*1^^|bgcolor(#ffc0c0): Lu |bgcolor(#ffc0c0): Hf |bgcolor(#ffc0c0): Ta |bgcolor(#ffc0c0): W |bgcolor(#ffc0c0): Re |bgcolor(#ffc0c0): Os |bgcolor(#ffc0c0): Ir |bgcolor(#ffc0c0): Pt |bgcolor(#ffc0c0): Au |bgcolor(#ffc0c0): @@color(green):Hg@@ |bgcolor(#cccccc): Tl |bgcolor(#cccccc): Pb |bgcolor(#cccccc): Bi |bgcolor(#cccc99): Po |bgcolor(#ffff99): At |bgcolor(#c0ffff): @@color(red):Rn@@ |
+|!7|bgcolor(#ff6666): Fr |bgcolor(#ffdead): Ra |bgcolor(#ff99cc):^^*2^^|bgcolor(#ffc0c0): Lr |bgcolor(#ffc0c0): Rf |bgcolor(#ffc0c0): Db |bgcolor(#ffc0c0): Sq |bgcolor(#ffc0c0): Bh |bgcolor(#ffc0c0): Hs |bgcolor(#ffc0c0): Mt |bgcolor(#ffc0c0): Ds |bgcolor(#ffc0c0): Rg |bgcolor(#ffc0c0): @@color(green):Uub@@ |bgcolor(#cccccc): Uut |bgcolor(#cccccc): Uuq |bgcolor(#cccccc): Uup |bgcolor(#cccccc): Uuh |bgcolor(#fcfecc): @@color(#cccccc):Uus@@ |bgcolor(#ecfefc): @@color(#cccccc):Uuo@@ |
+
+| !Lanthanides^^*1^^|bgcolor(#ffbfff): La |bgcolor(#ffbfff): Ce |bgcolor(#ffbfff): Pr |bgcolor(#ffbfff): Nd |bgcolor(#ffbfff): Pm |bgcolor(#ffbfff): Sm |bgcolor(#ffbfff): Eu |bgcolor(#ffbfff): Gd |bgcolor(#ffbfff): Tb |bgcolor(#ffbfff): Dy |bgcolor(#ffbfff): Ho |bgcolor(#ffbfff): Er |bgcolor(#ffbfff): Tm |bgcolor(#ffbfff): Yb |
+| !Actinides^^*2^^|bgcolor(#ff99cc): Ac |bgcolor(#ff99cc): Th |bgcolor(#ff99cc): Pa |bgcolor(#ff99cc): U |bgcolor(#ff99cc): Np |bgcolor(#ff99cc): Pu |bgcolor(#ff99cc): Am |bgcolor(#ff99cc): Cm |bgcolor(#ff99cc): Bk |bgcolor(#ff99cc): Cf |bgcolor(#ff99cc): Es |bgcolor(#ff99cc): Fm |bgcolor(#ff99cc): Md |bgcolor(#ff99cc): No |
+
+*Chemical Series of the Periodic Table
+**@@bgcolor(#ff6666): Alkali metals@@
+**@@bgcolor(#ffdead): Alkaline earth metals@@
+**@@bgcolor(#ffbfff): Lanthanides@@
+**@@bgcolor(#ff99cc): Actinides@@
+**@@bgcolor(#ffc0c0): Transition metals@@
+**@@bgcolor(#cccccc): Poor metals@@
+**@@bgcolor(#cccc99): Metalloids@@
+**@@bgcolor(#a0ffa0): Nonmetals@@
+**@@bgcolor(#ffff99): Halogens@@
+**@@bgcolor(#c0ffff): Noble gases@@
+
+*State at standard temperature and pressure
+**those in @@color(red):red@@ are gases
+**those in @@color(green):green@@ are liquids
+**those in black are solids
+
+
+
+
PermaView is a button in the right-hand sidebar that sets the browser address bar to a URL embodying all the currently open tiddlers in the order that they are currently shown. To use it, arrange the open tiddlers that you want, click the permaview button, copy the URL from the browser address bar, and then paste it into an email, web page or whatever.
+
+On some browsers, PermaView can be unreliable if any of the tiddler titles include characters that have special meanings in URLs (like "+" and "\") or are outside the basic ANSI character set.
+
+
+
+
Changes the browser address bar to a permalink to the current tiddler. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar permalink>>
+}}}
+
+On some browsers, the PermalinkCommand can be unreliable if the tiddler title includes characters that have special meanings in ~URLs (like "+" and "\") or are outside the basic ANSI character set.
+
+
+
+
TiddlyWiki now has the ability to save options in the TiddlyWiki document itself (in addition to the ability to save options in cookies). Persistent options are stored in the [[SystemSettings]] tiddler. Persistent options are sometimes called "baked" options, since the value of the cookie is "baked" into the TiddlyWiki.
+
+As an example, let's look at the {{{chkAnimate}}} option; this is currently stored as a cookie. You can get its value to be persistent by adding it to the [[SystemSettings]] tiddler. If there is no SystemSettings tiddler, you need to create it. Then add, for example:
+<<<
+chkAnimate: true
+<<<
+This has two effects, it makes the {{{chkAnimate}}} option persistent, and it gives the option a value (in this case {{{true}}}, it could equally well have been {{{false}}}).
+
+The option can still be changed in the normal way (by ticking the box in the options panel); it can also be changed by editing the value in the [[SystemSettings]] tiddler. If the option is removed from the [[SystemSettings]] tiddler, then its value will be retained, but it will stored in a cookie.
+
+!!Under the hood
+The "source" of an option is controlled by the {{{config.optionsSource[optionName]}}} variable. This can take one of two values:
+# {{{null}}} - the option is stored in a cookie, this is the ''default''.
+# {{{setting}}} - the option is stored in the [[SystemSettings]] tiddler. 
+The {{{config.optionsSource[]}}} variable is controlled by the values in the [[SystemSettings]] tiddler. If the option exists in [[SystemSettings]], then {{{config.optionsSource[optionName]}}} is set to {{{setting}}}, and the option is persisted.
+
+
+
+
Sometimes text can inadvertently match TiddlyWiki formatting instructions - particularly program code, or text pasted from elsewhere. In these situations you can either use MonospacedText or you can accomplish the same thing without the monospaced effect like this:
+{{{
+This is AnotherLink, this is a copyright symbol &copy; and this site is called <<tiddler SiteTitle>>
+<nowiki>This is AnotherLink, this is a copyright symbol &copy; and this site is called <<tiddler SiteTitle>></nowiki>
+"""This is AnotherLink, this is a copyright symbol &copy; and this site is called <<tiddler SiteTitle>>"""
+}}}
+Which displays as:
+This is AnotherLink, this is a copyright symbol &copy; and this site is called <<tiddler SiteTitle>>
+<nowiki>This is AnotherLink, this is a copyright symbol &copy; and this site is called <<tiddler SiteTitle>></nowiki>
+"""This is AnotherLink, this is a copyright symbol &copy; and this site is called <<tiddler SiteTitle>>"""
+
+
+
+
It is recommended that [[Plugins]]  start with some standard information in TiddlerSlicing format. For example, see the ExamplePlugin:
+{{{
+|''Name:''|ExamplePlugin|
+|''Description:''|To demonstrate how to write TiddlyWiki plugins|
+|''Version:''|2.0.2|
+|''Date:''|Jul 12, 2006|
+|''Source:''|http://www.tiddlywiki.com/#ExamplePlugin|
+|''Author:''|JeremyRuston (jeremy (at) osmosoft (dot) com)|
+|''License:''|[[BSD open source license]]|
+|''~CoreVersion:''|2.1.0|
+|''Browser:''|Firefox 1.0.4+; Firefox 1.5; InternetExplorer 6.0|
+}}}
+At the moment, only ~CoreVersion affects how [[Plugins]] are processed: if the ~CoreVersion is specified for a plugin, TiddlyWiki will only execute the plugin if the core code version matches or exceeds the version specified. For example, if you specify a ~CoreVersion of 2.2, version 2.1.x of TiddlyWiki will refuse to execute the plugin.
+
+To indicate an error, plugins should just {{{throw}}} an exception. The text of the exception will be displayed in the PluginManager.
+
+
+
+
To make plugins, stylesheets and templates easier to read, you can use special alternative formatting for monospaced blocks.
+
+In [[JavaScript|http://en.wikipedia.org/wiki/JavaScript]] code:
+{{{
+//{{{
+var id = document.getElementById("mainMenu");
+//}}}
+}}}
+In HTML templates:
+{{{
+<!--{{{-->
+<div id="MainMenu">
+</div>
+<!--}}}-->
+}}}
+In CSS stylesheets
+{{{
+/*{{{*/
+div {color: #ff0000;}
+/*}}}*/
+}}}
+It will be displayed as:
+//{{{
+var id = document.getElementById("mainMenu");
+//}}}
+
+<!--{{{-->
+<div id="MainMenu">
+</div>
+<!--}}}-->
+
+/*{{{*/
+div {color: #ff0000;}
+/*}}}*/
+
+
+
+
This tiddler contains the command to display the plugin manager: 
+{{{
+<<plugins>>
+}}}
+
+
+
+
~TiddlyWiki can be extended by [[installing plugins|InstallingPlugins]] that implement a wide variety of features (including [[Macros]], [[themes|http://tiddlythemes.com/]] or tweaks).
+
+Community member Dave Gifford oversees an extensive catalogue of available plugins at [[TiddlyVault|http://tiddlyvault.tiddlyspot.com/]], and Eric Shulman maintains his own impressive collection of plugins at [[TiddlyTools|http://www.tiddlytools.com]]. More recently, Saq Imtiaz has built a service which lists plugins from all around the web in a single place, at [[plugins.tiddlywiki.org|http://plugins.tiddlywiki.org/]]
+
+When you've chosen the plugin you'd like to import, follow the directions in ImportTiddlers (just make sure to tag the plugin {{{systemConfig}}}).
+
+See http://tiddlywikidev.tiddlyspace.com/ for information on writing plugins for TiddlyWiki.
+
+
+
+
|''URL:''|http://ptw.sourceforge.net/ptwe.html|
+|''Description:''|~BramChen's Extensions for TiddlyWiki|
+|''Author:''|~BramChen|
+
+
+
+
~TiddlyWiki has appeared in a number of publications and articles:
+* A [[book about TiddlyWiki|http://www.flickr.com/photos/philhawksworth/1933329531/]] published in Taiwain
+* A [[guide|http://www.broowaha.com/articles/7671/a-personal-knowledgebase-solution-tiddlywiki-and-dropbox]] on using ~TiddlyWiki with ~DropBox.
+* [[Wikify Yourself|http://eriwen.com/tools/wikify-yourself/]] - Why every programmer should have a Tiddlywiki
+* Mike Mahemoff has created a [[screencast|http://softwareas.com/tiddlywiki-screencast-forum-in-15-minutes]] showing how to create a multi-user forum with ~TiddlyWiki in 15 minutes.
+* Pao-hsu Shih, a professor at Taipai University in Taiwan, has written an [[Introduction to TiddlyWiki|http://www.wunan.com.tw/bookdetail.asp?no=8852]] in Chinese
+* Dmitri Popov discusses ~TiddlyWiki in [[Writer for Writers and Advanced Users|http://www.lulu.com/content/221513]]
+* Jeremy Wagstaff's [[The Power of Tiddly|http://www.loosewireblog.com/2007/11/the-power-of-ti.html]]
+* Eric Shulman talking about TiddlyWiki at TiddlyWest, a meeting of TiddlyWiki enthusiasts in San Francisco in 2008:
+** http://blip.tv/play/gtQ9vexsAg
+** http://blip.tv/play/gtQ9ve0NAg
+** http://blip.tv/play/gtQ9ve0vAg
+
+
+
+
!Block Quotations
+Blocks of text can be displayed as quotations:
+{{{
+<<<
+Steve Jobs: "computers are like a bicycle for our minds"
+<<<
+}}}
+Displays as:
+<<<
+Steve Jobs: "computers are like a bicycle for our minds"
+<<<
+!Nested Quotations 
+Quotes can be displayed on multi-levels:
+{{{
+> blockquote, level 1
+>> blockquote, level 2
+>>> blockquote, level 3
+}}}
+Displays as:
+> blockquote, level 1
+>> blockquote, level 2
+>>> blockquote, level 3
+
+
+
+
~TiddlyWiki has received many favourable reviews:
+* "~TiddlyWiki offers a glimpse of how things are changing in terms of how people think about software... a new beginning for simple software." -- //Jeremy Wagstaff, [[WSJ.com|http://groups.google.com/group/TiddlyWiki/browse_thread/thread/53c7b7686b9bb5c2/122f1b2146d2ba6d?q=wsj&rnum=1]]//
+* "The original ~TiddlyWiki by Jeremy Ruston is, without a doubt, one of the most amazing dynamic web apps I've ever seen (sorry Gmail.)" -- [[Lifehacker.com recommendation|http://www.lifehacker.com/software/productivity/getting-things-done-tiddlywiki-102953.php]]
+* "It's blowing my mind." -- //Evan Williams, founder of Blogger, Twitter and Odeo, [[EvHead|http://evhead.com/2005/05/tiddlywiki-reusable-non-linear.asp]]//
+* "What I love most about ~TiddlyWiki is that it is quite easy to use but incredibly flexible." -- //Ed Sim of Dawntreader Ventures, [[BeyondVC|http://www.beyondvc.com/2005/10/tiddlywiki.html]]//
+* "~TiddlyWiki is completely blowing my mind... Completely tripped out. Try it and you'll see what I mean." -- //Russell Beattie of Yahoo!, [[Russell Beattie's Notebook|http://www.russellbeattie.com/notebook/1008896.html]]//
+* "OK, this is the first wiki interface I’ve seen that has real potential. Dunno quite why exactly, but this blows my mind." -- //Jason Kottke, [[Kottke's Remaindered Links|http://www.kottke.org/remainder/04/09/6574.html]]//
+There is also the [[The Great TiddlyWiki Viral Mass Interview Challenge|http://interview.tiddlyspace.com/]], a community project to record the impact that TiddlyWiki has had on the people that use it and build it.
+
+
+
+
|''URL:''|http://solo.dc3.com/tw/|
+|''Description:''|Bob Denny's extensions to TiddlyWiki|
+|''Author:''|~BobDenny|
+
+
+
+
|!Formatting|!Macros|!Shadow tiddlers|!Miscellaneous|
+|<<tiddler WikiTextContent>>|<<tiddler MacrosContent>>|<<tiddler ShadowTiddlersContent>>|<<tiddler ReferenceContent>>|
+
+
+
+
InterfaceOptions
+SaveChanges
+ToolbarButtons
+SafeMode
+KeyboardShortcuts
+StartupParameters
+SpecialTags
+SpecialTiddlers
+PermaView
+HtmlEntities
+[[Tags]]
+IncrementalSearch
+RegExpSearch
+SaveEmptyTemplate
+CustomStyleSheet
+NestedStyleSheets
+NestedTemplates
+TiddlerSlicing
+CustomMarkup
+MobileDevices
+
+
+
+
Since the FirstVersion of TiddlyWiki, the ReferencesButton has been implemented as a canned search for the name of the current tiddler. That approach was a bit disruptive because of the way that a search operation wipes the current reading state of the document.
+
+The new implementation offers a popup menu of the names of all the referring tiddlers. It can be consulted without disturbing any tiddlers that are currently open.
+
+
+
+
Offers a popup menu displaying the tiddlers that link to the current one. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar references>>
+}}}
+
+
+
+
RegExpSearch is an [[advanced option|AdvancedOptions]] uses [[JavaScript|http://en.wikipedia.org/wiki/JavaScript]]'s [[RegExp syntax|http://www.programmershelp.co.uk/docs/javascript/regexp.html#1193188]] to allow flexible searches.
+
+
+
+
TiddlyWiki's RSS feed is available [[here|http://www.tiddlywiki.com/index.xml]]. You can generate an RSS feed for your own TiddlyWiki using the GenerateAnRssFeed option.
+
+
+
+
Safari can SaveChanges using the TiddlySaver Java applet.
+
+There are still some issues with Safari:
+* Permalinks with Unicode characters in them (like [[this one|http://avm.free.fr/tidlipo.html#AdaptationFran%C3%A7aise]]) don't work properly
+
+
+
+
SafeMode can be selected by putting {{{#start:safe}}} (see StartupParameters) on the end of the TiddlyWiki URL. It stops TiddlyWiki from executing any [[Plugins]], modifying shadow tiddlers, or reading/writing cookies. It can be useful for tracking down problems caused by rogue [[Plugins]].
+
+
+
+
This is one of the InterfaceOptions you can use to customize TiddlyWiki. It determines whether TiddlyWiki creates a backup file each time you SaveChanges. I'd suggest keeping it switched on for safety!
+
+
+
+
If you've followed the appropriate [[Downloading]] guidelines, then you can save your changes as you go along. (AutoSave makes this easy).
+
+It's important to save changes using ~TiddlyWiki's built in saving functionality (rather than the browser's 'Save As' function - [[here's why|SaveUnpredictabilities]]). Your local version of ~TiddlyWiki will have a button on the right hand side that says 'save changes' - that's the button you'll need to use.
+
+It's worthwhile configuring your backup settings. You can edit the AdvancedOptions to decide when backups are made and where they're saved.
+
+If you're still experiencing problems, the following links will help: 
+*General guidance on FireFox, InternetExplorer, [[Opera]], [[Camino]] or [[Safari]]
+* if you're using InternetExplorer on Windows you might run into XP ServicePack2Problems or VistaIssues
+
+
+
+
This causes a blank, template TiddlyWiki to be saved alongside your file when you SaveChanges. It's intended to help people who are distributing TiddlyWikiAdaptations, and isn't needed when you're an end-user of TiddlyWiki.
+
+The template TiddlyWiki is called 'empty.html'.
+
+
+
+
Saves any pending edits to the current tiddler, and switches it to the default view. It is used with the ToolbarMacro like this:
+{{{
+<<toolbar saveTiddler>>
+}}}
+
+
+
+
Several people have reported problems with reusing TiddlyWiki when they have used the File/Save command of their browser to save it. The issue is that some browsers (notably FireFox) don't save the text of the HTML file exactly as it appears on the server, but rather save a snapshot of the current state of the page. In the case of a highly dynamic page like TiddlyWiki, this leads to all sorts of peculiarness...
+
+TiddlyWiki now displays a warning if it thinks that it has been saved wrongly.
+
+
+
+
The default version of ~TiddlyWiki includes basic search functionality. When a search term is entered, all tiddlers that contain this term are opened, with the search term highlighted wherever it appears.
+
+However, using the [[plugin architecture|Plugins]] of ~TiddlyWiki, the [[community|Help and Support]] has written a number of search plugins which can be installed on your local ~TiddlyWiki files easily. The most popular search plugins are:
+*Eric Shulman's ~SearchOptionsPlugin - a highly configurable plugin that produces a list of tiddler titles with options for advanced searches. Available at http://www.TiddlyTools.com/#SearchOptionsPlugin
+*Udo Borkowski's ~YourSearchPlugin - opens the search results in a large iFrame in the same style as Google (result, then synopsis). Available at http://tiddlywiki.abego-software.de/
+*Frederik Dohr's ~SimpleSearchPlugin (which this site is using) - copy and paste the code on [[this page|http://svn.tiddlywiki.org/Trunk/contributors/FND/plugins/SimpleSearchPlugin.js]] into a new tiddler, tag the tiddler with systemConfig, and save / re-open your ~TiddlyWiki.
+
+
+
+
+
One of the neatest features of TiddlyWiki is that it is entirely self-contained in a single HTML file - even including graphics like the GradientMacro. The file contains the actual hypertext document, and the ~JavaScript, ~CascadingStyleSheets and HTML necessary to both view and edit it. This means that it is trivial to host a TiddlyWiki on a website, or to distribute one by email. And anyone with a reasonably recent web browser will be able to read and edit it.
+
+
+
+
Out of the box, ~TiddlyWiki doesn't have a Server Side back end. In many applications that's a great strength because it means that you can work with ~TiddlyWiki without having to be connected to the Internet or, because it's SelfContained, installing any software.
+
+In other applications, a ~ServerSide can be very useful, particularly if you want to edit a ~TiddlyWiki while it's online, or you need lots of people to be able to edit a ~TiddlyWiki at the same time. The development [[Community]] has come up with several ~ServerSide implementations that are suitable for a range of applications, listed under HostedOptions.
+
+
+
+
Internet Explorer Windows XP ~SP2 seems to have a magical ability to keep track of html files that have been downloaded from the internet and saved on an NTFS drive. By storing additional data in an [[alternate data stream|http://www.microsoft.com/technet/sysinternals/FileAndDisk/Streams.mspx]], it manages to keep them in the 'Internet' zone regardless of attempts to rename or modify the file. But, in order to be able to SaveChanges, TiddlyWiki needs to run in the 'My Computer' zone.
+
+The solution is to right-click on the TiddlyWiki html file and choose //Properties//. If the file is blocked, you'll see an 'Unblock' button on the resulting property sheet that removes the protection and allows the file to open in the 'My Computer' zone. Then open the file in Internet Explorer - it might put up its information bar asking you whether you want to run it. You need to 'Allow blocked content' to let TiddlyWiki do its stuff.
+<<<
+If you find yourself running into the information bar frequently, you can disable it by visiting the "Options" dialog and, on the "Advanced" tab make sure that "Allow active content to run in files on My Computer" is checked.
+
+Alternatively, you can rename the file to {{{*.hta}}} which has the added bonus of automatically granting all necessary save permissions.
+<<<
+This is all a bit frustrating. An easy alternative is to use FireFox, which seems to do the trick on all platforms.
+
+
+
+
ShadowTiddlers are special tiddlers that have default values that take over if they are undefined or deleted. For example, PageTemplate and StyleSheetColors are both shadow tiddlers.
+
+ShadowTiddlers make it harder to render a TiddlyWiki inoperative by accidentally deleting something important.  You can see a list of shadow tiddlers in the Shadowed tab under the More tab in the right hand column.  When you create a tiddler with the same title you override the underlying shadow tiddler. 
+----
+<<list shadowed>>
+
+
+
+
[[ColorPalette|ColorPalette shadows]]
+[[DefaultTiddlers|DefaultTiddlers shadows]]
+[[EditTemplate|EditTemplate shadows]]
+[[GettingStarted|GettingStarted shadows]]
+[[MainMenu|MainMenu shadows]]
+[[PageTemplate|PageTemplate shadows]]
+[[PluginManager|PluginManager shadows]]
+[[SideBarOptions|SideBarOptions shadows]]
+[[SiteSubtitle|SiteSubtitle shadows]]
+[[SiteTitle|SiteTitle shadows]]
+[[StyleSheet|StyleSheet shadows]]
+[[TabAll|TabAll shadows]]
+[[TabMoreMissing|TabMoreMissing shadows]]
+[[TabMoreOrphans|TabMoreOrphans shadows]]
+[[TabMoreShadowed|TabMoreShadowed shadows]]
+[[TabTimeline|TabTimeline shadows]]
+[[ViewTemplate|ViewTemplate shadows]]
+[[WindowTitle|WindowTitle shadows]]
+
+
+
+
<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">>
+
+
+
+
This tiddler contains the commands that produce the buttons contained in the slider above the timeline on the right. 
+In ~TiddlySpace they are displayed on the right hand side of the menu bar.
+By default it is set to:
+{{{
+<<closeAll>><<permaview>><<newTiddler>>
+}}}
+
+You can edit it to include anything else you want.
+
+
+
+
/***
+|''Name''|SimpleSearchPlugin|
+|''Description''|displays search results as a simple list of matching tiddlers|
+|''Authors''|FND|
+|''Version''|0.4.1|
+|''Status''|stable|
+|''Source''|http://devpad.tiddlyspot.com/#SimpleSearchPlugin|
+|''CodeRepository''|http://svn.tiddlywiki.org/Trunk/contributors/FND/plugins/SimpleSearchPlugin.js|
+|''License''|[[Creative Commons Attribution-ShareAlike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/]]|
+|''Keywords''|search|
+!Revision History
+!!v0.2.0 (2008-08-18)
+* initial release
+!!v0.3.0 (2008-08-19)
+* added Open All button (renders Classic Search option obsolete)
+* sorting by relevance (title matches before content matches)
+!!v0.4.0 (2008-08-26)
+* added tag matching
+!To Do
+* tag matching optional
+* animations for container creation and removal
+* when clicking on search results, do not scroll to the respective tiddler (optional)
+* use template for search results
+!Code
+***/
+//{{{
+if(!version.extensions.SimpleSearchPlugin) { //# ensure that the plugin is only installed once
+version.extensions.SimpleSearchPlugin = { installed: true };
+
+if(!config.extensions) { config.extensions = {}; }
+
+config.extensions.SimpleSearchPlugin = {
+	heading: "Search Results",
+	containerId: "searchResults",
+	btnCloseLabel: "close",
+	btnCloseTooltip: "dismiss search results",
+	btnCloseId: "search_close",
+	btnOpenLabel: "Open all",
+	btnOpenTooltip: "open all search results",
+	btnOpenId: "search_open",
+
+	displayResults: function(matches, query) {
+		story.refreshAllTiddlers(true); // update highlighting within story tiddlers
+		var el = document.getElementById(this.containerId);
+		query = '"""' + query + '"""'; // prevent WikiLinks
+		if(el) {
+			removeChildren(el);
+		} else { //# fallback: use displayArea as parent
+			var container = document.getElementById("displayArea");
+			el = document.createElement("div");
+			el.id = this.containerId;
+			el = container.insertBefore(el, container.firstChild);
+		}
+		var msg = "!" + this.heading + "\n";
+		if(matches.length > 0) {
+			msg += "''" + config.macros.search.successMsg.format([matches.length.toString(), query]) + ":''\n";
+			this.results = [];
+			for(var i = 0 ; i < matches.length; i++) {
+				this.results.push(matches[i].title);
+				msg += "* [[" + matches[i].title + "]]\n";
+			}
+		} else {
+			msg += "''" + config.macros.search.failureMsg.format([query]) + "''"; // XXX: do not use bold here!?
+		}
+		createTiddlyButton(el, this.btnCloseLabel, this.btnCloseTooltip, config.extensions.SimpleSearchPlugin.closeResults, "button", this.btnCloseId);
+		wikify(msg, el);
+		if(matches.length > 0) { // XXX: redundant!?
+			createTiddlyButton(el, this.btnOpenLabel, this.btnOpenTooltip, config.extensions.SimpleSearchPlugin.openAll, "button", this.btnOpenId);
+		}
+	},
+
+	closeResults: function() {
+		var el = document.getElementById(config.extensions.SimpleSearchPlugin.containerId);
+		removeNode(el);
+		config.extensions.SimpleSearchPlugin.results = null;
+		highlightHack = null;
+	},
+
+	openAll: function(ev) {
+		story.displayTiddlers(null, config.extensions.SimpleSearchPlugin.results);
+		return false;
+	}
+};
+
+config.shadowTiddlers.StyleSheetSimpleSearch = "/*{{{*/\n" +
+	"#" + config.extensions.SimpleSearchPlugin.containerId + " {\n" +
+	"\toverflow: auto;\n" +
+	"\tpadding: 5px 1em 10px;\n" +
+	"\tbackground-color: [[ColorPalette::TertiaryPale]];\n" +
+	"}\n\n" +
+	"#" + config.extensions.SimpleSearchPlugin.containerId + " h1 {\n" +
+	"\tmargin-top: 0;\n" +
+	"\tborder: none;\n" +
+	"}\n\n" +
+	"#" + config.extensions.SimpleSearchPlugin.containerId + " ul {\n" +
+	"\tmargin: 0.5em;\n" +
+	"\tpadding-left: 1.5em;\n" +
+	"}\n\n" +
+	"#" + config.extensions.SimpleSearchPlugin.containerId + " .button {\n" +
+	"\tdisplay: block;\n" +
+	"\tborder-color: [[ColorPalette::TertiaryDark]];\n" +
+	"\tpadding: 5px;\n" +
+	"\tbackground-color: [[ColorPalette::TertiaryLight]];\n" +
+	"}\n\n" +
+	"#" + config.extensions.SimpleSearchPlugin.containerId + " .button:hover {\n" +
+	"\tborder-color: [[ColorPalette::SecondaryMid]];\n" +
+	"\tbackground-color: [[ColorPalette::SecondaryLight]];\n" +
+	"}\n\n" +
+	"#" + config.extensions.SimpleSearchPlugin.btnCloseId + " {\n" +
+	"\tfloat: right;\n" +
+	"\tmargin: -5px -1em 5px 5px;\n" +
+	"}\n\n" +
+	"#" + config.extensions.SimpleSearchPlugin.btnOpenId + " {\n" +
+	"\tfloat: left;\n" +
+	"\tmargin-top: 5px;\n" +
+	"}\n" +
+	"/*}}}*/";
+store.addNotification("StyleSheetSimpleSearch", refreshStyles);
+
+// override Story.search()
+Story.prototype.search = function(text, useCaseSensitive, useRegExp) {
+	highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(), useCaseSensitive ? "mg" : "img");
+	var matches = store.search(highlightHack, null, "excludeSearch");
+	var q = useRegExp ? "/" : "'";
+	config.extensions.SimpleSearchPlugin.displayResults(matches, q + text + q);
+};
+
+// override TiddlyWiki.search() to sort by relevance
+TiddlyWiki.prototype.search = function(searchRegExp, sortField, excludeTag, match) {
+	var candidates = this.reverseLookup("tags", excludeTag, !!match);
+	var primary = [];
+	var secondary = [];
+	var tertiary = [];
+	for(var t = 0; t < candidates.length; t++) {
+		if(candidates[t].title.search(searchRegExp) != -1) {
+			primary.push(candidates[t]);
+		} else if(candidates[t].tags.join(" ").search(searchRegExp) != -1) {
+			secondary.push(candidates[t]);
+		} else if(candidates[t].text.search(searchRegExp) != -1) {
+			tertiary.push(candidates[t]);
+		}
+	}
+	var results = primary.concat(secondary).concat(tertiary);
+	if(sortField) {
+		results.sort(function(a, b) {
+			return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);
+		});
+	}
+	return results;
+};
+
+} //# end of "install only once"
+//}}}
+
+
+
+
TiddlyWiki
+
+
+
+
In the SiteTitle tiddler you can define the title of your TiddlyWiki.  When you first create a TiddlyWiki it defaults to 
+{{{
+TiddlyWiki
+}}}
+However you can edit it to display anything that you choose.
+
+
+
+
http://tiddlywiki.com/
+
+
+
+
TiddlyWiki defines a small number of SpecialTags that are used to indicate that tiddlers should be treated differently in some way:
+* ''excludeSearch'': excludes a tiddler from search results
+* ''excludeLists'': excludes a tiddler from the lists in the sidebar tabs
+* ''systemConfig'': marks tiddlers that contain [[JavaScript|http://en.wikipedia.org/wiki/JavaScript]] that should be executed once TiddlyWiki has loaded
+* ''excludeMissing'': excludes a tiddler from the processing that generates the MissingTiddlers list. Use it when you have a tiddler that contains links to missing tiddlers and you don't want those missing links to appear in the MissingTiddlers list
+
+
+
+
~TiddlyWiki uses several "special tiddlers" to perform a number of essential functions as follows:
+*[[Configuration]] tiddlers configure the layout of the page, including the MainMenu, the SiteTitle, the SiteSubtitle and other features. 
+*A tiddler called DefaultTiddlers is used to list the tiddlers that are shown at startup. 
+*A tiddler called SaveChanges is automatically displayed if there's a problem with saving. Any of them can be edited with the changes taking effect immediately.
+* MoreTab, MissingTiddlers, OrphanTiddlers
+
+
+
+
TiddlyWiki obtains its StartupParameters from the //location// portion of it's URL (the bit after the '#'). At it's simplest, the StartupParameters can list the names of the tiddlers to be opened when the TiddlyWiki is opened:
+{{{
+http://www.tiddlywiki.com/#HelloThere JeremyRuston
+}}}
+In fact, that usage is equivalent to:
+{{{
+http://www.tiddlywiki.com/#open:HelloThere open:JeremyRuston
+}}}
+The complete list of commands is:
+|!Command |!Description |!Example |
+|open:title |Opens the tiddler with the specified title |http://www.tiddlywiki.com/#open:HelloThere |
+|start:safe |Switches to SafeMode |http://www.tiddlywiki.com/#start:safe |
+|search:text |Performs a search for the specified text |http://www.tiddlywiki.com/#search:jeremy |
+|tag:text |Displays tiddlers tagged with the specified tag |http://www.tiddlywiki.com/#tag:news |
+|newTiddler:title |Opens a new tiddler with the specified title in edit mode |http://www.tiddlywiki.com/#newTiddler:"This is a new tiddler" |
+|newJournal:titleFormat |Opens a new tiddler with the specified [[Date Format String|Date Formats]] |http://www.tiddlywiki.com/#newJournal:"YYYY MMM DD" |
+
+See the details of the underlying ParameterParser for more details.
+
+
+
+
/***
+This fixes a problem with the tabs slider
+***/
+/*{{{*/
+#sidebarTabs .button {
+margin:0em 0.2em;
+padding:0.2em 0.3em;
+display:block;
+}
+/*}}}*/
+/***
+This is a sample style definition to demonstrate [[CustomCssClass|CSS Formatting]] formatting
+***/
+/*{{{*/
+.wrappingClass {color: #666; background: #bbb;}
+/*}}}*/
+/***
+Prettyify
+***/
+/*{{{*/
+body {
+	font-size: 0.92em;
+	margin-bottom: 4em;
+}
+
+.tiddler .viewer {
+	font-size: 1.1em;
+}
+
+#contentWrapper .chunkyButton {
+	width: auto;
+}
+
+#contentWrapper span.chunkyButton {
+	border: none;
+}
+
+#contentWrapper span.chunkyButton a.button, #contentWrapper span.chunkyButton a:active.button {
+	font-weight: bold;
+	font-size: 1.3em;
+	color: [[ColorPalette::Background]];
+	background-color: [[ColorPalette::QuaternaryMid]];
+	text-align: center;
+	padding: 0.5em 1em;
+	border-radius: 1ex;
+	border-style: none !important;
+}
+
+#contentWrapper span.chunkyButton a.button:hover {
+	background-color: [[ColorPalette::QuaternaryDark]];
+	color: [[ColorPalette::Background]];
+	border-style: none !important;
+}
+
+/*}}}*/
+
+
+
+
+
The StyleSheet tiddler allows you to customise the look and feel of your TiddlyWiki using CSS
+You can put simple one line tweaks in, like:
+{{{
+.mainMenu { color: red; }
+}}}
+
+Or you can put in full ~StyleSheets, like that seen in TiddlySpace in the StyleSheetTiddlySpace
+{{{
+/*{{{*/
+[[StyleSheetTiddlySpace]]
+/*}}}*/
+}}}
+
+
+
+
Wiki markup rules can be suppressed for any given section of text by enclosing it in 3 ''double'' quotes:
+{{{
+"""//WikiWord//"""
+}}}
+Displays as:
+"""//WikiWord//"""
+
+WikiWords can also be suppressed by prefixing the WikiWord with ''~'':
+{{{
+~WikiWord
+}}}
+Displays as:
+~WikiWord
+!!Also See
+[[Code formatting]]
+
+
+
+
The TabAll tiddler contains a command to list all of the tiddler in your TiddlyWiki.
+{{{
+<<list all>>
+}}}
+In TiddlyWiki this list appears as default in the {{{All}}} tab on the right-hand menu.
+
+
+
+
The TabMoreMissing tiddler contains a command to list all of the tiddlers that have links to them but are undefined.
+{{{
+<<list missing>>
+}}}
+In TiddlyWiki this list appears as default in the {{{Missing}}} tab on the right-hand menu.
+
+
+
+
The TabMoreOrphans tiddler contains a command that lists all the tiddlers that are not linked to from any other tidder.
+{{{
+<<list orphans>>
+}}}
+In TiddlyWiki this list appears as default in the {{{Orphans}}} tab on the right-hand menu.
+
+
+
+
The TabMoreShadowed tiddler contains a command that displays a list of all tiddlers shadowed with default content in them
+{{{
+<<list shadowed>>
+}}}
+In TiddlyWiki this list appears as default in the {{{Shadows}}} tab on the right-hand menu.
+
+
+
+
The TabTimeline tiddler contains a command to list in reverse chronological the tiddlers in your TiddlyWiki
+{{{
+<<timeline>>
+}}}
+In TiddlyWiki this list appears as default in the {{{Recent}}} tab on the right-hand menu.
+
+
+
+
!Simple Tables
+{{{
+|North West|North|North East|
+|West|Here|East|
+|South West|South|South East|
+}}}
+Displays as:
+|North West|North|North East|
+|West|Here|East|
+|South West|South|South East|
+!Cell Formatting
+*Insert a space before cell content to right justify cell
+*Insert a space after cell content to left justify cell
+*Insert spaces before and after cell content to centre justify cell
+*Insert an exclamation mark ({{{!}}}) as the first non-space character of a cell to turn it into a header cell
+For example:
+{{{
+|!First column|!Second column|!Third column|
+|left | centre | right|
+}}}
+Displays as:
+|!First column|!Second column|!Third column|
+|left | centre | right|
+!Table Headers and Footers
+* Mark a table row as a header by adding an 'h' to the end
+* Mark a table row as a footer by adding an 'f' to the end
+For example:
+{{{
+|North West|North|North East|h
+|West|Here|East|
+|South West|South|South East|f
+}}}
+Displays as:
+|North West|North|North East|h
+|West|Here|East|
+|South West|South|South East|f
+!Table Caption
+A caption can be added above or below a table by adding a special row marked with a 'c':
+{{{
+|A caption above the table|c
+|North West|North|North East|
+|West|Here|East|
+|South West|South|South East|
+}}}
+{{{
+|North West|North|North East|
+|West|Here|East|
+|South West|South|South East|
+|A caption below the table|c
+}}}
+Displays as:
+|A caption above the table|c
+|North West|North|North East|h
+|West|Here|East|
+|South West|South|South East|f
+
+|North West|North|North East|h
+|West|Here|East|
+|South West|South|South East|f
+|A caption below the table|c
+!Mergine Table Cells
+A cell can be merged horizontally with the cell to its right by giving it the text {{{>}}}:
+{{{
+|North West|North|North East|
+|>|>|West and Here and East|
+|South West|South|South East|
+}}}
+Displays as:
+|North West|North|North East|
+|>|>|West and Here and East|
+|South West|South|South East|
+A cell can be merged vertically with the cell in the row above by giving it the text {{{~}}}:
+{{{
+|Westerly|North|North East|
+|~|Here|East|
+|~|South|South East|
+}}}
+Displays as:
+|Westerly|North|North East|
+|~|Here|East|
+|~|South|South East|
+!Table CSS Formatting
+A CSS class can be added to an entire table by adding a special row tagged with a 'k':
+{{{
+|myClass|k
+|North West|North|North East|
+|West|Here|East|
+|South West|South|South East|
+}}}
+CSS properties can be added to a table cell by preceding the cell content with CSS name/value pairs. There are two alternative forms of syntax:
+{{{
+|color:red; North West|opacity:0.5;North|North East|
+|color(green):West|Here|East|
+|South West|South|South East|
+}}}
+Displays as:
+|color:red; North West|opacity:0.5;North|North East|
+|color(green):West|Here|East|
+|South West|South|South East|
+!Alternating Rows
+TiddlyWiki automatically assigns the classes {{{oddRow}}} and {{{evenRow}}} to table rows {{{<TR>}}} elements. These can then be styled via the StyleSheet:
+{{{
+.viewer tr.oddRow { background-color: #fff; }
+.viewer tr.evenRow { background-color: #ffc; }
+}}}
+
+
+
+
You can categorise a tiddler by assigning it with one or more special keywords called tags. 
+
+Tiddlers which have been assigned tags can be accessed using the tagging menu that appears at the top right of each tiddler (when in read mode). For instance, this tiddler is tagged with "features", "terminology" and "gettingstarted". If you click on these tag names in the tagging menu, you get access to all the other tiddlers that have been given the same tag.
+
+You can see a whole list of tags by looking in the [[SideBarTabs]], and selecting the Tags tab.
+
+This site uses the standard convention that the names of tags start with a lower case letter, and tiddlers with an upper case letter, but this is not actually enforced.
+
+Tags can be tiddlers themselves, with their own tags, so you can have hierarchies of tiddlers.  This is a surprisingly useful and powerful technique has been dubbed ~TagglyTagging by the [[Community]].
+
+
+
+
A 'tiddler' is the name given to a unit of MicroContent in TiddlyWiki.
+
+Tiddlers are pervasive in TiddlyWiki. The MainMenu is defined by a tiddler, plugins are delivered in tiddlers, there are special StyleSheet tiddlers, and so on.
+
+Other systems have analogous concepts with more prosaic names: like "items", "entries", "entities". Even though "tiddler" is a vaguely preposterous name it at least has the virtue of being confusingly distinctive rather than confusingly generic.
+
+
+
+
To hide text within a tiddler so that it is not displayed you can wrap it in {{{/%}}} and {{{%/}}}. It can be a useful trick for hiding drafts or annotating complex markup. Edit this tiddler to see an example.
+/%This text is not displayed
+until you try to edit %/
+
+
+
+
TiddlerSlicing allows you to use special notation to pull out a chunk of specially marked text from within a tiddler. Each slice has a name and a value which can be specified anywhere within a tiddler in any of these formats:
+{{{
+    theName:  textSlice
+    |theName:| textSlice |
+    |theName| textSlice |
+}}}
+The name may contain any of the characters "a-ZA-Z_0-9", and may also be decorated with {{{''}}} or {{{//}}} markers for ''bold'' and //italic// formatting that are ignored. For example:
+{{{
+    |''theName:''| textSlice |
+}}}
+Slices can be then be referenced by qualifying the parent tiddler name with the symbols "::" and the name of the slice. For example:
+{{{
+ColorPalette::PrimaryLight
+}}}
+TiddlerSlicing doesn't work eveywhere; at this point it is mainly intended to support the ColorPalette and similar usages.
+
+Finally, here's an example of some more complex slice formatting:
+{{{
+version: 1.2.3.4
+Author: Joe Brown
+''Credits:'' ~ASmith ~BBrown ~CCalony
+//~SeeAlso:// The rise and fall of the M-perium
+|!Name|!Value|
+|Name:|TextSlice Tester|
+|URL:|http:\\sample.com\TestSliced |
+|''Type:''| Plugin |
+|//Source//| http:\\sample.com\TestSliced\src\text.js |
+}}}
+The slices defined in that example are:
+|version|1.2.3.4|
+|Author|Joe Brown|
+|Credits|~ASmith ~BBrown ~CCalony|
+|~SeeAlso|The rise and fall of the M-perium|
+|Name|TextSlice Tester|
+|URL|http:\\sample.com\TestSliced|
+|Type|Plugin|
+|Source|http:\\sample.com\TestSliced\src\text.js|
+
+
+
+
[[Bookmarklets|http://en.wikipedia.org/wiki/Bookmarklet]] can be useful for TiddlyWiki hackers. They are browser bookmarks that contain embedded ~JavaScript that can reach into the currently loaded TiddlyWiki page to manipulate them and extract data.
+
+Drag these links to your bookmark/favourites bar, or right click on them and choose "add to bookmarks" or "add to favourites":
+* [[Scrub shadow tiddlers|javascript:(function(){if(window.version&&window.version.title=='TiddlyWiki'){for(var s in config.shadowTiddlers){store.removeTiddler(s);}refreshDisplay();}})()]] - deletes any overridden shadow tiddlers, reverting them to their default values. Handy when you’ve gone mad with PageTemplate customisations and your ~TiddlyWiki document won’t display properly
+* [[Scrub tiddler fields|javascript:(function(){if(window.version&&window.version.title=='TiddlyWiki'){store.forEachTiddler(function(title,tiddler){tiddler.fields={};});refreshDisplay();}})()]] - all the extended fields from a ~TiddlyWiki document, including that pesky “changecount” field
+* [[Rescue raw TiddlyWiki content|javascript:(function(){if(window.version&&window.version.title=='TiddlyWiki'){var w=window.open();w.document.open();w.document.write('<html><body><pre>');w.document.write(store.allTiddlersAsHtml().htmlEncode());w.document.write('</pre></body></html>');w.document.close();}})()]] - opens a new window containing the raw content of a ~TiddlyWiki.  Handy when you’ve inadvertently been editing an online version of TiddlyWiki that isn’t letting you save changes in the usual way
+
+If you're creating your own bookmarklets, this [[editor|http://subsimple.com/bookmarklets/jsbuilder.htm]] and these [[tips|http://subsimple.com/bookmarklets/tips.asp]] are useful resources.
+
+
+
+
The ~TiddlySaver Java applet allows ~TiddlyWiki to save changes in a local version (from a file:// URL) of Safari, Opera and other browsers. It is a small file named [["TiddlySaver.jar"|TiddlySaver.jar]] that must be placed in the same directory as your ~TiddlyWiki file. As of August 2008, when an empty ~TiddlyWiki file is downloaded using either Safari or Opera, it is bundled with a copy of the ~TiddlySaver.jar file in a zip file - both files must be in the same directory ''whenever the ~TiddlyWiki file is opened'' in order to work.
+
+[["TiddlySaver.jar"|TiddlySaver.jar]]  is signed by [[UnaMesa Association|UnaMesa]]. The [[UnaMesa Association|UnaMesa]] certificate is signed by the ''Thawte Code Signing CA'' intermediate certificate which is chained to the ''Thawte Premium Server CA'' root certificate. You need to trust this certificate Chain to be able to use the applet.
+
+Note that there is currently [[a bug|http://trac.tiddlywiki.org/ticket/172]] that prevents ~TiddlySaver from working if you have specified a backup directory in AdvancedOptions and the directory doesn't exist.
+
+Thanks to Andrew Gregory for the original TiddlySaver code, and ~BidiX for arranging all the certificate magic.
+
+
+
+
|''URL:''|http://tiddlystyles.com/|
+
+
+
+
|''URL:''|http://www.tiddlytools.com/|
+|''Description:''|Small Tools for Big Ideas!|
+|''Author:''|EricShulman|
+
+
+
+
~TiddlyWiki is a complete [[wiki|WikiWikiWeb]] in a single HTML file. It contains the entire text of the wiki, and all the ~JavaScript, CSS and HTML goodness to be able to display it, and let you edit it or search it - without needing a server. Although, having said that, there are some fine ServerSide adaptations out there.
+
+~TiddlyWiki was originally created by JeremyRuston and is now a thriving [[open source|OpenSourceLicense]] project with a busy [[Community]] of independent developers. [[Osmosoft]], part of [[BT|http://btplc.com]], is the leading contributor to the project.
+
+
+
+
Recent versions of Firefox and Chrome feature tighter default security settings that can interfere with the operation of TiddlyWiki. The main issue is new restrictions on the ability of TiddlyWiki running from a {{{file://}}} URI to access other servers.
+!!!Known Problems with Upgrading TiddlyWiki
+Versions of TiddlyWiki before and including 2.6.3 are known to have an issue with the upgrade function. To upgrade these versions you will need to download an empty latest version of TiddlyWiki and import your content into it using the [[import function|Import Tiddlers]] in the backstage.
+!!!Known Problems with Importing in TiddlyWiki
+* It is not possible to import from TiddlyWiki's on servers which are not [[CORS|http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing]] enabled. Popular sites such as [[TiddlyTools|http://tiddlytools.com]] are currently not CORS enabled. Fortunately this is fixable - webmasters can enable their sites to be CORS enabled by following the instructions [[here|http://enable-cors.org]] and if a website is not CORS enabled you can e-mail the owner to tell them the problem.
+* Some browsers will only allow you to import from other TiddlyWikis in the same directory as your TiddlyWiki. If you use Chrome you can enable this by modifying the Chrome shortcut to include the following parameter
+{{{
+--allow-file-access-from-files
+}}}
+* In Opera you can enable this by entering the following into your address bar and ticking the checkbox.
+{{{
+opera:config#UserPrefs|AllowFileXMLHttpRequest
+}}}
+* Note some browsers, do not support import / upgrade in any form.
+
+
+
+
Before TiddlyWiki supported [[Plugins]], several independent developers created their own extended adaptations to support new features. These can be considered forks of the original core code, and won't necessarily be based on the latest version. For that reason, the trend more recently has been for developers to release new features as [[Plugins]] that can be readily mixed and matched and upgraded to the latest version.
+
+Here's a list of known adaptations - note that many of these are quite old, and the links may be dead (all working as at 10 July 2008):
+* Bram Chen's [[PrinceTiddlyWiki|http://ptw.sourceforge.net/]]
+* Jacques Turbé's [[TidliPo|http://avm.free.fr/tidlipo.html]] (in French)
+* Jároli József's [[MagyarTiddlyWiki|http://innen.hu/MagyarTiddlyWiki]] (in Hungarian)
+* Yoshimov's [[EncryptedTiddlyWiki|http://wiki.yoshimov.com/?page=EncryptedTiddlyWiki]] (in Japanese)
+* Nathan Bower's [[GTDTiddlyWiki|http://shared.snapgrid.com/gtd_tiddlywiki.html]]
+* Jonny ~LeRoy's [[TiddlyTagWiki|http://www.digitaldimsum.co.uk/tiddlywiki/]]
+* Christian Hauck's [[CompleXimple InDeed|http://www.christianhauck.net/html/14300.html]]
+* Tim Cuthbertson and Matt Giuca's [[TiddlyWikiCSS|http://codestar.lidonet.net/misc/tiddlywikicss.html]]
+* Patrick Curry and Gabriel Jeffrey's [[PhpTiddlyWiki|http://www.patrickcurry.com/tiddly/]]
+* Henrik Aasted Sorensen's [[server side adaptation|http://aasted.org/wiki]]
+* [[KamiWiki|http://rakusai.org/kamiwiki/]]
+* Kevem Buangga's [[TiddlyWikiClone|http://www.kevembuangga.com/hwk/hailiwiki.htm]]
+* Andre Nho's [[StickWiki|http://stickwiki.sourceforge.net/]]
+* [[MessageVault|http://www.kokogiak.com/gedankengang/2007/03/introducing-message-vault.html]], a personal password store built on some ~TiddlyWiki technology
+There's also some TiddlyWikiTools that extend ~TiddlyWiki.
+
+
+
+
~TiddlyWiki has been used as the basis of a few experiments in hypertext fiction. 
+*"[[Bibliotheca Caelestis. Tiddlywikiroman|http://bc.etkbooks.com/opac/]]" from Hartmut Abendschein
+*"[[Die, Vampire! Die!|http://www.davidvanwert.com/wiki/dievampiredie.html]]" from David Van Wert
+*"[[Rose|http://www.emacswiki.org/alex/rose.html]]" from Alex Schroeder
+
+
+
+
If your press the command key on the Mac or the control key on the PC while clicking on a link to a tiddler, the tiddler will be opened as usual if it isn't already open, but if //is// open, it will be closed. It makes a handy way to review links without having to move the mouse around to re-close tiddlers.
+
+You can make this behaviour the default under AdvancedOptions (you can override back to the normal behaviour with the same command/control key).
+
+
+
+
A row of ToolbarButtons appears to the right of a tiddler title when the mouse is hovered over it. The buttons are:
+* ''close'' - close the current tiddler
+* ''edit'' - edit the current tiddler
+* ''fields'' - show the custom fields attached to the current tiddler
+* ''syncing'' - shows the server synchronisation status of the current tiddler
+* ''jump'' - jump straight to another open tiddler
+* ''permalink'' - puts a link direct to the current tiddler into the address bar
+* ''references'' - displays all the tiddlers that link to the current tiddler
+* ''done'' - save changes to a tiddler being editted
+* ''cancel'' - cancel changes to a tiddler being editted
+* ''delete'' - delete the current tiddler
+
+The shadow tiddler ToolbarCommands allows these commands to customised and reordered.
+
+
+
+
+
The ~TiddlyWiki community have created several tutorials and guides. Here are some of the most popular ones:
+* Dave Gifford's [[TiddlyWiki for the Rest of Us|http://www.giffmex.org/twfortherestofus.html]]
+* Morris Gray's [[TW Help - TiddlyWiki help file for beginners|http://tiddlyspot.com/twhelp/]]
+* Julie Starr's [[Tiddler Toddler|http://tiddlertoddler.tiddlyspot.com/]], documenting the learning curve from a beginner's perspective
+* Screencasts from [[JimVentola|http://faculty.massasoit.mass.edu/jventola/videocasts/tidhelp2/tidhelp2.html]] and [[Phil Whitehouse|http://www.youtube.com/watch?v=ezNScBd7_h4]]
+
+
+
+
[[UnaMesa|http://www.unamesa.org/]] is a non-profit organisation that owns and holds the IPR for TiddlyWiki in trust for the public.
+
+
+
+
This tiddler contains the mark up to display tiddlers.  You can change the mark up to create a custom view of a tiddler.
+
+By default it contains the following markup:
+
+{{{
+<div class='toolbar' 
+	macro='toolbar [[ToolbarCommands::ViewToolbar]] icons:yes height:16 width:16 more:popup'>
+</div>
+<div class='heading'>
+	<span class='spaceSiteIcon' 
+		macro='tiddlerOrigin label:no spaceLink:yes height:48 width:48 preserveAspectRatio:yes'>
+	</span>
+	<span class="titleBar">
+		<div class='title' macro='view title text'></div>
+		<div class="subtitle" macro='viewRevisions page:5'>
+			last modified on
+			<span macro="view modified date"></span>
+		</div>
+	</span>
+	<span class='followPlaceHolder' macro='followTiddlers'></span>
+	<span class='modifierIcon'
+		macro='view modifier SiteIcon label:no spaceLink:yes height:48 width:48 preserveAspectRatio:yes'>
+	</span>
+	<div class='tagClear'></div>
+</div>
+<div class='content'>
+	<div class='viewer' macro='view text wikified'></div>
+</div>
+<div class='tagInfo'>
+	<div class='tidTags' macro='tags'></div>
+	<div class='tagging' macro='tagging'></div>
+</div>
+}}}
+
+
+
+
TiddlyWiki works fine under Internet Explorer on Windows Vista, although you may run into the blocking problem described in ServicePack2Problems.
+
+Under Vista, TiddlyWiki will not save changes correctly if you are viewing the TiddlyWiki file in Vista's file preview pane.
+
+
+
+
|''URL:''|http://visualtw.ouvaton.org/VisualTW.html|
+|''Author:''|Pascal|
+
+
+
+
TiddlyWiki seems to [[work fine on the Wii browser|http://blog.deuceofalltrades.com/2007/02/wii-browser-meets-microcontent.html]], at least for browsing -- it doesn't appear that you can save or upload changes.
+
+
+
+
Putting ~TiddlyWiki on a USB stick lets you carry around a self contained notebook that you can update wherever there's a computer, whether it's a Mac, Linux or a PC. 
+
+To be even more independent you can [[install FireFox on the drive|http://portableapps.com/apps/internet/firefox_portable]] as well!
+
+
+
+
[[Basic|Basic Formatting]]
+[[CSS|CSS Formatting]]
+[[Code|Code Formatting]]
+[[HTML Entities|HTML Entities Formatting]]
+[[HTML|HTML Formatting]]
+[[Headings|Headings Formatting]]
+[[Horizontal Rules|Horizontal Rule Formatting]]
+[[Images|Image Formatting]]
+[[Line Breaks|Line Break Formatting]]
+[[Links|Link Formatting]]
+[[Lists|List Formatting]]
+[[Quotations|Quotations Formatting]]
+[[Suppressing|Suppressing Formatting]]
+[[Tables|Tables Formatting]]
+
+
+
+
A wiki (derived from the Hawaiian word for "fast") is a popular way of building collaborative websites. It's based on the two ideas of allowing users to easily edit any page and the use of special formatting to create links between pages. See Wikipedia for [[more details|http://en.wikipedia.org/wiki/Wiki]].
+
+~TiddlyWiki is fundamentally different from a conventional Wiki because it is not based on separate, entire pages of content, but rather items of MicroContent referred to as [[Tiddlers|Tiddler]] that live together on the same page.
+
+Out of the box, ~TiddlyWiki is also different because it doesn't support public editing - or indeed any persistent editing when viewed over the web. However, there are ServerSide editions, TiddlyWikiAdaptations and [[Plugins]] that provide these features in a wide range of different configurations.
+
+
+
+
A WikiWord is a word composed of a bunch of other words slammed together with each of their first letters capitalised. WikiWord notation in a conventional WikiWikiWeb is used to name individual pages while TiddlyWiki uses WikiWord titles for smaller chunks of MicroContent. Referring to a page with a WikiWord automatically creates a link to it. Clicking on a link jumps to that page or, if it doesn't exist, to an editor to create it. It's also easy to have NonWikiWordLinks, and there's a WikiWordEscape for situations where you don't want a WikiWord to be interpreted as a link.
+
+
+
+
In this tiddler you put the text you wish to be displayed as the title of the page in the browser.
+By default it is set to:
+{{{
+<<tiddler SiteTitle>> - <<tiddler SiteSubtitle>>
+}}}
+which could display something like:
+{{{
+shadowtiddlers - a tiddlywiki
+}}}
+
+You can change this at any time. For example:
+{{{
+Shadow Tiddlers Documentation | TiddlyWiki.com
+}}}
+
+
+
+
I'm hoping that after using TiddlyWiki for a while a new WritingStyle will emerge that is appropriate for this medium.				Jakob Neilsen wrote an article about [[writing styles|http://www.useit.com/alertbox/980906.html]] for MicroContent back in 1998 that still seems surprisingly relevant.
+
+
+
+
At it's most basic this macro lists all the tags used in you tiddlywiki as dropdown lists of all the tiddlers which have that tag
+For example
+{{{
+<<allTags>>
+}}}
+
+Displays as:
+<<allTags>>
+
+
+
+
Hello $1, welcome to $2
+
+
+
+
To close all open tiddlers:
+{{{
+<<closeAll>>
+}}}
+Displays as:
+
+<<closeAll>>
+
+
+
+
You just clicked on an example link, which was created by putting the word 'example' in double square brackets. You can now close this tiddler by clicking on the 'close' link above, and return to the guidelines.
+
+
+
+
AAABAAEAEBAAAAAAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD////////////////////////////////////////////////a2tra2tra2tra2tr///////////+fn5+fn5+fn5+fn5+fn5+fn5/////////////a2tra2tra2tra2tr////MzMz////////////////////////////////////////a2tra2tra2tra2tr///////////+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5/////a2tra2tra2tra2tr////MzMz////////////////////////////////////////a2tra2tra2tra2tr///////////+fn5+fn5+fn5+fn5/////////////////////a2tra2tra2tra2tr////MzMz///+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5/////a2tra2tra2tra2tr///////////+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5/////////////////////////MzMz////////////////////////////////////////a2tra2tra2tra2tr///////////+fn5+fn5+fn5+fn5+fn5+fn5+fn5/////////////////////////////MzMz///+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5/////a2tra2tra2tra2tr////////////////////////////////////////////////////////////////ETQPETQPETQPETQPETQPETQPETQPETQPETQPETQPETQPETQPETQPETQPETQPETQPVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfVXgfmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwvmbwv2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw/2fw8AAOLpAADg6QAAf18AAP//AAD//wAA//8AAP//AAD//wAAAIAAAP//AAD/fwAA//8AAACAAAD//wAA//8AAOHp
+
+
+
+
Community member ~BidiX has created an adaptation of the ~TiddlyWiki user interface [[specially for the iPhone|http://www.apple.com/webapps/productivity/itwatiddlywikiforiphone.html]]
+
+
+
+
!all
+To list all tiddlers
+{{{
+<<list all>>
+}}}
+!filter
+List tiddlers that match a certain [[filter|filters syntax]]. The following example lists all plugins.
+{{{
+<<list filter [tag[systemConfig]]>>
+}}}
+!Missing
+To list tiddlers that have links to them but are not defined:
+{{{
+<<list missing>>
+}}}
+!Orphans
+To list tiddlers that are not linked to from any other tiddlers:
+{{{
+<<list orphans>>
+}}}
+!Shadowed
+To list tiddlers shadowed with default contents:
+{{{
+<<list shadowed>>
+}}}
+!Touched
+Show tiddlers that have been modified locally:
+{{{
+<<list touched>>
+}}}
+
+
+
+
To create a new tiddler from the current date and time:
+{{{
+<<newJournal>>
+}}}
+Displays as:
+
+<<newJournal>>
+
+
+
+
Using the following you get a new tiddler button:
+{{{
+<<newTiddler>>
+}}}
+Displays as:
+
+<<newTiddler>>
+
+Certain attributes can also be attached, such as a label, default text, tags and fields:
+{{{
+<<newTiddler label:WikiWord text:"text" tag:tag tag:tag accessKey:key focus:field>>
+}}}
+Displays as:
+
+<<newTiddler label:WikiWord text:"text" tag:tag tag:tag accessKey:key focus:field>>
+
+
+
+
The options macro, used predominately in the backstage, allows the user to set a number of cookie based options that affect the enabled features of the Tiddlywiki.  These settings can be trivial, such as do you want animations enabled, or can be used for more complex things, such as enable regular expressions searches.
+It is used as below:
+{{{
+<<options>>
+}}}
+
+Options can be used by plugin developers to add settings that can be used by their plugin.
+
+Displays as:
+<<options>>
+
+
+
+
To create a permaview URL for all open tiddlers:
+{{{
+<<permaview>>
+}}}
+Displays as:
+
+<<permaview>>
+
+
+
+
Using the following creates a save changes button
+{{{
+<<saveChanges>>
+}}}
+
+Displays as:
+
+<<saveChanges>>
+
+The button label can also be changed and a tooltip added:
+{{{
+<<saveChanges [label] [tip]>>
+}}}
+Displays as:
+
+<<saveChanges [label] [tip]>>
+
+
+
+
Search fields can be created using:
+{{{
+<<search>>
+}}}
+Displays as:
+
+<<search>>
+
+A default search term can also be added:
+{{{
+<<search [term]>>
+}}}
+Displays as:
+
+<<search [term]>>
+
+
+
+
The slider macro allows embedding tiddlers within another tiddler, with the option to toggle the visibility of the [[transclusion|transcluded]] content:
+{{{
+<<slider cookie tiddler label tooltip>>
+}}}
+* {{{cookie}}}: variable to save the state of the slider (e.g. {{{chkFooSlider}}})
+* {{{tiddler}}}: name of the tiddler to include in the slider
+* {{{label}}}: title text of the slider
+* {{{tooltip}}}: tooltip text of the slider
+
+For example: 
+{{{
+<<slider chkTestSlider [[OptionsPanel]] "Options" "Open advanced options">>
+}}}
+
+
+
+
Tiddlers tagged {{{systemServer}}} wrap up all the connection details of a particular server so that they can be accessed quickly.
+
+A list of available {{{systemServer}}} tiddlers is presented at the start of the ImportTiddlers macro. Selecting one of them pre-selects the relevant fields in the wizard. After a successful import operation, ImportTiddlers also offers the opportunity to automatically create a {{{systemServer}}} tiddler with the details of the server that was used. {{{systemServer}}} tiddlers are also used by the [[syncing]] command.
+
+Here's an example {{{systemServer}}} tiddler:
+
+<<<
+|''Description:''|My example server|
+|''Type:''|mediawiki|
+|''URL:''|http://www.myexampleserver.org/x/|
+|''Workspace:''|Main|
+|''~TiddlerFilter:''|[tags[important unpluggable]]|
+<<<
+
+The fields are as follows:
+
+|!Field |!Description |!Status |
+|Description |Brief description of the server |Optional |
+|Type |Type of server connection eg {{{file}}} or {{{mediawiki}}} |Optional - defaults to {{{file}}} |
+|URL |Host URL of the server |Mandatory |
+|Workspace |Name of the workspace |Optional |
+|TiddlerFilter |Specifies which tiddlers are to be imported |Optional |
+
+
+
+
The tab macro lets you create tab interfaces such as the one used for the timeline in the sidebar.
+You use it as follows:
+{{{
+<<tabs theCookieName
+nameOfFirstTab "HoverValueOfFirstTab" TiddlerToShowInTab(or content)
+nameOfSecondTab "HoverValueOfSecondTab" TiddlerToShowInTab(or content)
+...
+>>
+}}}
+''theCookieName'' is used to store the name of the tab you currently have open. It does this so that when you return the tiddler will still be displaying the last tab you had selected.
+
+Example use of the tab macro:
+{{{
+<<tabs txtFavourite
+tags "First tab" "tags macro"
+tag "Second tab" "tag macro"
+>>
+}}}
+
+
+
+
The tag macro generates a button with the named [[tag]] which when clicked provides a drop-down list of [[tiddlers]] tagged with the given tag along with an option to open all of the tagged tiddlers.
+{{{
+<<tag tagName [label] [tooltip]>>
+}}}
+For example
+{{{
+<<tag macro>>
+}}}
+Which displays
+<<tag macro>>
+
+
+
+
The tagging macro lists all the tiddlers that have a given tag. You set the tag you are looking for as the first parameter. E.g. to look for all tiddlers tagged with ''transclude'' you need to do the following:
+{{{
+<<tagging transclude>>
+}}}
+Displays as:
+<<tagging transclude>>
+This macro also accepts a 2nd parameter which allows you to define a separator. 
+{{{
+<<tagging transclude sep:____>> e.g. <<tagging transclude sep:---------->>
+}}}
+Displays as:
+<<tagging transclude sep:---------->>
+
+
+
+
To generate a list of [[tags]] applied to the current [[tiddler]]:
+{{{
+<<tags [tiddler]>>
+}}}
+
+Note this will not display any tags that are themselves tagged excludeLists.
+For instance if there is a tiddler called "foo" tagged excludeLists and the tags macro is run on a tiddler with the tag foo, this tag will not be displayed.
+
+
+
+
The tiddler macro allows you to [[transclude|Tranclusion]] the text of other tidders, or sections of other tiddlers, into your current tiddler.
+!!Basic Usage
+The basic usage is:
+{{{
+<<tiddler tiddlerToTransclude>>
+}}}
+
+This displays as:
+<<<
+<<tiddler tiddlerToTransclude>>
+<<<
+Have a look at the actual [[tiddlerToTransclude]] tiddler.
+!!Transcluding Sections and Slices
+Instead of transcluding an entire tiddler, you can also transclude individual [[slices|Tiddler Slices]]  or [[sections|Tiddler Sections]]:
+{{{
+<<tiddler [[Title::sliceLabel]]>>
+<<tiddler [[Title##sectionHeading]]>>
+}}}
+!!Parameterised Transclusion
+Using the {{{with:}}} parameter, placeholders in the transcluded content can be replaced with a desired value. For example:
+{{{
+<<tiddler anotherTiddlerToTransclude with:"Jim" "Oz">> 
+}}}
+Which displays as:
+<<<
+<<tiddler anotherTiddlerToTransclude with:"Jim" "Oz">> 
+<<<
+Have a look at the actual [[anotherTiddlerToTransclude]].
+
+
+
+
This text has been transcluded from another tidder.
+
+
+
+
@@Please do not modify this tiddler; it was created automatically upon space creation.@@
+
+
+
+
@@Please do not modify this tiddler; it was created automatically upon space creation.@@
+
+
+
+
To create a chronological list of recent edits:
+{{{
+<<timeline>>
+}}}
+It can accept 2 parameters. The first parameter can be either {{{modified}}} or {{{created}}}. This determines whether to list the tiddlers by last edited date or by creation date.
+It accepts a second parameter, an integer, which determines the number of tiddlers to list. This is particularly useful for option to use in spaces that have a large number of tiddlers.
+e.g.
+{{{
+<<timeline created 5>>>
+}}}
+displays as:
+<<timeline created 5>>
+{{{
+<<timeline modified 5>>
+}}}
+displays as:
+<<timeline modified 5>>
+
+
+
+
The date macro will display the current date and time:
+{{{
+<<today>>
+}}}
+Displays as:
+<<today>>
+
+This macro accepts one parameter - a date format. Such as:
+{{{
+<<today DD/MM/YYYY>>
+}}}
+Displays as:
+<<today DD/MM/YYYY>>
+Read more about [[Date Formats]]
+
+
+
+
~TiddlyWiki is available in 17 other languages languages.
+
+Translations are packaged as plugins which are stored at https://github.com/TiddlyWiki/translations
+
+
+
+
This is a simple macro that outputs the version of TiddlyWiki that is being used.
+{{{
+<<version>>
+}}}
+Displays as:
+<<version>>
+
+
+
+
The view macro is a powerful macro that allows you to access fields on a given tiddler and format them.
+
+The first parameter is the field to access (note tags does not work).
+The second parameter defines a [[view type|view macro types]] which defines how the value of that field should be rendered.
+
+{{{
+<<view title link>>
+}}}
+renders the link of the current tiddler i.e. <<view title link>>
+
+
+
+
!Text
+Renders the field exactly how it is stored.
+For example
+{{{
+<<view modified text>>
+<<view title text>>
+}}}
+gives:
+* <<view modified text>>
+* <<view title text>>
+
+!Link
+Renders the value of the field as a link
+{{{
+<<view title link>>
+}}}
+gives
+* <<view title link>>
+
+!Date
+Applicable to the modified and created fields. Note a further parameter - a date format can be used.
+{{{
+<<view created date>>
+<<view modified date "YYYY">>
+}}}
+gives: 
+* <<view created date>>
+* <<view modified date "YYYY">>
+See [[Date Formats]] for possible date format strings.
+
+!Wikified
+Treats the string as wikitext.
+e.g.
+{{{
+<<view customfield wikified>>
+}}}
+
+!TiddlySpace specific
+See [[TiddlySpace View Types]]
+
+
+ + + +
+ + + + + + + + + + + + + + + From 11a07e71dc4bf0396da625af56746dbe5d80fce6 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 12:32:57 +0100 Subject: [PATCH 081/183] Move stripcomments from the classictools plugin into the core It's a bit of a hack for the moment. The plan is to implement stripcomments via the macro mechanism, at which point it can move back into the plugin. --- core/modules/new_widgets/view.js | 15 ++++++ nbld.sh | 11 ++++ .../classictools/modules/stripcomments.js | 54 ------------------- 3 files changed, 26 insertions(+), 54 deletions(-) delete mode 100644 plugins/tiddlywiki/classictools/modules/stripcomments.js diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index 5294b8e15..b87a8a088 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -61,6 +61,9 @@ ViewWidget.prototype.execute = function() { case "relativedate": this.text = this.getValueAsRelativeDate(); break; + case "stripcomments": + this.text = this.getValueAsStrippedComments(); + break; default: // "text" this.text = this.getValueAsText(); break; @@ -138,6 +141,18 @@ ViewWidget.prototype.getValueAsRelativeDate = function(format) { } }; +ViewWidget.prototype.getValueAsStrippedComments = function() { + var lines = this.getValueAsText().split("\n"), + out = []; + for(var line=0; line Date: Fri, 25 Oct 2013 12:46:46 +0100 Subject: [PATCH 082/183] Cleaning up the TW2 edition --- editions/tw2/target/{pre-widgetredux2.html => prebuilt.html} | 0 nbld.sh | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) rename editions/tw2/target/{pre-widgetredux2.html => prebuilt.html} (100%) diff --git a/editions/tw2/target/pre-widgetredux2.html b/editions/tw2/target/prebuilt.html similarity index 100% rename from editions/tw2/target/pre-widgetredux2.html rename to editions/tw2/target/prebuilt.html diff --git a/nbld.sh b/nbld.sh index e89aa8f9b..042181f4c 100755 --- a/nbld.sh +++ b/nbld.sh @@ -62,7 +62,10 @@ node ./tiddlywiki.js \ --new_rendertiddler $:/core/templates/tiddlywiki2.template.html ./tmp/tw2/index.html text/plain \ || exit 1 -opendiff tmp/tw2/index.html editions/tw2/target/pre-widgetredux2.html +# Compare what we've built to the prebuilt standard +# TODO: We should exit the batch file if there are any differences + +diff -q tmp/tw2/index.html editions/tw2/target/prebuilt.html # Run tests From 62b72f6bd11f5ad0a14be9ea3448403c91f90985 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 12:53:40 +0100 Subject: [PATCH 083/183] Move browser-startup module initialisation to the end of startup --- core/modules/startup.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/modules/startup.js b/core/modules/startup.js index 606cb15d6..c20b42198 100755 --- a/core/modules/startup.js +++ b/core/modules/startup.js @@ -53,12 +53,6 @@ exports.startup = function() { $tw.wiki.addTiddler({title: storyTitle, text: "", list: story},$tw.wiki.getModificationFields()); // Host-specific startup if($tw.browser) { - // Call browser startup modules - $tw.modules.forEachModuleOfType("browser-startup",function(title,module) { - if(module.startup) { - module.startup(); - } - }); // Install the popup manager $tw.popup = new $tw.utils.Popup({ rootElement: document.body @@ -130,6 +124,12 @@ exports.startup = function() { param: "$:/messages/SaveInstructions" }); } + // Call browser startup modules + $tw.modules.forEachModuleOfType("browser-startup",function(title,module) { + if(module.startup) { + module.startup(); + } + }); } else { // On the server, start a commander with the command line arguments commander = new $tw.Commander( From eb5a8253baf9c36438fe680f4dc65acc683dd172 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 12:53:58 +0100 Subject: [PATCH 084/183] Make the full screen plugin work again --- editions/tw5.com/tiddlers/Features.tid | 1 + plugins/tiddlywiki/fullscreen/init.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/editions/tw5.com/tiddlers/Features.tid b/editions/tw5.com/tiddlers/Features.tid index 1dc18a44d..2acd4d2f8 100644 --- a/editions/tw5.com/tiddlers/Features.tid +++ b/editions/tw5.com/tiddlers/Features.tid @@ -13,6 +13,7 @@ title: Features * Familiar user interface elements like <$button message="tw-modal" param="SampleWizard" class="btn btn-inverse">wizards and <$button message="tw-notify" param="SampleNotification" class="btn btn-inverse">notifications * Easily [[import|ImportTiddlers]] content via drag and drop, copy and paste, or browsing for local files * TiddlyWiki is [[surprisingly scalable|Scalability]] to many thousands of tiddlers and megabytes of content +* Try out the <$button message="tw-full-screen">full screen support * Explore the [[D3.js visualisation plugin|http://five.tiddlywiki.com/d3demo.html]] * Try out the [[CodeMirror plugin|http://five.tiddlywiki.com/codemirrordemo.html]] * Many internal improvements: diff --git a/plugins/tiddlywiki/fullscreen/init.js b/plugins/tiddlywiki/fullscreen/init.js index c617006a2..fca509c8d 100644 --- a/plugins/tiddlywiki/fullscreen/init.js +++ b/plugins/tiddlywiki/fullscreen/init.js @@ -22,9 +22,9 @@ var toggleFullScreen = function() { exports.startup = function() { // Install the full screen handler - document.addEventListener("tw-full-screen",function(event) { + $tw.rootWidget.addEventListener("tw-full-screen",function(event) { toggleFullScreen(); - },false); + }); }; })(); From 8c2fb7b8d0bde2d21acf6d8acbc2e7517fb5e809 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 21:15:20 +0100 Subject: [PATCH 085/183] Extend wiki object to have methods required for importing --- core/modules/wiki.js | 71 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/core/modules/wiki.js b/core/modules/wiki.js index cca58c2c1..15d6c5545 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -170,6 +170,18 @@ exports.tiddlerExists = function(title) { return !!this.tiddlers[title]; }; +/* +Generate an unused title from the specified base +*/ +exports.generateNewTitle = function(baseTitle) { + var c = 0; + do { + var title = baseTitle + (c ? " " + (c + 1) : ""); + c++; + } while(this.tiddlerExists(title)); + return title; +}; + exports.isSystemTiddler = function(title) { return title.indexOf("$:/") === 0; }; @@ -920,4 +932,63 @@ exports.getTiddlerText = function(title,defaultText) { } }; +/* +Read an array of browser File objects, invoking callback(tiddlerFields) for each loaded file +*/ +exports.readFiles = function(files,callback) { + for(var f=0; f Date: Fri, 25 Oct 2013 21:15:32 +0100 Subject: [PATCH 086/183] Extend the navigator widget to handle import events --- core/modules/new_widgets/navigator.js | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/navigator.js b/core/modules/new_widgets/navigator.js index 33702ebf8..5ec07c5a9 100755 --- a/core/modules/new_widgets/navigator.js +++ b/core/modules/new_widgets/navigator.js @@ -24,7 +24,8 @@ var NavigatorWidget = function(parseTreeNode,options) { {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-new-tiddler", handler: "handleNewTiddlerEvent"}, + {type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"}, ]); }; @@ -305,6 +306,42 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) { return false; }; +// Import JSON tiddlers +NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) { + var self = this; + // Get the story and history details + this.getStoryList(); + var history = this.wiki.getTiddlerData(this.historyTitle,[]); + // Get the tiddlers + var tiddlers = []; + try { + tiddlers = JSON.parse(event.param); + } catch(e) { + } + // Process each tiddler + $tw.utils.each(tiddlers,function(tiddlerFields) { + // Generate a unique title for the tiddler + var title = self.wiki.generateNewTitle(tiddlerFields.title); + // Add it to the store + self.wiki.addTiddler(new $tw.Tiddler( + self.wiki.getCreationFields(), + tiddlerFields, + self.wiki.getModificationFields(), + {title: title} + )); + // Add it to the story + if(self.storyList.indexOf(title) === -1) { + self.storyList.unshift(title); + } + // And to history + history.push({title: title}); + }); + // Save the updated story and history + this.saveStoryList(); + this.wiki.setTiddlerData(this.historyTitle,history); + return false; +}; + exports.navigator = NavigatorWidget; })(); From 75231fec39f00578a75f0e075fc826d6238ca240 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 21:16:03 +0100 Subject: [PATCH 087/183] Add browse and dropzone widgets We're splitting out the two aspects of the old import widget --- core/modules/new_widgets/browse.js | 77 +++++++++++++ core/modules/new_widgets/dropzone.js | 159 +++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 core/modules/new_widgets/browse.js create mode 100644 core/modules/new_widgets/dropzone.js diff --git a/core/modules/new_widgets/browse.js b/core/modules/new_widgets/browse.js new file mode 100644 index 000000000..7005ad3f8 --- /dev/null +++ b/core/modules/new_widgets/browse.js @@ -0,0 +1,77 @@ +/*\ +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"); + // 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/dropzone.js b/core/modules/new_widgets/dropzone.js new file mode 100644 index 000000000..df450fa33 --- /dev/null +++ b/core/modules/new_widgets/dropzone.js @@ -0,0 +1,159 @@ +/*\ +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.handlePasteEvent = function(event) { + // Let the browser handle it if we're in a textarea or input box + if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1) { + var self = this, + items = event.clipboardData.items; + // Enumerate the clipboard items + for(var t = 0; t Date: Fri, 25 Oct 2013 21:16:53 +0100 Subject: [PATCH 088/183] Fix up the new import mechanism --- core/ui/PageTemplate.tid | 4 ++-- core/ui/ToolsPanel.tid | 2 +- core/wiki/ControlPanel.tid | 2 +- themes/tiddlywiki/snowwhite/base.tid | 16 ++-------------- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/core/ui/PageTemplate.tid b/core/ui/PageTemplate.tid index c2c37dce7..56d795e4c 100644 --- a/core/ui/PageTemplate.tid +++ b/core/ui/PageTemplate.tid @@ -3,7 +3,7 @@ title: $:/core/ui/PageTemplate <$navigator story="$:/StoryList" history="$:/HistoryList"> -<$import browse="no" class="tw-drop-zone-fullscreen"> +<$dropzone> {{$:/TopSideBar}} @@ -31,7 +31,7 @@ title: $:/core/ui/PageTemplate - + diff --git a/core/ui/ToolsPanel.tid b/core/ui/ToolsPanel.tid index f460a9f75..79584d084 100644 --- a/core/ui/ToolsPanel.tid +++ b/core/ui/ToolsPanel.tid @@ -12,7 +12,7 @@ title: $:/core/ui/ToolsPanel --- -Import: <$import class="tw-drop-zone"/> +Import: <$browse/> --- diff --git a/core/wiki/ControlPanel.tid b/core/wiki/ControlPanel.tid index b367a3a39..a46166500 100644 --- a/core/wiki/ControlPanel.tid +++ b/core/wiki/ControlPanel.tid @@ -15,7 +15,7 @@ title: $:/ControlPanel ''Add plugins and tiddlers to your ~TiddlyWiki'' -:Browse for files on your computer to import their contents (the individual tiddlers within TiddlyWiki HTML files are imported separately). <$import class="tw-drop-zone"/> +:Browse for files on your computer to import their contents (the individual tiddlers within TiddlyWiki HTML files are imported separately). <$browse/> ''Visual appearance'' diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 5c3fd9a70..0abe08c69 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -207,23 +207,11 @@ a.tw-tiddlylink-external { background-color: {{$:/themes/tiddlywiki/snowwhite/colourmappings##pagebackground}}; } -.tw-import { +.tw-dropzone { position: relative; } -.tw-drop-zone-fullscreen.tw-dragover:before { - z-index: 10000; - display: block; - position: fixed; - top: 0; - left: 0; - right: 0; - background: rgba(0,200,0,0.7); - text-align: center; - content: "Drop here"; -} - -.tw-drop-zone.tw-dragover:before { +.tw-dropzone.tw-dragover:before { z-index: 10000; display: block; position: absolute; From 8fbc5759ad9b2083bbfdbf8e2409e5348c0735dc Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 22:16:15 +0100 Subject: [PATCH 089/183] Refactor widget.getVariable() to have better behaved parameters --- core/modules/new_widgets/element.js | 2 +- core/modules/new_widgets/macrocall.js | 2 +- core/modules/new_widgets/transclude.js | 2 +- core/modules/new_widgets/widget.js | 14 +++++++++----- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/core/modules/new_widgets/element.js b/core/modules/new_widgets/element.js index 034490bb9..2a65789cd 100755 --- a/core/modules/new_widgets/element.js +++ b/core/modules/new_widgets/element.js @@ -50,7 +50,7 @@ ElementWidget.prototype.execute = function() { if(this.namespace) { this.setVariable("namespace",this.namespace); } else { - this.namespace = this.getVariable("namespace",null,"http://www.w3.org/1999/xhtml"); + this.namespace = this.getVariable("namespace",{defaultValue: "http://www.w3.org/1999/xhtml"}); } // Make the child widgets this.makeChildWidgets(); diff --git a/core/modules/new_widgets/macrocall.js b/core/modules/new_widgets/macrocall.js index 3d47a9969..a488917e0 100644 --- a/core/modules/new_widgets/macrocall.js +++ b/core/modules/new_widgets/macrocall.js @@ -43,7 +43,7 @@ MacroCallWidget.prototype.execute = function() { params.push({name: name, value: attribute}); }); // Get the macro value - var text = this.getVariable(this.parseTreeNode.name || this.getAttribute("$name"),params); + var text = this.getVariable(this.parseTreeNode.name || this.getAttribute("$name"),{params: params}); // Parse the macro var parser = this.wiki.new_parseText("text/vnd.tiddlywiki",text, {parseAsInline: !this.parseTreeNode.isBlock}), diff --git a/core/modules/new_widgets/transclude.js b/core/modules/new_widgets/transclude.js index 754e34608..572dcc539 100755 --- a/core/modules/new_widgets/transclude.js +++ b/core/modules/new_widgets/transclude.js @@ -66,7 +66,7 @@ Compose a string comprising the title, field and/or index to identify this trans TranscludeWidget.prototype.makeRecursionMarker = function() { var output = []; output.push("{"); - output.push(this.getVariable("tiddlerTitle","")); + output.push(this.getVariable("tiddlerTitle",{defaultValue: ""})); output.push("|"); output.push(this.transcludeTitle || ""); output.push("|"); diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index eb8b90cf9..adeb75140 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -72,10 +72,14 @@ Widget.prototype.execute = function() { /* 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,actualParams,defaultValue) { - actualParams = actualParams || []; +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)) { @@ -83,7 +87,7 @@ Widget.prototype.getVariable = function(name,actualParams,defaultValue) { } // If we get to the root then look for a macro module if(!node) { - return this.evaluateMacroModule(name,actualParams,defaultValue); + return this.evaluateMacroModule(name,actualParams,options.defaultValue); } // Get the value var value = node.variables[name].value; @@ -126,7 +130,7 @@ Widget.prototype.substituteVariableParameters = function(text,formalParams,actua Widget.prototype.substituteVariableReferences = function(text) { var self = this; return text.replace(/\$\(([^\)\$]+)\)\$/g,function(match,p1,offset,string) { - return self.getVariable(p1,null,""); + return self.getVariable(p1,{defaultValue: ""}); }); }; @@ -215,7 +219,7 @@ Widget.prototype.computeAttributes = function() { if(attribute.type === "indirect") { value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("tiddlerTitle")); } else if(attribute.type === "macro") { - value = self.getVariable(attribute.value.name,attribute.value.params); + value = self.getVariable(attribute.value.name,{params: attribute.value.params}); } else { // String attribute value = attribute.value; } From b3ae45cbded68bcdb365b9e48292d2b336a4771b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 22:29:28 +0100 Subject: [PATCH 090/183] Invoke macros with `this` set to the host widget --- core/modules/new_widgets/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index adeb75140..b542935ef 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -162,7 +162,7 @@ Widget.prototype.evaluateMacroModule = function(name,actualParams,defaultValue) // Save the parameter args.push(paramValue); } - return macro.run.apply(null,args) + return macro.run.apply(this,args) } else { return defaultValue; } From bdcead624a93f61f2bf2ef6c3e136f480ca542a1 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 22:29:49 +0100 Subject: [PATCH 091/183] Add a changecount macro --- core/modules/macros/changecount.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 core/modules/macros/changecount.js diff --git a/core/modules/macros/changecount.js b/core/modules/macros/changecount.js new file mode 100644 index 000000000..08d8a799e --- /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("tiddlerTitle")) + ""; +}; + +})(); From 925e22c46f1117a6caf3dd57fd6704f5195851d2 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 22:30:15 +0100 Subject: [PATCH 092/183] Fix up the server command to use the new widget mechanism --- core/modules/commands/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"); } }); From c46748b82fe2c461db6e8a959d55490255f53743 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 22:30:36 +0100 Subject: [PATCH 093/183] Fix the template for serving tiddlers --- plugins/tiddlywiki/tiddlyweb/html-div-tiddler.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tiddlywiki/tiddlyweb/html-div-tiddler.tid b/plugins/tiddlywiki/tiddlyweb/html-div-tiddler.tid index 754b3d0eb..95e2a21b2 100644 --- a/plugins/tiddlywiki/tiddlyweb/html-div-tiddler.tid +++ b/plugins/tiddlywiki/tiddlyweb/html-div-tiddler.tid @@ -4,6 +4,6 @@ title: $:/core/templates/html-div-tiddler This template is used for saving tiddlers as an HTML DIV tag with attributes representing the tiddler fields. This version includes the tiddler changecount as the field `revision`. --->`` revision="`<$info type='changecount'/>`" bag="default"> +-->`` revision="`<>`" bag="default">
`<$view field="text" format="htmlencoded" />`
` From ad518c181de757dd4fa077296dc7a64837eb93c1 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 23:00:43 +0100 Subject: [PATCH 094/183] Add jsencoded view format --- core/modules/new_widgets/view.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index b87a8a088..3e6b489cb 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -64,6 +64,9 @@ ViewWidget.prototype.execute = function() { case "stripcomments": this.text = this.getValueAsStrippedComments(); break; + case "jsencoded": + this.text = this.getValueAsJsEncoded(); + break; default: // "text" this.text = this.getValueAsText(); break; @@ -153,6 +156,10 @@ ViewWidget.prototype.getValueAsStrippedComments = function() { return out.join("\n"); }; +ViewWidget.prototype.getValueAsJsEncoded = function() { + return $tw.utils.stringify(this.getValueAsText()); +}; + /* Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ From 068befb24f49f92c3bc56a0e24b2192623524824 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 25 Oct 2013 23:22:46 +0100 Subject: [PATCH 095/183] Refactor link widget with tidier event handler --- core/modules/new_widgets/link.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/core/modules/new_widgets/link.js b/core/modules/new_widgets/link.js index cf8c81a25..74f73e9c5 100755 --- a/core/modules/new_widgets/link.js +++ b/core/modules/new_widgets/link.js @@ -74,26 +74,30 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { wikiLinkText = wikiLinkText.replace("$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to))); domNode.setAttribute("href",wikiLinkText); // Add a click event handler - domNode.addEventListener("click",function (event) { - // Send the click on it's way as a navigate event - var bounds = domNode.getBoundingClientRect(); - self.dispatchEvent({ - type: "tw-navigate", - navigateTo: self.to, - navigateFromNode: self, - navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height - } - }); - event.preventDefault(); - event.stopPropagation(); - return false; - },false); + $tw.utils.addEventListeners(domNode,[ + {name: "click", handlerObject: this, handlerMethod: "handleClickEvent"} + ]); // Insert the link into the DOM and render any children parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); this.domNodes.push(domNode); }; +LinkWidget.prototype.handleClickEvent = function (event) { + // Send the click on it's way as a navigate event + var bounds = this.domNodes[0].getBoundingClientRect(); + this.dispatchEvent({ + type: "tw-navigate", + navigateTo: this.to, + navigateFromNode: this, + navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height + } + }); + event.preventDefault(); + event.stopPropagation(); + return false; +}; + /* Compute the internal state of the widget */ From 05fc9848bbe8e3bfca1978e34205e98839925890 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 26 Oct 2013 08:36:31 +0100 Subject: [PATCH 096/183] Extend the dropzone widget to cope with dataTransfer objects (as well as files) --- core/modules/new_widgets/dropzone.js | 33 +++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/dropzone.js b/core/modules/new_widgets/dropzone.js index df450fa33..78cc0e279 100644 --- a/core/modules/new_widgets/dropzone.js +++ b/core/modules/new_widgets/dropzone.js @@ -88,7 +88,7 @@ DropZoneWidget.prototype.handleDropEvent = function(event) { // Remove highlighting $tw.utils.removeClass(this.domNodes[0],"tw-dragover"); // Try to import the various data types we understand -// this.importData(dataTransfer); + 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])}); @@ -99,6 +99,37 @@ DropZoneWidget.prototype.handleDropEvent = function(event) { event.stopPropagation(); }; +DropZoneWidget.prototype.importData = function(dataTransfer) { + for(var t=0; t Date: Sat, 26 Oct 2013 08:36:43 +0100 Subject: [PATCH 097/183] Extend the link widget to make the links draggable --- core/modules/new_widgets/link.js | 47 +++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/link.js b/core/modules/new_widgets/link.js index 74f73e9c5..7825a625b 100755 --- a/core/modules/new_widgets/link.js +++ b/core/modules/new_widgets/link.js @@ -75,7 +75,9 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { domNode.setAttribute("href",wikiLinkText); // Add a click event handler $tw.utils.addEventListeners(domNode,[ - {name: "click", handlerObject: this, handlerMethod: "handleClickEvent"} + {name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}, + {name: "dragstart", handlerObject: this, handlerMethod: "handleDragStartEvent"}, + {name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"} ]); // Insert the link into the DOM and render any children parent.insertBefore(domNode,nextSibling); @@ -98,6 +100,49 @@ LinkWidget.prototype.handleClickEvent = function (event) { return false; }; +LinkWidget.prototype.handleDragStartEvent = function(event) { + if(this.to) { + // Set the dragging class on the element being dragged + $tw.utils.addClass(event.target,"tw-tiddlylink-dragging"); + // Create the drag image elements + this.dragImage = this.document.createElement("div"); + this.dragImage.className = "tw-tiddler-dragger"; + var inner = this.document.createElement("div"); + inner.className = "tw-tiddler-dragger-inner"; + inner.appendChild(this.document.createTextNode(this.to)); + this.dragImage.appendChild(inner); + this.document.body.appendChild(this.dragImage); + // Astoundingly, we need to cover the dragger up: http://www.kryogenix.org/code/browser/custom-drag-image.html + var bounds = this.dragImage.firstChild.getBoundingClientRect(), + cover = this.document.createElement("div"); + cover.className = "tw-tiddler-dragger-cover"; + cover.style.left = (bounds.left - 8) + "px"; + cover.style.top = (bounds.top - 8) + "px"; + cover.style.width = (bounds.width + 16) + "px"; + cover.style.height = (bounds.height + 16) + "px"; + this.dragImage.appendChild(cover); + // Set the data transfer properties + var dataTransfer = event.dataTransfer; + dataTransfer.effectAllowed = "copy"; + dataTransfer.setDragImage(this.dragImage.firstChild,-16,-16); + dataTransfer.clearData(); + dataTransfer.setData("text/vnd.tiddler",this.wiki.getTiddlerAsJson(this.to)); + dataTransfer.setData("text/plain",this.wiki.getTiddlerText(this.to,"")); + event.stopPropagation(); + } else { + event.preventDefault(); + } +}; + +LinkWidget.prototype.handleDragEndEvent = function(event) { + // Remove the dragging class on the element being dragged + $tw.utils.removeClass(event.target,"tw-tiddlylink-dragging"); + // Delete the drag image element + if(this.dragImage) { + this.dragImage.parentNode.removeChild(this.dragImage); + } +}; + /* Compute the internal state of the widget */ From d1299f05af4a192bc82847deb1b2dded1baf3b0e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 26 Oct 2013 08:43:35 +0100 Subject: [PATCH 098/183] Fix up the datauri handling for the Star Light theme --- themes/tiddlywiki/starlight/styles.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/tiddlywiki/starlight/styles.tid b/themes/tiddlywiki/starlight/styles.tid index 4d99175be..0eb095885 100644 --- a/themes/tiddlywiki/starlight/styles.tid +++ b/themes/tiddlywiki/starlight/styles.tid @@ -33,7 +33,7 @@ background-image: -ms-linear-gradient($gradient$); ``` \end \define datauri(title) -<$datauri tiddler="$title$"/> +<$macrocall $name="makedatauri" type={{$title$!!type}} text={{$title$}}/> \end /* From 76faba2d9f8dca90f961877783bbfd5d31d06cec Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 26 Oct 2013 10:29:40 +0100 Subject: [PATCH 099/183] Get rid of the old tempwidget --- core/modules/new_widgets/tempwidgets.js | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100755 core/modules/new_widgets/tempwidgets.js diff --git a/core/modules/new_widgets/tempwidgets.js b/core/modules/new_widgets/tempwidgets.js deleted file mode 100755 index 34c5c77d9..000000000 --- a/core/modules/new_widgets/tempwidgets.js +++ /dev/null @@ -1,19 +0,0 @@ -/*\ -title: $:/core/modules/new_widgets/tempwidgets.js -type: application/javascript -module-type: new_widget - -Temporary shim widgets - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -var Widget = require("$:/core/modules/new_widgets/widget.js").widget; - -exports["import"] = Widget; - -})(); From f8f524d3788ea33b7fa2f4dd44517394ccd33a1f Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 26 Oct 2013 10:29:52 +0100 Subject: [PATCH 100/183] Make sure the dragger cover properly covers the dragger --- core/modules/new_widgets/link.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/modules/new_widgets/link.js b/core/modules/new_widgets/link.js index 7825a625b..d17f7adca 100755 --- a/core/modules/new_widgets/link.js +++ b/core/modules/new_widgets/link.js @@ -116,10 +116,10 @@ LinkWidget.prototype.handleDragStartEvent = function(event) { var bounds = this.dragImage.firstChild.getBoundingClientRect(), cover = this.document.createElement("div"); cover.className = "tw-tiddler-dragger-cover"; - cover.style.left = (bounds.left - 8) + "px"; - cover.style.top = (bounds.top - 8) + "px"; - cover.style.width = (bounds.width + 16) + "px"; - cover.style.height = (bounds.height + 16) + "px"; + cover.style.left = (bounds.left - 16) + "px"; + cover.style.top = (bounds.top - 16) + "px"; + cover.style.width = (bounds.width + 32) + "px"; + cover.style.height = (bounds.height + 32) + "px"; this.dragImage.appendChild(cover); // Set the data transfer properties var dataTransfer = event.dataTransfer; From b474d8c13dea03224dc34c3c82f20eb569ecc940 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 26 Oct 2013 13:13:45 +0100 Subject: [PATCH 101/183] Extend domMaker to set event listeners --- boot/boot.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/boot/boot.js b/boot/boot.js index d52ae9d7d..954071c2f 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -114,6 +114,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; @@ -133,6 +134,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; }; From 3ad3cfd526b95395c5c34413d7520c22f355ad0a Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 26 Oct 2013 13:13:55 +0100 Subject: [PATCH 102/183] Bring back the bitmap editor widget --- core/modules/new_widgets/edit-bitmap.js | 313 ++++++++++++++++++++++++ themes/tiddlywiki/snowwhite/base.tid | 5 +- 2 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 core/modules/new_widgets/edit-bitmap.js diff --git a/core/modules/new_widgets/edit-bitmap.js b/core/modules/new_widgets/edit-bitmap.js new file mode 100644 index 000000000..caa7c971b --- /dev/null +++ b/core/modules/new_widgets/edit-bitmap.js @@ -0,0 +1,313 @@ +/*\ +title: $:/core/modules/new_widgets/edit-bitmap.js +type: application/javascript +module-type: new_widget + +Edit-bitmap widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +// Default image sizes +var DEFAULT_IMAGE_WIDTH = 300, + DEFAULT_IMAGE_HEIGHT = 185; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var EditBitmapWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +EditBitmapWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +EditBitmapWidget.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 + this.canvasDomNode = $tw.utils.domMaker("canvas",{ + document: this.document, + "class":"tw-edit-bitmapeditor", + eventListeners: [{ + name: "touchstart", handlerObject: this, handlerMethod: "handleTouchStartEvent" + },{ + name: "touchmove", handlerObject: this, handlerMethod: "handleTouchMoveEvent" + },{ + name: "touchend", handlerObject: this, handlerMethod: "handleTouchEndEvent" + },{ + name: "mousedown", handlerObject: this, handlerMethod: "handleMouseDownEvent" + },{ + name: "mousemove", handlerObject: this, handlerMethod: "handleMouseMoveEvent" + },{ + name: "mouseup", handlerObject: this, handlerMethod: "handleMouseUpEvent" + }] + }); + this.widthDomNode = $tw.utils.domMaker("input",{ + document: this.document, + "class":"tw-edit-bitmapeditor-width", + eventListeners: [{ + name: "change", handlerObject: this, handlerMethod: "handleWidthChangeEvent" + }] + }); + this.heightDomNode = $tw.utils.domMaker("input",{ + document: this.document, + "class":"tw-edit-bitmapeditor-height", + eventListeners: [{ + name: "change", handlerObject: this, handlerMethod: "handleHeightChangeEvent" + }] + }); + // Insert the elements into the DOM + parent.insertBefore(this.canvasDomNode,nextSibling); + parent.insertBefore(this.widthDomNode,nextSibling); + parent.insertBefore(this.heightDomNode,nextSibling); + this.domNodes.push(this.canvasDomNode,this.widthDomNode,this.heightDomNode); + // Load the image into the canvas + this.loadCanvas(); +}; + +/* +Compute the internal state of the widget +*/ +EditBitmapWidget.prototype.execute = function() { + // Get our parameters + this.editTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.editClass = this.getAttribute("class"); +}; + +/* +Note that the bitmap editor intentionally doesn't try to refresh itself because it would be confusing to have the image changing spontaneously while editting it +*/ +EditBitmapWidget.prototype.refresh = function(changedTiddlers) { + return false; +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +EditBitmapWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +EditBitmapWidget.prototype.loadCanvas = function() { + var tiddler = this.wiki.getTiddler(this.editTitle), + currImage = new Image(); + // Set up event handlers for loading the image + var self = this; + currImage.onload = function() { + // Copy the image to the on-screen canvas + self.initCanvas(self.canvasDomNode,currImage.width,currImage.height,currImage); + // And also copy the current bitmap to the off-screen canvas + self.currCanvas = self.document.createElement("canvas"); + self.initCanvas(self.currCanvas,currImage.width,currImage.height,currImage); + // Set the width and height input boxes + self.updateSize(); + }; + currImage.onerror = function() { + // Set the on-screen canvas size and clear it + self.initCanvas(self.canvasDomNode,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT); + // Set the off-screen canvas size and clear it + self.currCanvas = self.document.createElement("canvas"); + self.initCanvas(self.currCanvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT); + // Set the width and height input boxes + self.updateSize(); + } + // Get the current bitmap into an image object + currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text; +}; + +EditBitmapWidget.prototype.initCanvas = function(canvas,width,height,image) { + canvas.width = width; + canvas.height = height; + var ctx = canvas.getContext("2d"); + if(image) { + ctx.drawImage(image,0,0); + } else { + ctx.fillStyle = "#fff"; + ctx.fillRect(0,0,canvas.width,canvas.height); + } +} + +/* +** Update the input boxes with the actual size of the canvas +*/ +EditBitmapWidget.prototype.updateSize = function() { + this.widthDomNode.value = this.currCanvas.width; + this.heightDomNode.value = this.currCanvas.height; +}; + +/* +** Change the size of the canvas, preserving the current image +*/ +EditBitmapWidget.prototype.changeCanvasSize = function(newWidth,newHeight) { + // Create and size a new canvas + var newCanvas = this.document.createElement("canvas"); + this.initCanvas(newCanvas,newWidth,newHeight); + // Copy the old image + var ctx = newCanvas.getContext("2d"); + ctx.drawImage(this.currCanvas,0,0); + // Set the new canvas as the current one + this.currCanvas = newCanvas; + // Set the size of the onscreen canvas + this.canvasDomNode.width = newWidth; + this.canvasDomNode.height = newHeight; + // Paint the onscreen canvas with the offscreen canvas + ctx = this.canvasDomNode.getContext("2d"); + ctx.drawImage(this.currCanvas,0,0); +}; + +EditBitmapWidget.prototype.handleWidthChangeEvent = function(event) { + // Get the new width + var newWidth = parseInt(this.widthDomNode.value,10); + // Update if necessary + if(newWidth > 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/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 0abe08c69..91dd94651 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -527,11 +527,14 @@ a.tw-tiddlylink-external { } canvas.tw-edit-bitmapeditor { - border: 3px dashed #F0F0F0; + border: 6px solid #fff; cursor: crosshair; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; + <> + margin-top: 6px; + margin-bottom: 6px; } .tw-edit-bitmapeditor-width { From 124a7bed951a1a7e10d5b29095d546640620c851 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 27 Oct 2013 13:54:09 +0000 Subject: [PATCH 103/183] Build empty.html with the new widget mechanism --- nbld.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nbld.sh b/nbld.sh index 042181f4c..fa05689ce 100755 --- a/nbld.sh +++ b/nbld.sh @@ -37,6 +37,14 @@ node ./tiddlywiki.js \ --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \ || exit 1 +# Third, empty.html: empty wiki for reuse + +node ./tiddlywiki.js \ + ./editions/empty \ + --verbose \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/empty.html text/plain \ + || exit 1 + # Fifth, d3demo.html: wiki to demo d3 plugin node ./tiddlywiki.js \ From b1208a8d3751f2655edf3f8c7f23308d259bd7f4 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 27 Oct 2013 13:54:39 +0000 Subject: [PATCH 104/183] Fix problem with getTiddlerLinks() It was crashing if it encountered a link widget that didn't have a `to` attribute. --- core/modules/wiki.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 15d6c5545..d76fb1ba8 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -335,7 +335,7 @@ exports.getTiddlerLinks = function(title) { checkParseTree = function(parseTree) { for(var t=0; t Date: Sun, 27 Oct 2013 22:52:04 +0000 Subject: [PATCH 105/183] Remove inaccurate comments --- core/modules/new_widgets/widget.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index b542935ef..3eabce1cf 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -23,8 +23,6 @@ Options include: 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 Widget = function(parseTreeNode,options) { if(arguments.length > 0) { From c7d56361ee967be99ca089cb579f5f288f146b8b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 27 Oct 2013 22:52:34 +0000 Subject: [PATCH 106/183] Parse and render macro attributes, rather than using the raw text --- core/modules/new_widgets/widget.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index 3eabce1cf..cfc10b6b1 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -217,7 +217,8 @@ Widget.prototype.computeAttributes = function() { if(attribute.type === "indirect") { value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("tiddlerTitle")); } else if(attribute.type === "macro") { - value = self.getVariable(attribute.value.name,{params: attribute.value.params}); + var text = self.getVariable(attribute.value.name,{params: attribute.value.params}); + value = self.wiki.new_renderText("text/plain","text/vnd.tiddlywiki",text); } else { // String attribute value = attribute.value; } From d84e398d7a9a35ff8a0be138f9bef88310d79fa4 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 27 Oct 2013 22:53:06 +0000 Subject: [PATCH 107/183] Fix problem when widgets don't have a parse tree node --- core/modules/new_widgets/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index cfc10b6b1..bd1159ec6 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -271,7 +271,7 @@ Make child widgets correspondng to specified parseTreeNodes Widget.prototype.makeChildWidgets = function(parseTreeNodes) { this.children = []; var self = this; - $tw.utils.each(parseTreeNodes || this.parseTreeNode.children,function(childNode) { + $tw.utils.each(parseTreeNodes || (this.parseTreeNode && this.parseTreeNode.children),function(childNode) { self.children.push(self.makeChildWidget(childNode)); }); }; From 5610efff011aac371ded0b50044a5f26db4536d1 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 27 Oct 2013 22:53:22 +0000 Subject: [PATCH 108/183] Update typedblock parser to use new widget mechanism --- core/modules/parsers/wikiparser/rules/typedblock.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/modules/parsers/wikiparser/rules/typedblock.js b/core/modules/parsers/wikiparser/rules/typedblock.js index 6af2742f6..3ee3b5e35 100644 --- a/core/modules/parsers/wikiparser/rules/typedblock.js +++ b/core/modules/parsers/wikiparser/rules/typedblock.js @@ -28,6 +28,8 @@ $$$ /*global $tw: false */ "use strict"; +var widget = require("$:/core/modules/new_widgets/widget.js"); + exports.name = "typedblock"; exports.types = {block: true}; @@ -63,10 +65,13 @@ exports.parse = function() { return parser.tree; } else { // Otherwise, render to the rendertype and return in a
 tag
-		var renderTree = new $tw.WikiRenderTree(parser,{wiki: $tw.wiki, document: $tw.document});
-		renderTree.execute();
+		var parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined,
+			widgetNode = new widget.widget(parseTreeNode,{
+				wiki: $tw.wiki,
+				document: $tw.document
+			});
 		var container = $tw.document.createElement("div");
-		renderTree.renderInDom(container);
+		widgetNode.render(container,null);
 		var text = renderType === "text/html" ? container.innerHTML : container.textContent;
 		return [{
 			type: "element",

From 20f03de71209e962bdb6abed136d0d8d3130c2c7 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston 
Date: Sun, 27 Oct 2013 22:55:36 +0000
Subject: [PATCH 109/183] The big purge of the old widget mechanism

Getting rid of the old widget mechanism files finally gives us a payoff
for all the refactoring. Still a bit of tidying up to do, and we need
to re-introduce the animation mechanisms.
---
 2bld.sh                                       |   6 +-
 bld.sh                                        |  20 +-
 core/modules/commands/rendertiddler.js        |  44 --
 core/modules/commands/rendertiddlers.js       |  54 ---
 core/modules/rendertree/renderers/element.js  | 212 ---------
 core/modules/rendertree/renderers/entity.js   |  31 --
 .../modules/rendertree/renderers/macrocall.js |  65 ---
 core/modules/rendertree/renderers/macrodef.js |  30 --
 core/modules/rendertree/renderers/raw.js      |  33 --
 core/modules/rendertree/renderers/text.js     |  31 --
 core/modules/rendertree/wikirendertree.js     | 198 ---------
 core/modules/widgets/button.js                | 150 -------
 core/modules/widgets/checkbox.js              | 111 -----
 core/modules/widgets/count.js                 |  59 ---
 core/modules/widgets/datauri.js               |  53 ---
 core/modules/widgets/edit/edit.js             |  87 ----
 .../widgets/edit/editors/bitmapeditor.js      | 318 --------------
 .../widgets/edit/editors/texteditor.js        | 219 ----------
 core/modules/widgets/encrypt.js               |  51 ---
 core/modules/widgets/error.js                 |  37 --
 core/modules/widgets/fieldmangler.js          |  87 ----
 core/modules/widgets/fields.js                |  94 ----
 core/modules/widgets/grid.js                  | 109 -----
 core/modules/widgets/import.js                | 256 -----------
 core/modules/widgets/info.js                  | 115 -----
 core/modules/widgets/link.js                  | 203 ---------
 core/modules/widgets/linkcatcher.js           |  71 ---
 core/modules/widgets/list/list.js             | 408 ------------------
 .../modules/widgets/list/listviews/classic.js | 100 -----
 core/modules/widgets/list/listviews/pop.js    |  75 ----
 .../widgets/list/listviews/scroller.js        |  31 --
 core/modules/widgets/list/listviews/zoomin.js | 207 ---------
 core/modules/widgets/navigator.js             | 301 -------------
 core/modules/widgets/password.js              |  45 --
 core/modules/widgets/reveal.js                | 216 ----------
 core/modules/widgets/setstyle.js              |  58 ---
 core/modules/widgets/tiddler.js               |  82 ----
 core/modules/widgets/transclude.js            | 105 -----
 core/modules/widgets/version.js               |  34 --
 core/modules/widgets/video.js                 |  70 ---
 core/modules/widgets/view/view.js             | 120 ------
 core/modules/widgets/view/viewers/date.js     |  41 --
 .../widgets/view/viewers/htmlencoded.js       |  44 --
 .../widgets/view/viewers/htmlwikified.js      |  44 --
 .../modules/widgets/view/viewers/jsencoded.js |  44 --
 core/modules/widgets/view/viewers/link.js     |  44 --
 .../widgets/view/viewers/relativedate.js      |  77 ----
 .../widgets/view/viewers/urlencoded.js        |  44 --
 core/modules/widgets/view/viewers/wikified.js |  43 --
 core/modules/wiki.js                          |  48 +--
 .../tahoelafs/tiddlers/tests/test-wikitext.js |  47 --
 .../tiddlers/tests/test-wikitext-parser.js    |   6 +-
 editions/test/tiddlers/tests/test-wikitext.js |  15 +-
 editions/tw2/readme.md                        |   6 +-
 ginsu.sh                                      |   4 +-
 test.sh                                       |   2 +-
 wbld.sh                                       |   2 +-
 57 files changed, 39 insertions(+), 5068 deletions(-)
 delete mode 100644 core/modules/commands/rendertiddler.js
 delete mode 100644 core/modules/commands/rendertiddlers.js
 delete mode 100644 core/modules/rendertree/renderers/element.js
 delete mode 100644 core/modules/rendertree/renderers/entity.js
 delete mode 100644 core/modules/rendertree/renderers/macrocall.js
 delete mode 100644 core/modules/rendertree/renderers/macrodef.js
 delete mode 100644 core/modules/rendertree/renderers/raw.js
 delete mode 100644 core/modules/rendertree/renderers/text.js
 delete mode 100644 core/modules/rendertree/wikirendertree.js
 delete mode 100644 core/modules/widgets/button.js
 delete mode 100644 core/modules/widgets/checkbox.js
 delete mode 100644 core/modules/widgets/count.js
 delete mode 100644 core/modules/widgets/datauri.js
 delete mode 100644 core/modules/widgets/edit/edit.js
 delete mode 100644 core/modules/widgets/edit/editors/bitmapeditor.js
 delete mode 100644 core/modules/widgets/edit/editors/texteditor.js
 delete mode 100644 core/modules/widgets/encrypt.js
 delete mode 100644 core/modules/widgets/error.js
 delete mode 100644 core/modules/widgets/fieldmangler.js
 delete mode 100644 core/modules/widgets/fields.js
 delete mode 100644 core/modules/widgets/grid.js
 delete mode 100644 core/modules/widgets/import.js
 delete mode 100644 core/modules/widgets/info.js
 delete mode 100644 core/modules/widgets/link.js
 delete mode 100644 core/modules/widgets/linkcatcher.js
 delete mode 100644 core/modules/widgets/list/list.js
 delete mode 100644 core/modules/widgets/list/listviews/classic.js
 delete mode 100644 core/modules/widgets/list/listviews/pop.js
 delete mode 100644 core/modules/widgets/list/listviews/scroller.js
 delete mode 100644 core/modules/widgets/list/listviews/zoomin.js
 delete mode 100644 core/modules/widgets/navigator.js
 delete mode 100644 core/modules/widgets/password.js
 delete mode 100644 core/modules/widgets/reveal.js
 delete mode 100644 core/modules/widgets/setstyle.js
 delete mode 100644 core/modules/widgets/tiddler.js
 delete mode 100644 core/modules/widgets/transclude.js
 delete mode 100644 core/modules/widgets/version.js
 delete mode 100644 core/modules/widgets/video.js
 delete mode 100644 core/modules/widgets/view/view.js
 delete mode 100644 core/modules/widgets/view/viewers/date.js
 delete mode 100644 core/modules/widgets/view/viewers/htmlencoded.js
 delete mode 100644 core/modules/widgets/view/viewers/htmlwikified.js
 delete mode 100644 core/modules/widgets/view/viewers/jsencoded.js
 delete mode 100644 core/modules/widgets/view/viewers/link.js
 delete mode 100644 core/modules/widgets/view/viewers/relativedate.js
 delete mode 100644 core/modules/widgets/view/viewers/urlencoded.js
 delete mode 100644 core/modules/widgets/view/viewers/wikified.js
 delete mode 100644 editions/tahoelafs/tiddlers/tests/test-wikitext.js

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..40436ffe4 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,7 +73,7 @@ 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
diff --git a/core/modules/commands/rendertiddler.js b/core/modules/commands/rendertiddler.js
deleted file mode 100644
index b63d9855d..000000000
--- a/core/modules/commands/rendertiddler.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*\
-title: $:/core/modules/commands/rendertiddler.js
-type: application/javascript
-module-type: command
-
-Command to render a tiddler and save it to a file
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-exports.info = {
-	name: "rendertiddler",
-	synchronous: false
-};
-
-var Command = function(params,commander,callback) {
-	this.params = params;
-	this.commander = commander;
-	this.callback = callback;
-};
-
-Command.prototype.execute = function() {
-	if(this.params.length < 2) {
-		return "Missing filename";
-	}
-	var self = this,
-		fs = require("fs"),
-		path = require("path"),
-		title = this.params[0],
-		filename = this.params[1],
-		type = this.params[2] || "text/html";
-	fs.writeFile(filename,this.commander.wiki.renderTiddler(type,title),"utf8",function(err) {
-		self.callback(err);
-	});
-	return null;
-};
-
-exports.Command = Command;
-
-})();
diff --git a/core/modules/commands/rendertiddlers.js b/core/modules/commands/rendertiddlers.js
deleted file mode 100644
index 1bb1fa448..000000000
--- a/core/modules/commands/rendertiddlers.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/*\
-title: $:/core/modules/commands/rendertiddlers.js
-type: application/javascript
-module-type: command
-
-Command to render several tiddlers to a folder of files
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-exports.info = {
-	name: "rendertiddlers",
-	synchronous: true
-};
-
-var Command = function(params,commander,callback) {
-	this.params = params;
-	this.commander = commander;
-	this.callback = callback;
-};
-
-Command.prototype.execute = function() {
-	if(this.params.length < 2) {
-		return "Missing filename";
-	}
-	var self = this,
-		fs = require("fs"),
-		path = require("path"),
-		wiki = this.commander.wiki,
-		filter = this.params[0],
-		template = this.params[1],
-		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 container = $tw.document.createElement("div");
-		renderTree.renderInDom(container);
-		var text = type === "text/html" ? container.innerHTML : container.textContent;
-		fs.writeFileSync(path.resolve(pathname,encodeURIComponent(title) + extension),text,"utf8");
-	});
-	return null;
-};
-
-exports.Command = Command;
-
-})();
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
-
-<$checkbox tiddler="HelloThere" tag="red"/>
-
-<$checkbox tag="done">
-<$view field="title" format="link"/>
-
-```
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var CheckboxWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Generate child nodes
-	this.generate();
-};
-
-CheckboxWidget.prototype.generate = function() {
-	// Get the parameters from the attributes
-	this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
-	this.tagName = this.renderer.getAttribute("tag");
-	this["class"] = this.renderer.getAttribute("class");
-	// Compute classes
-	var classes = ["tw-checkbox"];
-	if(this["class"]) {
-		$tw.utils.pushTop(classes,this["class"]);
-	}
-	// Create the checkbox and span elements
-	var nodeCheckbox = {
-			type: "element",
-			tag: "input",
-			attributes: {
-				type: {type: "string", value: "checkbox"}
-			}
-		},
-		nodeSpan = {
-			type: "element",
-			tag: "span",
-			children: this.renderer.parseTreeNode.children
-		};
-	// Set the state of the checkbox
-	if(this.getValue()) {
-		$tw.utils.addAttributeToParseTreeNode(nodeCheckbox,"checked","true");
-	}
-	// Set the return element
-	this.tag = "label";
-	this.attributes ={"class": classes.join(" ")};
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,[nodeCheckbox,nodeSpan]);
-	this.events = [{name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"}];
-};
-
-CheckboxWidget.prototype.getValue = function() {
-	var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
-	return tiddler ? tiddler.hasTag(this.tagName) : false;
-};
-
-CheckboxWidget.prototype.handleChangeEvent  = function(event) {
-	var checked = this.children[0].domNode.checked,
-		tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
-	if(tiddler && tiddler.hasTag(this.tagName) !== checked) {
-		var newTags = tiddler.fields.tags.slice(0),
-			pos = newTags.indexOf(this.tagName);
-		if(pos !== -1) {
-			newTags.splice(pos,1);
-		}
-		if(checked) {
-			newTags.push(this.tagName);
-		}
-		this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,{tags: newTags}));
-	}
-};
-
-CheckboxWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
-	// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
-	if(changedAttributes.tiddler || changedAttributes.tag || changedAttributes["class"]) {
-		// Regenerate and rerender the widget and replace the existing DOM node
-		this.generate();
-		var oldDomNode = this.renderer.domNode,
-			newDomNode = this.renderer.renderInDom();
-		oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
-	} else {
-		// Update the checkbox if necessary
-		if(changedTiddlers[this.tiddlerTitle]) {
-			this.children[0].domNode.checked = this.getValue();
-		}
-		// Refresh children
-		$tw.utils.each(this.children,function(node) {
-			if(node.refreshInDom) {
-				node.refreshInDom(changedTiddlers);
-			}
-		});
-	}
-};
-
-exports.checkbox = CheckboxWidget;
-
-})();
diff --git a/core/modules/widgets/count.js b/core/modules/widgets/count.js
deleted file mode 100644
index 2c3770e70..000000000
--- a/core/modules/widgets/count.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/*\
-title: $:/core/modules/widgets/count.js
-type: application/javascript
-module-type: widget
-
-Implements the count widget that displays the number of tiddlers that match a filter
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var CountWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Execute the filter to get the initial count
-	this.executeFilter();
-	// Generate child nodes
-	this.generate();
-};
-
-CountWidget.prototype.executeFilter = function() {
-	// Get attributes
-	this.filter = this.renderer.getAttribute("filter");
-	// Execute the filter
-	if(this.filter) {
-		this.currentCount = this.renderer.renderTree.wiki.filterTiddlers(this.filter,this.renderer.tiddlerTitle).length;
-	} else {
-		this.currentCount = undefined;
-	}
-};
-
-CountWidget.prototype.generate = function() {
-	// Set the element
-	this.tag = "span";
-	this.attributes = {};
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,[
-		{type: "text", text: this.currentCount !== undefined ? this.currentCount.toString() : ""}
-	]);
-};
-
-CountWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
-	// Re-execute the filter to get the count
-	var oldCount = this.currentCount;
-	this.executeFilter();
-	if(this.currentCount !== oldCount) {
-		// Regenerate and rerender the widget and replace the existing DOM node
-		this.generate();
-		var oldDomNode = this.renderer.domNode,
-			newDomNode = this.renderer.renderInDom();
-		oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
-	}
-};
-
-exports.count = CountWidget;
-
-})();
diff --git a/core/modules/widgets/datauri.js b/core/modules/widgets/datauri.js
deleted file mode 100644
index a7920cf54..000000000
--- a/core/modules/widgets/datauri.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*\
-title: $:/core/modules/widgets/datauri.js
-type: application/javascript
-module-type: widget
-
-The datauri widget displays the contents of a tiddler as a data URI.
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var DataUriWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Generate child nodes
-	this.generate();
-};
-
-DataUriWidget.prototype.generate = function() {
-	// Get parameters from our attributes
-	this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
-	// Compose the data URI
-	var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
-		uri = "";
-	if(tiddler) {
-		var type = tiddler.fields.type || "text/vnd.tiddlywiki",
-			typeInfo = $tw.config.contentTypeInfo[type],
-			isBase64 = typeInfo && typeInfo.encoding === "base64",
-			parts = ["data:"];
-		parts.push(type);
-		parts.push(isBase64 ? ";base64" : "");
-		parts.push(",");
-		parts.push(isBase64 ? tiddler.fields.text : encodeURIComponent(tiddler.fields.text));
-		uri = parts.join("");
-	}
-	// Set the element
-	this.tag = "pre";
-	this.attributes = {
-		"class": "tw-data-uri"
-	};
-	// Create the renderers for the wrapper and the children
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
-		type: "text",
-		text: uri
-	}]);
-};
-
-exports.datauri = DataUriWidget;
-
-})();
\ No newline at end of file
diff --git a/core/modules/widgets/edit/edit.js b/core/modules/widgets/edit/edit.js
deleted file mode 100644
index 74d64d0b9..000000000
--- a/core/modules/widgets/edit/edit.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/*\
-title: $:/core/modules/widgets/edit/edit.js
-type: application/javascript
-module-type: widget
-
-The edit widget uses editor plugins to edit tiddlers of different types.
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var EditWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Initialise the editors if they've not been done already
-	if(!this.editors) {
-		EditWidget.prototype.editors = {};
-		$tw.modules.applyMethods("editor",this.editors);
-	}
-	// Generate child nodes
-	this.generate();
-};
-
-EditWidget.prototype.generate = function() {
-	// Get parameters from our attributes
-	this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
-	this.fieldName = this.renderer.getAttribute("field");
-	this.indexName = this.renderer.getAttribute("index");
-	if(!this.fieldName && !this.indexName) {
-		this.fieldName = "text";
-	}
-	// Choose the editor to use
-	// TODO: Tiddler field modules should be able to specify a field type from which the editor is derived
-	this.editorName = this.chooseEditor();
-	var Editor = this.editors[this.editorName];
-	// Instantiate the editor
-	this.editor = new Editor(this,this.tiddlerTitle,this.fieldName,this.indexName);
-	// Ask the editor to create the widget element
-	this.editor.render();
-};
-
-/*
-Return the name of the editor that should handle this tiddler field
-*/
-EditWidget.prototype.chooseEditor = function() {
-	var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
-	if(this.fieldName === "text" && tiddler && tiddler.fields.type && this.editors[tiddler.fields.type]) {
-		return tiddler.fields.type;
-	}
-	return "text/vnd.tiddlywiki";
-};
-
-EditWidget.prototype.postRenderInDom = function() {
-	if(this.editor && this.editor.postRenderInDom) {
-		this.editor.postRenderInDom();
-	}
-};
-
-EditWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
-	// We'll completely regenerate ourselves if any of our attributes have changed
-	if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.format || this.chooseEditor() !== this.editorName) {
-		// Regenerate and rerender the widget and replace the existing DOM node
-		this.generate();
-		var oldDomNode = this.renderer.domNode,
-			newDomNode = this.renderer.renderInDom();
-		oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
-	} else if(this.tiddlerTitle && changedTiddlers[this.tiddlerTitle]) {
-		// Refresh the editor if our tiddler has changed
-		if(this.editor && this.editor.refreshInDom) {
-			this.editor.refreshInDom(changedTiddlers);
-		}
-	} else {
-		// Otherwise, just refresh any child nodes
-		$tw.utils.each(this.children,function(node) {
-			if(node.refreshInDom) {
-				node.refreshInDom(changedTiddlers);
-			}
-		});
-	}
-};
-
-exports.edit = EditWidget;
-
-})();
diff --git a/core/modules/widgets/edit/editors/bitmapeditor.js b/core/modules/widgets/edit/editors/bitmapeditor.js
deleted file mode 100644
index c2882dbb0..000000000
--- a/core/modules/widgets/edit/editors/bitmapeditor.js
+++ /dev/null
@@ -1,318 +0,0 @@
-/*\
-title: $:/core/modules/widgets/edit/editors/bitmapeditor.js
-type: application/javascript
-module-type: editor
-
-A bitmap editor
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-// Default images sizes
-var DEFAULT_IMAGE_WIDTH = 300,
-	DEFAULT_IMAGE_HEIGHT = 185;
-
-// The elements of the editor UI
-var DOM_CANVAS = 0,
-	DOM_WIDTH = 1,
-	DOM_HEIGHT = 2;
-
-var BitmapEditor = function(editWidget,tiddlerTitle,fieldName,indexName) {
-	this.editWidget = editWidget;
-	this.tiddlerTitle = tiddlerTitle;
-	this.fieldName = fieldName;
-};
-
-BitmapEditor.prototype.render = function() {
-	// Set the element details
-	this.editWidget.tag = "div";
-	this.editWidget.attributes = {
-		"class": "tw-edit-bitmapeditor-wrapper"
-	};
-	var children = [{
-		type: "element",
-		tag: "canvas",
-		attributes: {
-			"class": {type: "string", value: "tw-edit-bitmapeditor"}
-		},
-		events: [{
-			name: "touchstart",
-			handlerObject: this,
-			handlerMethod: "handleTouchStartEvent"
-		},{
-			name: "touchmove",
-			handlerObject: this,
-			handlerMethod: "handleTouchMoveEvent"
-		},{
-			name: "touchend",
-			handlerObject: this,
-			handlerMethod: "handleTouchEndEvent"
-		},{
-			name: "mousedown",
-			handlerObject: this,
-			handlerMethod: "handleMouseDownEvent"
-		},{
-			name: "mousemove",
-			handlerObject: this,
-			handlerMethod: "handleMouseMoveEvent"
-		},{
-			name: "mouseup",
-			handlerObject: this,
-			handlerMethod: "handleMouseUpEvent"
-		}]
-	},{
-		type: "element",
-		tag: "input",
-		attributes: {
-			"class": {type: "string", value: "tw-edit-bitmapeditor-width"},
-			"type": {type: "string", value: "number"},
-			"value": {type: "string", value: ""}
-		},
-		events: [{
-			name: "change",
-			handlerObject: this,
-			handlerMethod: "handleWidthChangeEvent"
-		}]
-	},{
-		type: "element",
-		tag: "input",
-		attributes: {
-			"class": {type: "string", value: "tw-edit-bitmapeditor-height"},
-			"type": {type: "string", value: "number"},
-			"value": {type: "string", value: ""}
-		},
-		events: [{
-			name: "change",
-			handlerObject: this,
-			handlerMethod: "handleHeightChangeEvent"
-		}]
-	}];
-	this.editWidget.children = this.editWidget.renderer.renderTree.createRenderers(this.editWidget.renderer,children);
-};
-
-BitmapEditor.prototype.postRenderInDom = function() {
-	var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
-		canvas = this.getDomNode(DOM_CANVAS),
-		currImage = new Image();
-	// Set up event handlers for loading the image
-	var self = this;
-	currImage.onload = function() {
-		// Copy the image to the on-screen canvas
-		self.initCanvas(canvas,currImage.width,currImage.height,currImage);
-		// And also copy the current bitmap to the off-screen canvas
-		self.currCanvas = self.editWidget.renderer.renderTree.document.createElement("canvas");
-		self.initCanvas(self.currCanvas,currImage.width,currImage.height,currImage);
-		// Set the width and height input boxes
-		self.updateSize();
-	};
-	currImage.onerror = function() {
-		// Set the on-screen canvas size and clear it
-		self.initCanvas(canvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
-		// Set the off-screen canvas size and clear it
-		self.currCanvas = self.editWidget.renderer.renderTree.document.createElement("canvas");
-		self.initCanvas(self.currCanvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
-		// Set the width and height input boxes
-		self.updateSize();
-	}
-	// Get the current bitmap into an image object
-	currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
-};
-
-BitmapEditor.prototype.initCanvas = function(canvas,width,height,image) {
-	canvas.width = width;
-	canvas.height = height;
-	var ctx = canvas.getContext("2d");
-	if(image) {
-		ctx.drawImage(image,0,0);
-	} else {
-		ctx.fillStyle = "#fff";
-		ctx.fillRect(0,0,canvas.width,canvas.height);
-	}
-}
-
-BitmapEditor.prototype.getDomNode = function(index) {
-	return this.editWidget.renderer.domNode.childNodes[index];
-};
-
-/*
-** Update the input boxes with the actual size of the canvas
-*/
-BitmapEditor.prototype.updateSize = function() {
-	this.getDomNode(DOM_WIDTH).value = this.currCanvas.width;
-	this.getDomNode(DOM_HEIGHT).value = this.currCanvas.height;
-};
-
-/*
-** Change the size of the canvas, preserving the current image
-*/
-BitmapEditor.prototype.changeCanvasSize = function(newWidth,newHeight) {
-	// Create and size a new canvas
-	var newCanvas = this.editWidget.renderer.renderTree.document.createElement("canvas");
-	this.initCanvas(newCanvas,newWidth,newHeight);
-	// Copy the old image
-	var ctx = newCanvas.getContext("2d");
-	ctx.drawImage(this.currCanvas,0,0);
-	// Set the new canvas as the current one
-	this.currCanvas = newCanvas;
-	// Set the size of the onscreen canvas
-	var canvas = this.getDomNode(DOM_CANVAS);
-	canvas.width = newWidth;
-	canvas.height = newHeight;
-	// Paint the onscreen canvas with the offscreen canvas
-	ctx = canvas.getContext("2d");
-	ctx.drawImage(this.currCanvas,0,0);
-};
-
-BitmapEditor.prototype.handleWidthChangeEvent = function(event) {
-	// Get the new width
-	var newWidth = parseInt(this.getDomNode(DOM_WIDTH).value,10);
-	// Update if necessary
-	if(newWidth > 0 && newWidth !== this.currCanvas.width) {
-		this.changeCanvasSize(newWidth,this.currCanvas.height);
-	}
-	// Update the input controls
-	this.updateSize();
-};
-
-BitmapEditor.prototype.handleHeightChangeEvent = function(event) {
-	// Get the new width
-	var newHeight = parseInt(this.getDomNode(DOM_HEIGHT).value,10);
-	// Update if necessary
-	if(newHeight > 0 && newHeight !== this.currCanvas.height) {
-		this.changeCanvasSize(this.currCanvas.width,newHeight);
-	}
-	// Update the input controls
-	this.updateSize();
-};
-
-BitmapEditor.prototype.handleTouchStartEvent = function(event) {
-	this.brushDown = true;
-	this.strokeStart(event.touches[0].clientX,event.touches[0].clientY);
-	event.preventDefault();
-	event.stopPropagation();
-	return false;
-};
-
-BitmapEditor.prototype.handleTouchMoveEvent = function(event) {
-	if(this.brushDown) {
-		this.strokeMove(event.touches[0].clientX,event.touches[0].clientY);
-	}
-	event.preventDefault();
-	event.stopPropagation();
-	return false;
-};
-
-BitmapEditor.prototype.handleTouchEndEvent = function(event) {
-	if(this.brushDown) {
-		this.brushDown = false;
-		this.strokeEnd();
-	}
-	event.preventDefault();
-	event.stopPropagation();
-	return false;
-};
-
-BitmapEditor.prototype.handleMouseDownEvent = function(event) {
-	this.strokeStart(event.clientX,event.clientY);
-	this.brushDown = true;
-	event.preventDefault();
-	event.stopPropagation();
-	return false;
-};
-
-BitmapEditor.prototype.handleMouseMoveEvent = function(event) {
-	if(this.brushDown) {
-		this.strokeMove(event.clientX,event.clientY);
-		event.preventDefault();
-		event.stopPropagation();
-		return false;
-	}
-	return true;
-};
-
-BitmapEditor.prototype.handleMouseUpEvent = function(event) {
-	if(this.brushDown) {
-		this.brushDown = false;
-		this.strokeEnd();
-		event.preventDefault();
-		event.stopPropagation();
-		return false;
-	}
-	return true;
-};
-
-BitmapEditor.prototype.adjustCoordinates = function(x,y) {
-	var canvas = this.getDomNode(DOM_CANVAS),
-		canvasRect = canvas.getBoundingClientRect(),
-		scale = canvas.width/canvasRect.width;
-	return {x: (x - canvasRect.left) * scale, y: (y - canvasRect.top) * scale};
-};
-
-BitmapEditor.prototype.strokeStart = function(x,y) {
-	// Start off a new stroke
-	this.stroke = [this.adjustCoordinates(x,y)];
-};
-
-BitmapEditor.prototype.strokeMove = function(x,y) {
-	var canvas = this.getDomNode(DOM_CANVAS),
-		ctx = canvas.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.getDomNode(DOM_CANVAS).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.editWidget.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
-	}
-};
-
-/*
-Note that the bitmap editor intentionally doesn't have a refreshInDom method to avoid the situation where a bitmap being editted is modified externally
-*/
-
-exports["image/jpg"] = BitmapEditor;
-exports["image/jpeg"] = BitmapEditor;
-exports["image/png"] = BitmapEditor;
-exports["image/gif"] = BitmapEditor;
-
-})();
diff --git a/core/modules/widgets/edit/editors/texteditor.js b/core/modules/widgets/edit/editors/texteditor.js
deleted file mode 100644
index 52c1db49d..000000000
--- a/core/modules/widgets/edit/editors/texteditor.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/*\
-title: $:/core/modules/widgets/edit/editors/texteditor.js
-type: application/javascript
-module-type: editor
-
-A plain text editor
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var MIN_TEXT_AREA_HEIGHT = 100;
-
-var TextEditor = function(editWidget,tiddlerTitle,fieldName,indexName) {
-	this.editWidget = editWidget;
-	this.tiddlerTitle = tiddlerTitle;
-	this.fieldName = fieldName;
-	this.indexName = indexName;
-};
-
-/*
-Get the tiddler being edited and current value
-*/
-TextEditor.prototype.getEditInfo = function() {
-	var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
-		value;
-	if(this.fieldName) {
-		// 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.fieldName);
-		} else {
-			// Otherwise, we need to construct a default value for the editor
-			switch(this.fieldName) {
-				case "text":
-					value = "Type the text for the tiddler '" + this.tiddlerTitle + "'";
-					break;
-				case "title":
-					value = this.tiddlerTitle;
-					break;
-				default:
-					value = "";
-					break;
-			}
-			value = this.editWidget.renderer.getAttribute("default",value);
-		}
-	} else {
-		value = this.editWidget.renderer.renderTree.wiki.extractTiddlerDataItem(this.tiddlerTitle,this.indexName,this.editWidget.renderer.getAttribute("default"));
-	}
-	return {tiddler: tiddler, value: value};
-};
-
-TextEditor.prototype.render = function() {
-	// Get the initial value of the editor
-	var editInfo = this.getEditInfo();
-	// Create the editor nodes
-	var node = {
-		type: "element",
-		attributes: {}
-	};
-	// Get the edit type associated with this field
-	var type = "input";
-	if(this.fieldName === "text") {
-		type = "textarea";
-	} else {
-		var fieldModule = $tw.Tiddler.fieldModules[this.fieldName];
-		if(fieldModule && fieldModule.editType) {
-			type = fieldModule.editType;
-		}
-	}
-	var type = this.editWidget.renderer.getAttribute("type",type);
-	switch(type) {
-		case "textarea":
-			node.tag = "textarea";
-			node.children = [{
-				type: "text",
-				text: editInfo.value
-			}];
-			break;
-		case "color":
-			node.tag = "input";
-			node.attributes.type = {type: "string", value: "color"};
-			node.attributes.value = {type: "string", value: editInfo.value};
-			break;
-		case "search":
-			node.tag = "input";
-			node.attributes.type = {type: "string", value: "search"};
-			node.attributes.value = {type: "string", value: editInfo.value};
-			break;
-		default: // "input"
-			node.tag = "input";
-			node.attributes.type = {type: "string", value: "text"};
-			node.attributes.value = {type: "string", value: editInfo.value};
-			break;
-	}
-	node.events = [
-		{name: "focus", handlerObject: this, handlerMethod: "handleFocusEvent"},
-		{name: "blur", handlerObject: this, handlerMethod: "handleBlurEvent"},
-		{name: "input", handlerObject: this, handlerMethod: "handleInputEvent"}
-	];
-	// Add a placeholder if specified
-	if(this.editWidget.renderer.hasAttribute("placeholder")) {
-		node.attributes.placeholder = {type: "string", value: this.editWidget.renderer.getAttribute("placeholder")};
-	}
-	// Set the element details
-	this.editWidget.tag = this.editWidget.renderer.parseTreeNode.isBlock ? "div" : "span";
-	this.editWidget.attributes = {
-		"class": "tw-edit-texteditor"
-	};
-	if(this.editWidget.renderer.hasAttribute("class")) {
-		this.editWidget.attributes["class"] += " " + this.editWidget.renderer.getAttribute("class");
-	}
-	if(this.editWidget.renderer.hasAttribute("style")) {
-		this.editWidget.attributes.style = this.editWidget.attributes.style || "";
-		this.editWidget.attributes.style += this.editWidget.renderer.getAttribute("style");
-	}
-	this.editWidget.children = this.editWidget.renderer.renderTree.createRenderers(this.editWidget.renderer,[node]);
-};
-
-TextEditor.prototype.setFocus = function() {
-	if(this.editWidget.renderer.hasAttribute("focusSet")) {
-		var title = this.editWidget.renderer.getAttribute("focusSet");
-		if(this.editWidget.renderer.getAttribute("qualifyTiddlerTitles") === "yes") {
-			title =  title + "-" + this.editWidget.renderer.renderTree.getContextScopeId(this.editWidget.renderer.parentRenderer);
-		}
-		$tw.popup.triggerPopup({
-			domNode: this.editWidget.renderer.domNode,
-			title: title,
-			wiki: this.editWidget.renderer.renderTree.wiki,
-			force: true
-		});
-	}
-};
-
-TextEditor.prototype.handleFocusEvent = function(event) {
-//	this.saveChanges();
-//	this.fixHeight();
-	this.setFocus();
-	return true;
-};
-
-TextEditor.prototype.handleBlurEvent = function(event) {
-//	this.saveChanges();
-	return true;
-};
-
-TextEditor.prototype.handleInputEvent = function(event) {
-	this.saveChanges();
-	this.fixHeight();
-	return true;
-};
-
-TextEditor.prototype.saveChanges = function() {
-	var text = this.editWidget.children[0].domNode.value
-	if(this.fieldName) {
-		var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
-		if(!tiddler) {
-			tiddler = new $tw.Tiddler({title: this.tiddlerTitle});
-		}
-		var oldValue = tiddler.getFieldString(this.fieldName);
-		if(text !== oldValue) {
-			var update = {};
-			update[this.fieldName] = text;
-			this.editWidget.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
-		}
-	} else {
-		var data = this.editWidget.renderer.renderTree.wiki.getTiddlerData(this.tiddlerTitle,{});
-		if(data[this.indexName] !== text) {
-			data[this.indexName] = text;
-			this.editWidget.renderer.renderTree.wiki.setTiddlerData(this.tiddlerTitle,data);
-		}
-	}
-};
-
-TextEditor.prototype.fixHeight = function() {
-	var self = this;
-	if(this.editWidget.children[0].domNode && this.editWidget.children[0].domNode.type === "textarea") {
-		$tw.utils.nextTick(function() {
-			// Resize the textarea to fit its content
-			var textarea = self.editWidget.children[0].domNode,
-				scrollPosition = $tw.utils.getScrollPosition(),
-				scrollTop = scrollPosition.y;
-			// Set its height to auto so that it snaps to the correct height
-			textarea.style.height = "auto";
-			// Calculate the revised height
-			var newHeight = Math.max(textarea.scrollHeight + textarea.offsetHeight - textarea.clientHeight,MIN_TEXT_AREA_HEIGHT);
-			// Only try to change the height if it has changed
-			if(newHeight !== textarea.offsetHeight) {
-				textarea.style.height =  newHeight + "px";
-				// Make sure that the dimensions of the textarea are recalculated
-				$tw.utils.forceLayout(textarea);
-				// Check that the scroll position is still visible before trying to scroll back to it
-				scrollTop = Math.min(scrollTop,self.editWidget.renderer.renderTree.document.body.scrollHeight - window.innerHeight);
-				window.scrollTo(scrollPosition.x,scrollTop);
-			}
-		});
-	}
-};
-
-TextEditor.prototype.postRenderInDom = function() {
-	this.fixHeight();
-};
-
-TextEditor.prototype.refreshInDom = function() {
-	if(this.editWidget.renderer.renderTree.document.activeElement !== this.editWidget.children[0].domNode) {
-		var editInfo = this.getEditInfo();
-		this.editWidget.children[0].domNode.value = editInfo.value;
-	}
-	// Fix the height if needed
-	this.fixHeight();
-};
-
-exports["text/vnd.tiddlywiki"] = TextEditor;
-exports["text/plain"] = TextEditor;
-
-})();
diff --git a/core/modules/widgets/encrypt.js b/core/modules/widgets/encrypt.js
deleted file mode 100644
index 342a95027..000000000
--- a/core/modules/widgets/encrypt.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*\
-title: $:/core/modules/widgets/encrypt.js
-type: application/javascript
-module-type: widget
-
-Implements the encrypt widget.
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var EncryptWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Generate child nodes
-	this.generate();
-};
-
-EncryptWidget.prototype.generate = function() {
-	// Get the parameters from the attributes
-	this.filter = this.renderer.getAttribute("filter");
-	// Check whether we've got an encryption password
-	var isEncrypted = $tw.crypto.hasPassword();
-	// Encrypt the filtered tiddlers
-	var tiddlers = this.renderer.renderTree.wiki.filterTiddlers(this.filter),
-		json = {},
-		self = this;
-	$tw.utils.each(tiddlers,function(title) {
-		var tiddler = self.renderer.renderTree.wiki.getTiddler(title),
-			jsonTiddler = {};
-		for(var f in tiddler.fields) {
-			jsonTiddler[f] = tiddler.getFieldString(f);
-		}
-		json[title] = jsonTiddler;
-	});
-	var encryptedText = $tw.utils.htmlEncode($tw.crypto.encrypt(JSON.stringify(json)));
-	// Set the return element
-	this.tag = "pre";
-	this.attributes ={"class": "tw-encrypt"};
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
-		type: "text",
-		text: encryptedText
-	}]);
-};
-
-exports.encrypt = EncryptWidget;
-
-})();
diff --git a/core/modules/widgets/error.js b/core/modules/widgets/error.js
deleted file mode 100644
index cd722bc45..000000000
--- a/core/modules/widgets/error.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*\
-title: $:/core/modules/widgets/error.js
-type: application/javascript
-module-type: widget
-
-The error widget displays an error message.
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var ErrorWidget = function(renderer,errorMessage) {
-	// Save state
-	this.renderer = renderer;
-	this.errorMessage = errorMessage;
-	// Generate child nodes
-	this.generate();
-};
-
-ErrorWidget.prototype.generate = function() {
-	// Set the element details
-	this.tag = "span";
-	this.attributes = {
-		"class": "tw-error-widget"
-	};
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
-			type: "text",
-			text: this.errorMessage
-		}]);
-};
-
-exports.error = ErrorWidget;
-
-})();
diff --git a/core/modules/widgets/fieldmangler.js b/core/modules/widgets/fieldmangler.js
deleted file mode 100644
index 3d5f3de09..000000000
--- a/core/modules/widgets/fieldmangler.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/*\
-title: $:/core/modules/widgets/fieldmangler.js
-type: application/javascript
-module-type: widget
-
-The fieldmangler widget modifies the fields of the current tiddler in response to messages.
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var FieldManglerWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Generate child nodes
-	this.generate();
-};
-
-FieldManglerWidget.prototype.generate = function() {
-	var self = this;
-	// Get parameters from our attributes
-	this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
-	// Set the element
-	this.tag = "div";
-	this.attributes = {
-		"class": "tw-fieldmangler"
-	};
-	// Set event handlers
-	this.events = [
-		{name: "tw-remove-field", handlerObject: this, handlerMethod: "handleRemoveFieldEvent"},
-		{name: "tw-add-field", handlerObject: this, handlerMethod: "handleAddFieldEvent"},
-		{name: "tw-remove-tag", handlerObject: this, handlerMethod: "handleRemoveTagEvent"},
-		{name: "tw-add-tag", handlerObject: this, handlerMethod: "handleAddTagEvent"}
-	];
-	// Render the children
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
-};
-
-FieldManglerWidget.prototype.handleRemoveFieldEvent = function(event) {
-	var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
-		deletion = {};
-	deletion[event.param] = undefined;
-	this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,deletion));
-	return true;
-};
-
-FieldManglerWidget.prototype.handleAddFieldEvent = function(event) {
-	var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
-	if(tiddler && typeof event.param === "string" && event.param !== "" && !$tw.utils.hop(tiddler.fields,event.param)) {
-		var addition = {};
-		addition[event.param] = "";
-		this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,addition));
-	}
-	return true;
-};
-
-FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) {
-	var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
-	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.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
-		}
-	}
-	return true;
-};
-
-FieldManglerWidget.prototype.handleAddTagEvent = function(event) {
-	var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
-	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.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
-	}
-	return true;
-};
-
-exports.fieldmangler = FieldManglerWidget;
-
-})();
diff --git a/core/modules/widgets/fields.js b/core/modules/widgets/fields.js
deleted file mode 100644
index 59a3bad2d..000000000
--- a/core/modules/widgets/fields.js
+++ /dev/null
@@ -1,94 +0,0 @@
-/*\
-title: $:/core/modules/widgets/fields.js
-type: application/javascript
-module-type: widget
-
-The fields widget displays the fields of a tiddler through a text substitution template.
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var FieldsWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Generate child nodes
-	this.generate();
-};
-
-FieldsWidget.prototype.generate = function() {
-	// Get parameters from our attributes
-	this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
-	this.template = this.renderer.getAttribute("template");
-	this.exclude = this.renderer.getAttribute("exclude");
-	this.stripTitlePrefix = this.renderer.getAttribute("stripTitlePrefix","no") === "yes";
-	// Get the tiddler we're displaying
-	var tiddler = this.renderer.renderTree.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) {
-		this.attributes["class"] = classes.join(" ");
-	}
-	if(this.renderer.hasAttribute("style")) {
-		this.attributes.style = this.renderer.getAttribute("style");
-	}
-	if(this.renderer.hasAttribute("tooltip")) {
-		this.attributes.title = this.renderer.getAttribute("tooltip");
-	}
-	// Create the renderers for the wrapper and the children
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
-		type: "text",
-		text: text.join("")
-	}]);
-};
-
-exports.fields = FieldsWidget;
-
-})();
diff --git a/core/modules/widgets/grid.js b/core/modules/widgets/grid.js
deleted file mode 100644
index 8f393fe7f..000000000
--- a/core/modules/widgets/grid.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/*\
-title: $:/core/modules/widgets/grid.js
-type: application/javascript
-module-type: widget
-
-The grid widget.
-
-This example renders a table made up of tiddlers titled `MySheet_A_1`, `MySheet_A_2`, `MySheet_A_3`, ... , `MySheet_B_1`, `MySheet_B_2`, `MySheet_B_3` etc.
-
-```
-<$grid prefix="MySheet" rows=20 cols=20/>
-```
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var GridWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Generate widget elements
-	this.generate();
-};
-
-GridWidget.prototype.generate = function() {
-	// Get our attributes
-	this.prefix = this.renderer.getAttribute("prefix","Grid");
-	this.rows = parseInt(this.renderer.getAttribute("rows","10"),10);
-	this.cols = parseInt(this.renderer.getAttribute("cols","10"),10);
-	this["class"] = this.renderer.getAttribute("class");
-	// Set up the classes
-	var classes = ["tw-grid-frame"];
-	if(this["class"]) {
-		$tw.utils.pushTop(classes,this["class"]);
-	}
-	// Create the grid table element
-	this.tag = "div";
-	this.attributes = {
-		"class": classes.join(" ")
-	};
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,this.generateTable());
-};
-
-GridWidget.prototype.generateTable = function() {
-	var rows = [];
-	for(var row=0; row0) {
-		c = String.fromCharCode(col % 26 + "A".charCodeAt(0) - 1) + c;
-		col = Math.floor(col/26);
-	}
-	return this.prefix + "_" + c + "_" + (row + 1);
-};
-
-GridWidget.prototype.postRenderInDom = function() {
-};
-
-GridWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
-	// Reexecute the widget if any of our attributes have changed
-	if(true) {
-		// Regenerate and rerender the widget and replace the existing DOM node
-		this.generate();
-		var oldDomNode = this.renderer.domNode,
-			newDomNode = this.renderer.renderInDom();
-		oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
-	} else {
-	}
-};
-
-exports.grid = GridWidget;
-
-})();
diff --git a/core/modules/widgets/import.js b/core/modules/widgets/import.js
deleted file mode 100644
index 8051a7a76..000000000
--- a/core/modules/widgets/import.js
+++ /dev/null
@@ -1,256 +0,0 @@
-/*\
-title: $:/core/modules/widgets/import.js
-type: application/javascript
-module-type: widget
-
-Implements the import widget.
-
-```
-<$import>
-Import using the "browse..." button or drag files onto this text
-
-```
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var ImportWidget = function(renderer) {
-	// Save state
-	this.renderer = renderer;
-	// Generate child nodes
-	this.generate();
-};
-
-ImportWidget.prototype.generate = function() {
-	// Get the parameters from the attributes
-	this.browse = this.renderer.getAttribute("browse","yes");
-	this["class"] = this.renderer.getAttribute("class");
-	// Compute classes
-	var classes = ["tw-import"];
-	if(this["class"]) {
-		$tw.utils.pushTop(classes,this["class"]);
-	}
-	// Create the file input and container elements
-	var fileInput = {
-			type: "element",
-			tag: "input",
-			attributes: {
-				type: {type: "string", value: "file"},
-				style: {type: "string", value: this.browse === "no" ? "display: none;" : "display: block;"}
-			},
-			events: [{name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"}]
-		},
-		container = {
-			type: "element",
-			tag: "div",
-			children: this.renderer.parseTreeNode.children,
-			events: [
-				{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"}]
-		};
-	// Set the return element
-	this.tag = "div";
-	this.attributes = {
-		"class": classes.join(" ")
-	};
-	this.children = this.renderer.renderTree.createRenderers(this.renderer,[fileInput,container]);
-};
-
-ImportWidget.prototype.handleChangeEvent  = function(event) {
-	event.stopPropagation();
-	this.importFiles(event.target.files);
-};
-
-ImportWidget.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.renderer.domNode,"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();
-};
-
-ImportWidget.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
-};
-
-ImportWidget.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.renderer.domNode,"tw-dragover");
-	}
-};
-
-ImportWidget.prototype.handleDropEvent  = function(event) {
-	var dataTransfer = event.dataTransfer;
-	// Reset the enter count
-	this.dragEnterCount = 0;
-	// Remove highlighting
-	$tw.utils.removeClass(this.renderer.domNode,"tw-dragover");
-	// Try to import the various data types we understand
-	this.importData(dataTransfer);
-	// Import any files in the drop
-	this.importFiles(dataTransfer.files);
-	// Tell the browser that we handled the drop
-	event.preventDefault();
-	// Stop the drop ripple up to any parent handlers
-	event.stopPropagation();
-};
-
-ImportWidget.prototype.handlePasteEvent  = function(event) {
-	// Let the browser handle it if we're in a textarea or input box
-	if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1) {
-		var self = this,
-			items = event.clipboardData.items;
-		// Enumerate the clipboard items
-		for(var t = 0; t 0) {
-			// Use our content as the template
-			templateTree = this.renderer.parseTreeNode.children;
-		} else {
-			// Use default content
-			templateTree = [{
-				type: "element",
-				tag: "$view",
-				attributes: {
-					field: {type: "string", value: "title"},
-					format: {type: "string", value: "link"}
-				}
-			}];
-		}
-	}
-	// Create the element widgets
-	if(this.renderer.hasAttribute("hackTemplate")) {
-		return {
-			type: "element",
-			tag: "$transclude",
-			isBlock: this.renderer.parseTreeNode.isBlock,
-			attributes: {
-				title: {type: "string", value: title}
-			}
-		};
-	} else {
-		if(!templateTree) {
-			templateTree = [{
-				type: "element",
-				tag: "$transclude",
-				attributes: {
-					title: {type: "string", value: template}
-				},
-				children: templateTree
-			}];
-		}
-		return {
-			type: "element",
-			tag: "$tiddler",
-			isBlock: this.renderer.parseTreeNode.isBlock,
-			attributes: {
-				title: {type: "string", value: title}
-			},
-			children: templateTree
-		};
-	}
-};
-
-/*
-Remove a list element from the list, along with the attendant DOM nodes
-*/
-ListWidget.prototype.removeListElement = function(index) {
-	// Get the list element
-	var listElement = this.children[index];
-	// Invoke the listview to animate the removal
-	if(this.listview && this.listview.remove) {
-		if(!this.listview.remove(index)) {
-			// Only delete the DOM element if the listview.remove() returned false
-			listElement.domNode.parentNode.removeChild(listElement.domNode);
-		}
-	} else {
-		// Always remove the DOM node if we didn't invoke the listview
-		listElement.domNode.parentNode.removeChild(listElement.domNode);
-	}
-	// Then delete the actual renderer node
-	this.children.splice(index,1);
-};
-
-/*
-Return the index of the list element that corresponds to a particular title
-startIndex: index to start search (use zero to search from the top)
-title: tiddler title to seach for
-*/
-ListWidget.prototype.findListElementByTitle = function(startIndex,title) {
-	var testNode = this.macro ? function(node) {
-		// We're looking for a macro list element
-		return node.widget.children[0].parseTreeNode.params[0].value === title;
-	} : (this.renderer.hasAttribute("hackTemplate") ? function(node) {
-			// We're looking for a transclusion list element
-			return node.widget.children[0].attributes.title === title;
-		} : function(node) {
-			// We're looking for a transclusion list element
-			return node.widget.children[0].attributes.title === title;
-		});
-	// Search for the list element
-	while(startIndex < this.children.length) {
-		if(testNode(this.children[startIndex])) {
-			return startIndex;
-		}
-		startIndex++;
-	}
-	return undefined;
-};
-
-ListWidget.prototype.postRenderInDom = function() {
-	this.listview = this.chooseListView();
-	this.history = [];
-};
-
-/*
-Select the appropriate list viewer
-*/
-ListWidget.prototype.chooseListView = function() {
-	// Instantiate the list view
-	var listviewName = this.renderer.getAttribute("listview");
-	var ListView = this.listViews[listviewName];
-	return ListView ? new ListView(this) : null;
-};
-
-ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
-	// Reexecute the widget if any of our attributes have changed
-	if(changedAttributes.itemClass || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.type || changedAttributes.filter || changedAttributes.template || changedAttributes.history || changedAttributes.listview) {
-		// Regenerate and rerender the widget and replace the existing DOM node
-		this.generate();
-		var oldDomNode = this.renderer.domNode,
-			newDomNode = this.renderer.renderInDom();
-		oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
-	} else {
-		// Handle any changes to the list, and refresh any nodes we're reusing
-		this.handleListChanges(changedTiddlers);
-		// Update the history list
-		var history = this.renderer.getAttribute("history");
-		if(history && changedTiddlers[history]) {
-			this.handleHistoryChanges();
-		}
-	}
-};
-
-ListWidget.prototype.handleListChanges = function(changedTiddlers) {
-	var t,
-		prevListLength = this.list.length,
-		self = this;
-	// Get the list of tiddlers, having saved the previous length
-	this.getTiddlerList();
-	// Check if the list is empty
-	if(this.list.length === 0) {
-		// Check if it was empty before
-		if(prevListLength === 0) {
-			// If so, just refresh the empty message
-			$tw.utils.each(this.children,function(node) {
-				if(node.refreshInDom) {
-					node.refreshInDom(changedTiddlers);
-				}
-			});
-			return;
-		} else {
-			// If the list wasn't empty before, empty it
-			for(t=prevListLength-1; t>=0; t--) {
-				this.removeListElement(t);
-			}
-			// Insert the empty message
-			this.children = this.renderer.renderTree.createRenderers(this.renderer,[this.getEmptyMessage()]);
-			$tw.utils.each(this.children,function(node) {
-				if(node.renderInDom) {
-					self.renderer.domNode.appendChild(node.renderInDom());
-				}
-			});
-			return;
-		}
-	} else {
-		// If it is not empty now, but was empty previously, then remove the empty message
-		if(prevListLength === 0) {
-			this.removeListElement(0);
-		}
-	}
-	// Step through the list and adjust our child list elements appropriately
-	for(t=0; t=t; n--) {
-				this.removeListElement(n);
-			}
-			// Refresh the node we're reusing
-			this.children[t].refreshInDom(changedTiddlers);
-		}
-	}
-	// Remove any left over elements
-	for(t=this.children.length-1; t>=this.list.length; t--) {
-		this.removeListElement(t);
-	}
-};
-
-/*
-Handle any changes to the history list
-*/
-ListWidget.prototype.handleHistoryChanges = function() {
-	// Get the history data
-	var historyAtt = this.renderer.getAttribute("history"),
-		newHistory = this.renderer.renderTree.wiki.getTiddlerData(historyAtt,[]);
-	// 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.listview && this.listview.navigateTo) {
-			this.listview.navigateTo(newHistory[entry]);
-		}
-		entry++;
-	}
-	// Update the history
-	this.history = newHistory;
-};
-
-exports.list = ListWidget;
-
-})();
diff --git a/core/modules/widgets/list/listviews/classic.js b/core/modules/widgets/list/listviews/classic.js
deleted file mode 100644
index 4634a54bb..000000000
--- a/core/modules/widgets/list/listviews/classic.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/*\
-title: $:/core/modules/widgets/list/listviews/classic.js
-type: application/javascript
-module-type: listview
-
-Views the list as a linear sequence
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var ClassicListView = function(listWidget) {
-	this.listWidget = listWidget;
-}
-
-ClassicListView.prototype.navigateTo = function(historyInfo) {
-	var listElementIndex = this.listWidget.findListElementByTitle(0,historyInfo.title);
-	if(listElementIndex === undefined) {
-		return;
-	}
-	var listElementNode = this.listWidget.children[listElementIndex],
-		targetElement = listElementNode.domNode;
-	// Scroll the node into view
-	var scrollEvent = this.listWidget.renderer.renderTree.document.createEvent("Event");
-	scrollEvent.initEvent("tw-scroll",true,true);
-	targetElement.dispatchEvent(scrollEvent);
-};
-
-ClassicListView.prototype.insert = function(index) {
-	var listElementNode = this.listWidget.children[index],
-		targetElement = listElementNode.domNode,
-		duration = $tw.utils.getAnimationDuration();
-	// Get the current height of the tiddler
-	var currMarginBottom = parseInt(window.getComputedStyle(targetElement).marginBottom,10),
-		currMarginTop = parseInt(window.getComputedStyle(targetElement).marginTop,10),
-		currHeight = targetElement.offsetHeight + currMarginTop;
-	// Reset the margin once the transition is over
-	setTimeout(function() {
-		$tw.utils.setStyle(targetElement,[
-			{transition: "none"},
-			{marginBottom: ""}
-		]);
-	},duration);
-	// Set up the initial position of the element
-	$tw.utils.setStyle(targetElement,[
-		{transition: "none"},
-		{marginBottom: (-currHeight) + "px"},
-		{opacity: "0.0"}
-	]);
-	$tw.utils.forceLayout(targetElement);
-	// Transition to the final position
-	$tw.utils.setStyle(targetElement,[
-		{transition: "opacity " + duration + "ms ease-in-out, " +
-					"margin-bottom " + duration + "ms ease-in-out"},
-		{marginBottom: currMarginBottom + "px"},
-		{opacity: "1.0"}
-	]);
-};
-
-ClassicListView.prototype.remove = function(index) {
-	var listElementNode = this.listWidget.children[index],
-		targetElement = listElementNode.domNode,
-		duration = $tw.utils.getAnimationDuration();
-	// Get the current height of the tiddler
-	var currWidth = targetElement.offsetWidth,
-		currMarginBottom = parseInt(window.getComputedStyle(targetElement).marginBottom,10),
-		currMarginTop = parseInt(window.getComputedStyle(targetElement).marginTop,10),
-		currHeight = targetElement.offsetHeight + currMarginTop;
-	// Remove the element at the end of the transition
-	setTimeout(function() {
-		if(targetElement.parentNode) {
-			targetElement.parentNode.removeChild(targetElement);
-		}
-	},duration);
-	// Animate the closure
-	$tw.utils.setStyle(targetElement,[
-		{transition: "none"},
-		{transform: "translateX(0px)"},
-		{marginBottom:  currMarginBottom + "px"},
-		{opacity: "1.0"}
-	]);
-	$tw.utils.forceLayout(targetElement);
-	$tw.utils.setStyle(targetElement,[
-		{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in-out, " +
-					"opacity " + duration + "ms ease-in-out, " +
-					"margin-bottom " + duration + "ms ease-in-out"},
-		{transform: "translateX(-" + currWidth + "px)"},
-		{marginBottom: (-currHeight) + "px"},
-		{opacity: "0.0"}
-	]);
-	// Returning true causes the DOM node not to be deleted
-	return true;
-};
-
-exports.classic = ClassicListView;
-
-})();
diff --git a/core/modules/widgets/list/listviews/pop.js b/core/modules/widgets/list/listviews/pop.js
deleted file mode 100644
index 4a8071175..000000000
--- a/core/modules/widgets/list/listviews/pop.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*\
-title: $:/core/modules/widgets/list/listviews/pop.js
-type: application/javascript
-module-type: listview
-
-Animates list insertions and removals
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var PopListView = function(listWidget) {
-	this.listWidget = listWidget;
-}
-
-PopListView.prototype.insert = function(index) {
-	var listElementNode = this.listWidget.children[index],
-		targetElement = listElementNode.domNode,
-		duration = $tw.utils.getAnimationDuration();
-	// Reset once the transition is over
-	setTimeout(function() {
-		$tw.utils.setStyle(targetElement,[
-			{transition: "none"},
-			{transform: "none"}
-		]);
-	},duration);
-	// Set up the initial position of the element
-	$tw.utils.setStyle(targetElement,[
-		{transition: "none"},
-		{transform: "scale(2)"},
-		{opacity: "0.0"}
-	]);
-	$tw.utils.forceLayout(targetElement);
-	// Transition to the final position
-	$tw.utils.setStyle(targetElement,[
-		{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in-out, " +
-					"opacity " + duration + "ms ease-in-out"},
-		{transform: "scale(1)"},
-		{opacity: "1.0"}
-	]);
-};
-
-PopListView.prototype.remove = function(index) {
-	var listElementNode = this.listWidget.children[index],
-		targetElement = listElementNode.domNode,
-		duration = $tw.utils.getAnimationDuration();
-	// Remove the element at the end of the transition
-	setTimeout(function() {
-		if(targetElement.parentNode) {
-			targetElement.parentNode.removeChild(targetElement);
-		}
-	},duration);
-	// Animate the closure
-	$tw.utils.setStyle(targetElement,[
-		{transition: "none"},
-		{transform: "scale(1)"},
-		{opacity: "1.0"}
-	]);
-	$tw.utils.forceLayout(targetElement);
-	$tw.utils.setStyle(targetElement,[
-		{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in-out, " +
-					"opacity " + duration + "ms ease-in-out"},
-		{transform: "scale(0.1)"},
-		{opacity: "0.0"}
-	]);
-	// Returning true causes the DOM node not to be deleted
-	return true;
-};
-
-exports.pop = PopListView;
-
-})();
diff --git a/core/modules/widgets/list/listviews/scroller.js b/core/modules/widgets/list/listviews/scroller.js
deleted file mode 100644
index 26d116e6d..000000000
--- a/core/modules/widgets/list/listviews/scroller.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*\
-title: $:/core/modules/widgets/list/listviews/scroller.js
-type: application/javascript
-module-type: listview
-
-A list view that scrolls to newly inserted elements
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var ScrollerListView = function(listWidget) {
-	this.listWidget = listWidget;
-}
-
-ScrollerListView.prototype.navigateTo = function(historyInfo) {
-	var listElementIndex = this.listWidget.findListElementByTitle(0,historyInfo.title),
-		listElementNode = this.listWidget.children[listElementIndex],
-		targetElement = listElementNode.domNode;
-	// Scroll the node into view
-	var scrollEvent = this.listWidget.renderer.renderTree.document.createEvent("Event");
-	scrollEvent.initEvent("tw-scroll",true,true);
-	targetElement.dispatchEvent(scrollEvent);
-};
-
-exports.scroller = ScrollerListView;
-
-})();
diff --git a/core/modules/widgets/list/listviews/zoomin.js b/core/modules/widgets/list/listviews/zoomin.js
deleted file mode 100644
index cfed003dc..000000000
--- a/core/modules/widgets/list/listviews/zoomin.js
+++ /dev/null
@@ -1,207 +0,0 @@
-/*\
-title: $:/core/modules/widgets/list/listviews/zoomin.js
-type: application/javascript
-module-type: listview
-
-Zooms between individual tiddlers
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-var ZoominListView = function(listWidget) {
-	this.listWidget = listWidget;
-	this.storyNode = this.listWidget.renderer.domNode;
-	// Set the current tiddler
-	this.currentTiddler = this.listWidget.children[0].domNode;
-	// Make all the tiddlers position absolute, and hide all but the first one
-	this.storyNode.style.position = "relative";
-	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
\ No newline at end of file + || exit 1
\ No newline at end of file diff --git a/ginsu.sh b/ginsu.sh index b83c7913a..fa866660d 100755 --- a/ginsu.sh +++ b/ginsu.sh @@ -9,6 +9,6 @@ node ./tiddlywiki.js \ ./editions/empty \ --verbose \ --load $1 \ - --rendertiddler $:/core/templates/split-recipe tmp/ginsu/split.recipe text/plain \ - --rendertiddlers [!is[system]] $:/core/templates/tid-tiddler tmp/ginsu text/plain .tid \ + --new_rendertiddler $:/core/templates/split-recipe tmp/ginsu/split.recipe text/plain \ + --new_rendertiddlers [!is[system]] $:/core/templates/tid-tiddler tmp/ginsu text/plain .tid \ || exit 1 diff --git a/test.sh b/test.sh index c610fd130..2fa16134c 100755 --- a/test.sh +++ b/test.sh @@ -20,6 +20,6 @@ echo "Using TW5_BUILD_OUTPUT as [$TW5_BUILD_OUTPUT]" node ./tiddlywiki.js \ ./editions/test \ --verbose \ - --rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/test.html text/plain \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/test.html text/plain \ || exit 1 diff --git a/wbld.sh b/wbld.sh index 9d685fa16..b6bc5ceaf 100755 --- a/wbld.sh +++ b/wbld.sh @@ -15,7 +15,7 @@ mkdir -p tmp node ./tiddlywiki.js \ editions/tw5tiddlyweb \ --verbose \ - --rendertiddler $:/core/templates/tiddlywiki5.template.html tmp/tiddlyweb.html text/plain \ + --new_rendertiddler $:/core/templates/tiddlywiki5.template.html tmp/tiddlyweb.html text/plain \ || exit 1 # Prepend the type information that TiddlyWeb needs to turn the .html file into a .tid file From 1d27760a378671b2b4aab3e9f282083e441c1c59 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 28 Oct 2013 15:03:52 +0000 Subject: [PATCH 110/183] Cleaner formatting for the control panel --- core/wiki/ControlPanel.tid | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/core/wiki/ControlPanel.tid b/core/wiki/ControlPanel.tid index a46166500..f75f0af29 100644 --- a/core/wiki/ControlPanel.tid +++ b/core/wiki/ControlPanel.tid @@ -2,16 +2,14 @@ title: $:/ControlPanel ''Initial setup'' -:[[Title of this TiddlyWiki|SiteTitle]]: <$edit-text title="SiteTitle" default="" tag="input"/> +| ![[Title of this TiddlyWiki|SiteTitle]]|<$edit-text title="SiteTitle" default="" tag="input"/> | +| ![[Subtitle|SiteSubtitle]]|<$edit-text title="SiteSubtitle" default="" tag="input"/> | +| ![[Username for signing edits|$:/status/UserName]]|<$edit-text title="$:/status/UserName" default="" tag="input"/> | +| ![[Animation duration|$:/config/AnimationDuration]]|<$edit-text title="$:/config/AnimationDuration" default="" tag="input"/> | -:[[Subtitle|SiteSubtitle]]: <$edit-text title="SiteSubtitle" default="" tag="input"/> +Edit [[DefaultTiddlers|$:/DefaultTiddlers]] to choose which tiddlers are displayed at startup -:[[Username for signing edits|$:/status/UserName]]: <$edit-text title="$:/status/UserName" default="" tag="input"/> - -:[[Animation duration|$:/config/AnimationDuration]]: <$edit-text title="$:/config/AnimationDuration" default="" tag="input"/>

-:Edit [[DefaultTiddlers|$:/DefaultTiddlers]] to choose which tiddlers are displayed at startup - -:{{$:/snippets/encryptionstatus}} +{{$:/snippets/encryptionstatus}} ''Add plugins and tiddlers to your ~TiddlyWiki'' From b4b0eae4e5ab17ce4b0617c845a60507a061a91b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 28 Oct 2013 15:05:19 +0000 Subject: [PATCH 111/183] Allow the foreignObject element in SVG to embed HTML via the body element This change means that any `` elements inside an SVG region of the DOM tree will switch the namespace back to XHTML. --- core/modules/new_widgets/element.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/element.js b/core/modules/new_widgets/element.js index 2a65789cd..a87ad922d 100755 --- a/core/modules/new_widgets/element.js +++ b/core/modules/new_widgets/element.js @@ -44,7 +44,8 @@ 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" + math: "http://www.w3.org/1998/Math/MathML", + body: "http://www.w3.org/1999/xhtml" }; this.namespace = tagNamespaces[this.parseTreeNode.tag]; if(this.namespace) { From c5ed5f161f3565bcce5948c3911472b687815a70 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 28 Oct 2013 15:05:30 +0000 Subject: [PATCH 112/183] Some documentation about SVG --- .../tiddlers/howtos/SVGExampleRadius.tid | 3 ++ editions/tw5.com/tiddlers/howtos/UsingSVG.tid | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 editions/tw5.com/tiddlers/howtos/SVGExampleRadius.tid create mode 100644 editions/tw5.com/tiddlers/howtos/UsingSVG.tid diff --git a/editions/tw5.com/tiddlers/howtos/SVGExampleRadius.tid b/editions/tw5.com/tiddlers/howtos/SVGExampleRadius.tid new file mode 100644 index 000000000..dbd21722e --- /dev/null +++ b/editions/tw5.com/tiddlers/howtos/SVGExampleRadius.tid @@ -0,0 +1,3 @@ +title: $:/SVGExampleRadius + +50 \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/howtos/UsingSVG.tid b/editions/tw5.com/tiddlers/howtos/UsingSVG.tid new file mode 100644 index 000000000..333ad1067 --- /dev/null +++ b/editions/tw5.com/tiddlers/howtos/UsingSVG.tid @@ -0,0 +1,49 @@ +title: UsingSVG +modified: 201310281327 +created: 201310281327 + +TiddlyWiki5 allows you to use SVG to display vector graphics in two ways: + +* Tiddlers with the type `image/svg+xml` are interpreted as SVG images, and displayed and transcluded as self-contained `` elements with the SVG embedded as a data URI in the `src` attribute. +** For examples of SVG images see [[Motovun Jack.svg]] and [[Tiddler Fishes.svg]] +* WikiText can also include inline SVG elements directly. See below for an example. + +! Embedding SVG tiddlers + +You can embed an SVG image tiddler using the ordinary transclusion syntax: + +``` +{{Motovun Jack.jpg}} +``` + +You can also use TypedBlockWikiText to embed an inline SVG tiddler. + +The implications of the image being rendered within an `` element are that it is sandboxed; it can't use CSS styles from the parent document, for example. Neither can the image use WikiText features like transclusion. + +! Embedding SVG elements + +The other way to use SVG is to embed the `` element directly. For example: + + + + + +Note that inline SVG elements don't need an `` directive. + +! Including HTML or WikiText content in SVG images + +You can include simple text strings in SVG images using the `` element: + +Hello, out there + +HTML or WikiText content can be included within inline SVG images using the `` element. For example: + +Here is some text that requires a word wrap, and includes a [[link to a tiddler|HelloThere]]. + +! Transcluding SVG elements + +When embedding SVG elements you can also use WikiText features like transclusion. For example, here is an SVG circle with the radius set to the value in the tiddler [[$:/SVGExampleRadius]]: + + + +You can edit the value of the radius here: <$edit-text title="$:/SVGExampleRadius" tag="input"/> From 3cab550ebb75f422112400ecd0a06e8a93dbedb6 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 28 Oct 2013 15:05:39 +0000 Subject: [PATCH 113/183] Link to docs --- editions/tw5.com/tiddlers/Features.tid | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editions/tw5.com/tiddlers/Features.tid b/editions/tw5.com/tiddlers/Features.tid index 2acd4d2f8..b78828130 100644 --- a/editions/tw5.com/tiddlers/Features.tid +++ b/editions/tw5.com/tiddlers/Features.tid @@ -1,12 +1,12 @@ created: 201308221728 creator: JeremyRuston -modified: 201310250846 +modified: 201310281322 modifier: JeremyRuston tags: introduction title: Features * The ability to save changes on almost any desktop HTML5 compatible-browser -* [[Bitmap images|Motovun Jack.jpg]] and [[SVG images|Motovun Jack.svg]] are first-class citizens alongside text +* [[Bitmap images|Motovun Jack.jpg]] and [[SVG images|UsingSVG]] are first-class citizens alongside text * Concise and expressive WikiText that generates semantic HTML * Integrated [[AES encryption|How to use TiddlyWiki5 as a standalone HTML file with encryption]] * TiddlyWiki isn't just a wiki - you can build custom applications with it like this TaskManagementExample From aefca26d72e3c2b4b803c7abd2ab598e669821e4 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 28 Oct 2013 15:06:17 +0000 Subject: [PATCH 114/183] Style the body tag through a CSS class This avoids accidentally styling body tags within SVG foreignObject elements. --- core/templates/static.template.html.tid | 2 +- core/templates/static.tiddler.html.tid | 2 +- core/templates/tiddlywiki5.template.html.tid | 2 +- themes/tiddlywiki/snowwhite/base.tid | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/templates/static.template.html.tid b/core/templates/static.template.html.tid index 9d9df2bc1..5209a1a22 100644 --- a/core/templates/static.template.html.tid +++ b/core/templates/static.template.html.tid @@ -18,7 +18,7 @@ type: text/vnd.tiddlywiki-html {{{ [tag[$:/tags/stylesheet]] [is[shadow]tag[$:/tags/stylesheet]] ||$:/core/templates/wikified-tiddler}}} - + {{$:/StaticBanner||$:/core/templates/html-tiddler}} {{$:/core/ui/PageTemplate||$:/core/templates/html-tiddler}} diff --git a/core/templates/static.tiddler.html.tid b/core/templates/static.tiddler.html.tid index 75ced02e4..4d1e5d9db 100644 --- a/core/templates/static.tiddler.html.tid +++ b/core/templates/static.tiddler.html.tid @@ -11,7 +11,7 @@ title: $:/core/templates/static.tiddler.html `{{$:/core/wiki/title}}` - + `{{$:/StaticBanner||$:/core/templates/html-tiddler}}`
`<$view title="$:/core/ui/ViewTemplate" format="htmlwikified"/>` diff --git a/core/templates/tiddlywiki5.template.html.tid b/core/templates/tiddlywiki5.template.html.tid index d8e6af114..e9aa3459b 100644 --- a/core/templates/tiddlywiki5.template.html.tid +++ b/core/templates/tiddlywiki5.template.html.tid @@ -19,7 +19,7 @@ title: $:/core/templates/tiddlywiki5.template.html {{{ [tag[$:/core/wiki/rawmarkup]] ||$:/core/templates/plain-text-tiddler}}} - +
{{{ [is[system]type[text/css]] ||$:/core/templates/css-tiddler}}} diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 91dd94651..ac476e9cb 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -67,7 +67,7 @@ html { font-family: {{$:/themes/tiddlywiki/snowwhite/settings##fontfamily}}; } -body { +body.tw-body { font-size: {{$:/themes/tiddlywiki/snowwhite/metrics##fontsize}}; line-height: {{$:/themes/tiddlywiki/snowwhite/metrics##lineheight}}; color: {{$:/themes/tiddlywiki/snowwhite/colourmappings##foreground}}; From 0a9ad8efaf60b7eb424c2dff361a2030d7f3f60e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 28 Oct 2013 15:22:31 +0000 Subject: [PATCH 115/183] Batch file cleanup --- bld.sh | 10 +++++++- nbld.sh | 80 --------------------------------------------------------- test.sh | 1 - 3 files changed, 9 insertions(+), 82 deletions(-) delete mode 100755 nbld.sh diff --git a/bld.sh b/bld.sh index 40436ffe4..6024da6c3 100755 --- a/bld.sh +++ b/bld.sh @@ -76,6 +76,14 @@ node ./tiddlywiki.js \ --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, 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/nbld.sh b/nbld.sh deleted file mode 100755 index fa05689ce..000000000 --- a/nbld.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash - -# Testing the new widget mechanism - -# Set up the build output directory - -if [ -z "$TW5_BUILD_OUTPUT" ]; then - TW5_BUILD_OUTPUT=../jermolene.github.com -fi - -if [ ! -d "$TW5_BUILD_OUTPUT" ]; then - echo 'A valid TW5_BUILD_OUTPUT environment variable must be set' - exit 1 -fi - -echo "Using TW5_BUILD_OUTPUT as [$TW5_BUILD_OUTPUT]" - -# Build it - -node ./tiddlywiki.js \ - ./editions/tw5.com \ - --verbose \ - --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \ - --new_rendertiddler ReadMe ./readme.md text/html \ - --new_rendertiddler ContributingTemplate ./contributing.md text/html \ - --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" - -node ./tiddlywiki.js \ - ./editions/tw5.com \ - --verbose \ - --password password \ - --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \ - || exit 1 - -# Third, empty.html: empty wiki for reuse - -node ./tiddlywiki.js \ - ./editions/empty \ - --verbose \ - --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/empty.html text/plain \ - || exit 1 - -# Fifth, d3demo.html: wiki to demo d3 plugin - -node ./tiddlywiki.js \ - ./editions/d3demo \ - --verbose \ - --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/d3demo.html text/plain \ - || exit 1 - -# Seventh, 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 - -# cook the TiddlyWiki 2.x.x index file - -node ./tiddlywiki.js \ - editions/tw2 \ - --verbose \ - --load editions/tw2/source/tiddlywiki.com/index.html.recipe \ - --new_rendertiddler $:/core/templates/tiddlywiki2.template.html ./tmp/tw2/index.html text/plain \ - || exit 1 - -# Compare what we've built to the prebuilt standard -# TODO: We should exit the batch file if there are any differences - -diff -q tmp/tw2/index.html editions/tw2/target/prebuilt.html - -# Run tests - -./test.sh diff --git a/test.sh b/test.sh index 2fa16134c..f31176b57 100755 --- a/test.sh +++ b/test.sh @@ -22,4 +22,3 @@ node ./tiddlywiki.js \ --verbose \ --new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/test.html text/plain \ || exit 1 - From bf48e6f8a11bc0c1c646625bdb9ebba2d20cb3e4 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 28 Oct 2013 23:40:45 +0000 Subject: [PATCH 116/183] Rename `tiddlerTitle` variable to `currentTiddler` --- core/modules/commands/new_rendertiddlers.js | 2 +- core/modules/macros/changecount.js | 2 +- core/modules/new_widgets/button.js | 2 +- core/modules/new_widgets/checkbox.js | 2 +- core/modules/new_widgets/count.js | 2 +- core/modules/new_widgets/edit-bitmap.js | 2 +- core/modules/new_widgets/edit-text.js | 2 +- core/modules/new_widgets/edit.js | 2 +- core/modules/new_widgets/fieldmangler.js | 2 +- core/modules/new_widgets/fields.js | 2 +- core/modules/new_widgets/link.js | 2 +- core/modules/new_widgets/linkcatcher.js | 2 +- core/modules/new_widgets/list.js | 2 +- core/modules/new_widgets/reveal.js | 2 +- core/modules/new_widgets/setvariable.js | 2 +- core/modules/new_widgets/tiddler.js | 2 +- core/modules/new_widgets/transclude.js | 4 ++-- core/modules/new_widgets/view.js | 2 +- core/modules/new_widgets/widget.js | 4 ++-- core/ui/FieldEditor.tid | 2 +- core/wiki/modules.tid | 2 +- editions/test/tiddlers/tests/test-widget.js | 2 +- editions/tw5.com/tiddlers/widgets/LinkWidget.tid | 2 +- 23 files changed, 25 insertions(+), 25 deletions(-) diff --git a/core/modules/commands/new_rendertiddlers.js b/core/modules/commands/new_rendertiddlers.js index be513b0bb..291b3840b 100644 --- a/core/modules/commands/new_rendertiddlers.js +++ b/core/modules/commands/new_rendertiddlers.js @@ -44,7 +44,7 @@ Command.prototype.execute = function() { parseTreeNode = parser ? {type: "widget", children: [{ type: "setvariable", attributes: { - name: {type: "string", value: "tiddlerTitle"}, + name: {type: "string", value: "currentTiddler"}, value: {type: "string", value: title} }, children: parser.tree diff --git a/core/modules/macros/changecount.js b/core/modules/macros/changecount.js index 08d8a799e..2d0f62e5a 100644 --- a/core/modules/macros/changecount.js +++ b/core/modules/macros/changecount.js @@ -24,7 +24,7 @@ exports.params = []; Run the macro */ exports.run = function() { - return this.wiki.getChangeCount(this.getVariable("tiddlerTitle")) + ""; + return this.wiki.getChangeCount(this.getVariable("currentTiddler")) + ""; }; })(); diff --git a/core/modules/new_widgets/button.js b/core/modules/new_widgets/button.js index 77224cb7c..47dfe1422 100644 --- a/core/modules/new_widgets/button.js +++ b/core/modules/new_widgets/button.js @@ -87,7 +87,7 @@ ButtonWidget.prototype.isPoppedUp = function() { }; ButtonWidget.prototype.dispatchMessage = function(event) { - this.dispatchEvent({type: this.message, param: this.param, tiddlerTitle: this.getVariable("tiddlerTitle")}); + this.dispatchEvent({type: this.message, param: this.param, tiddlerTitle: this.getVariable("currentTiddler")}); }; ButtonWidget.prototype.triggerPopup = function(event) { diff --git a/core/modules/new_widgets/checkbox.js b/core/modules/new_widgets/checkbox.js index d4f562daf..32c05bc62 100644 --- a/core/modules/new_widgets/checkbox.js +++ b/core/modules/new_widgets/checkbox.js @@ -79,7 +79,7 @@ Compute the internal state of the widget */ CheckboxWidget.prototype.execute = function() { // Get the parameters from the attributes - this.checkboxTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.checkboxTitle = this.getAttribute("title",this.getVariable("currentTiddler")); this.checkboxTag = this.getAttribute("tag"); this.checkboxClass = this.getAttribute("class"); // Make the child widgets diff --git a/core/modules/new_widgets/count.js b/core/modules/new_widgets/count.js index 3385a3d9e..8780b8d59 100644 --- a/core/modules/new_widgets/count.js +++ b/core/modules/new_widgets/count.js @@ -43,7 +43,7 @@ CountWidget.prototype.execute = function() { this.filter = this.getAttribute("filter"); // Execute the filter if(this.filter) { - this.currentCount = this.wiki.filterTiddlers(this.filter,this.getVariable("tiddlerTitle")).length; + this.currentCount = this.wiki.filterTiddlers(this.filter,this.getVariable("currentTiddler")).length; } else { this.currentCount = undefined; } diff --git a/core/modules/new_widgets/edit-bitmap.js b/core/modules/new_widgets/edit-bitmap.js index caa7c971b..cf42f2b68 100644 --- a/core/modules/new_widgets/edit-bitmap.js +++ b/core/modules/new_widgets/edit-bitmap.js @@ -84,7 +84,7 @@ Compute the internal state of the widget */ EditBitmapWidget.prototype.execute = function() { // Get our parameters - this.editTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.editTitle = this.getAttribute("title",this.getVariable("currentTiddler")); this.editClass = this.getAttribute("class"); }; diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index bc7af6a2b..939ae4e36 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -106,7 +106,7 @@ Compute the internal state of the widget */ EditTextWidget.prototype.execute = function() { // Get our parameters - this.editTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.editTitle = this.getAttribute("title",this.getVariable("currentTiddler")); this.editField = this.getAttribute("field","text"); this.editIndex = this.getAttribute("index"); this.editDefault = this.getAttribute("default",""); diff --git a/core/modules/new_widgets/edit.js b/core/modules/new_widgets/edit.js index e649341f0..b8855824e 100644 --- a/core/modules/new_widgets/edit.js +++ b/core/modules/new_widgets/edit.js @@ -49,7 +49,7 @@ Compute the internal state of the widget */ EditWidget.prototype.execute = function() { // Get our parameters - this.editTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.editTitle = this.getAttribute("title",this.getVariable("currentTiddler")); this.editField = this.getAttribute("field","text"); this.editIndex = this.getAttribute("index"); this.editClass = this.getAttribute("class"); diff --git a/core/modules/new_widgets/fieldmangler.js b/core/modules/new_widgets/fieldmangler.js index 040404bf5..06d696d7c 100644 --- a/core/modules/new_widgets/fieldmangler.js +++ b/core/modules/new_widgets/fieldmangler.js @@ -44,7 +44,7 @@ Compute the internal state of the widget */ FieldManglerWidget.prototype.execute = function() { // Get our parameters - this.mangleTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.mangleTitle = this.getAttribute("title",this.getVariable("currentTiddler")); // Construct the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/new_widgets/fields.js b/core/modules/new_widgets/fields.js index e4bd21a3d..362520a74 100755 --- a/core/modules/new_widgets/fields.js +++ b/core/modules/new_widgets/fields.js @@ -40,7 +40,7 @@ Compute the internal state of the widget */ FieldsWidget.prototype.execute = function() { // Get parameters from our attributes - this.tiddlerTitle = this.getAttribute("tiddler",this.getVariable("tiddlerTitle")); + this.tiddlerTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.template = this.getAttribute("template"); this.exclude = this.getAttribute("exclude"); this.stripTitlePrefix = this.getAttribute("stripTitlePrefix","no") === "yes"; diff --git a/core/modules/new_widgets/link.js b/core/modules/new_widgets/link.js index d17f7adca..f0b10b3a7 100755 --- a/core/modules/new_widgets/link.js +++ b/core/modules/new_widgets/link.js @@ -148,7 +148,7 @@ Compute the internal state of the widget */ LinkWidget.prototype.execute = function() { // Get the target tiddler title - this.to = this.getAttribute("to",this.getVariable("tiddlerTitle")); + this.to = this.getAttribute("to",this.getVariable("currentTiddler")); // Determine the link characteristics this.isMissing = !this.wiki.tiddlerExists(this.to); this.isShadow = this.wiki.isShadowTiddler(this.to); diff --git a/core/modules/new_widgets/linkcatcher.js b/core/modules/new_widgets/linkcatcher.js index f5afb1767..74ff417fe 100644 --- a/core/modules/new_widgets/linkcatcher.js +++ b/core/modules/new_widgets/linkcatcher.js @@ -67,7 +67,7 @@ Handle a tw-navigate event */ LinkCatcherWidget.prototype.handleNavigateEvent = function(event) { if(this.catchTo) { - this.wiki.setTextReference(this.catchTo,event.navigateTo,this.getVariable("tiddlerTitle")); + this.wiki.setTextReference(this.catchTo,event.navigateTo,this.getVariable("currentTiddler")); } if(this.catchMessage) { this.dispatchEvent({ diff --git a/core/modules/new_widgets/list.js b/core/modules/new_widgets/list.js index 58d9ae6fc..42d2651ea 100755 --- a/core/modules/new_widgets/list.js +++ b/core/modules/new_widgets/list.js @@ -63,7 +63,7 @@ ListWidget.prototype.execute = function() { ListWidget.prototype.getTiddlerList = function() { var defaultFilter = "[!is[system]sort[title]]"; - return this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this.getVariable("tiddlerTitle")); + return this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this.getVariable("currentTiddler")); }; ListWidget.prototype.getEmptyMessage = function() { diff --git a/core/modules/new_widgets/reveal.js b/core/modules/new_widgets/reveal.js index f4a4f00c4..ed9b082d2 100755 --- a/core/modules/new_widgets/reveal.js +++ b/core/modules/new_widgets/reveal.js @@ -103,7 +103,7 @@ Read the state tiddler RevealWidget.prototype.readState = function() { // Read the information from the state tiddler if(this.stateTitle) { - var state = this.wiki.getTextReference(this.stateTitle,this["default"],this.getVariable("tiddlerTitle")); + var state = this.wiki.getTextReference(this.stateTitle,this["default"],this.getVariable("currentTiddler")); switch(this.type) { case "popup": this.readPopupState(state); diff --git a/core/modules/new_widgets/setvariable.js b/core/modules/new_widgets/setvariable.js index 3be58e54d..921383812 100755 --- a/core/modules/new_widgets/setvariable.js +++ b/core/modules/new_widgets/setvariable.js @@ -38,7 +38,7 @@ Compute the internal state of the widget */ SetVariableWidget.prototype.execute = function() { // Get our parameters - this.setName = this.getAttribute("name","tiddlerTitle"); + this.setName = this.getAttribute("name","currentTiddler"); this.setValue = this.getAttribute("value"); // Set context variable this.setVariable(this.setName,this.setValue,this.parseTreeNode.params); diff --git a/core/modules/new_widgets/tiddler.js b/core/modules/new_widgets/tiddler.js index c61eaeca4..01a806f02 100755 --- a/core/modules/new_widgets/tiddler.js +++ b/core/modules/new_widgets/tiddler.js @@ -43,7 +43,7 @@ TiddlerWidget.prototype.execute = function() { // Get our parameters this.tiddlerTitle = this.getAttribute("title",""); // Set context variables - this.setVariable("tiddlerTitle",this.tiddlerTitle); + this.setVariable("currentTiddler",this.tiddlerTitle); this.setVariable("missingTiddlerClass",(this.wiki.tiddlerExists(this.tiddlerTitle) || this.wiki.isShadowTiddler(this.tiddlerTitle)) ? "tw-tiddler-exists" : "tw-tiddler-missing"); this.setVariable("shadowTiddlerClass",this.wiki.isShadowTiddler(this.tiddlerTitle) ? "tw-tiddler-shadow" : ""); this.setVariable("systemTiddlerClass",this.wiki.isSystemTiddler(this.tiddlerTitle) ? "tw-tiddler-system" : ""); diff --git a/core/modules/new_widgets/transclude.js b/core/modules/new_widgets/transclude.js index 572dcc539..cdd8b9cd0 100755 --- a/core/modules/new_widgets/transclude.js +++ b/core/modules/new_widgets/transclude.js @@ -38,7 +38,7 @@ Compute the internal state of the widget */ TranscludeWidget.prototype.execute = function() { // Get our parameters - this.transcludeTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.transcludeTitle = this.getAttribute("title",this.getVariable("currentTiddler")); this.transcludeField = this.getAttribute("field"); this.transcludeIndex = this.getAttribute("index"); // Check for recursion @@ -66,7 +66,7 @@ Compose a string comprising the title, field and/or index to identify this trans TranscludeWidget.prototype.makeRecursionMarker = function() { var output = []; output.push("{"); - output.push(this.getVariable("tiddlerTitle",{defaultValue: ""})); + output.push(this.getVariable("currentTiddler",{defaultValue: ""})); output.push("|"); output.push(this.transcludeTitle || ""); output.push("|"); diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index 3e6b489cb..da17492bb 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -40,7 +40,7 @@ Compute the internal state of the widget */ ViewWidget.prototype.execute = function() { // Get parameters from our attributes - this.viewTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.viewTitle = this.getAttribute("title",this.getVariable("currentTiddler")); this.viewField = this.getAttribute("field","text"); this.viewIndex = this.getAttribute("index"); this.viewFormat = this.getAttribute("format","text"); diff --git a/core/modules/new_widgets/widget.js b/core/modules/new_widgets/widget.js index bd1159ec6..53fcfb199 100755 --- a/core/modules/new_widgets/widget.js +++ b/core/modules/new_widgets/widget.js @@ -22,7 +22,7 @@ Options include: parentWidget: 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 + currentTiddler: title of the tiddler providing the context */ var Widget = function(parseTreeNode,options) { if(arguments.length > 0) { @@ -215,7 +215,7 @@ Widget.prototype.computeAttributes = function() { value; $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { if(attribute.type === "indirect") { - value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("tiddlerTitle")); + value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("currentTiddler")); } else if(attribute.type === "macro") { var text = self.getVariable(attribute.value.name,{params: attribute.value.params}); value = self.wiki.new_renderText("text/plain","text/vnd.tiddlywiki",text); diff --git a/core/ui/FieldEditor.tid b/core/ui/FieldEditor.tid index 4e448e2c8..64a0381dd 100644 --- a/core/ui/FieldEditor.tid +++ b/core/ui/FieldEditor.tid @@ -1,6 +1,6 @@ title: $:/core/ui/FieldEditor -<$fieldmangler><$setvariable name="targetTiddler" value=<>>
+<$fieldmangler><$setvariable name="targetTiddler" value=<>>
<$list filter="[is[current]fields[]] -title -tags -text -creator -created -modified -modifier -[[draft.title]] -[[draft.of]]"> diff --git a/core/wiki/modules.tid b/core/wiki/modules.tid index 1f6bc69de..135a2cfbb 100644 --- a/core/wiki/modules.tid +++ b/core/wiki/modules.tid @@ -6,7 +6,7 @@ title: $:/snippets/modules <$list filter="[moduletypes[]]"> !! <> <$macrocall $name="describeModuleType" type=<>/> - \end -
<>:<$edit-text title=<> field=<> placeholder="field value"/><$button message="tw-remove-field" param=<> class="btn-invisible">{{$:/core/images/delete-button}}
''$title$''://{{$:/docs/fields/$title$}}//
<$list filter="[fields[]sort[title]]"><$macrocall $name="renderfield" title=<>/> +
<$list filter="[fields[]sort[title]]"><$macrocall $name="renderfield" title=<>/>
diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index ac476e9cb..a9b6cda13 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -413,11 +413,12 @@ a.tw-tiddlylink-external { <> } -.tw-view-fields-table { +.tw-view-field-table { width: 100%; } .tw-view-field-name { + width: 1%; /* Makes this column be as narrow as possible */ text-align: right; font-style: italic; } From 9e3618bdcf4c1232ea2adec0237ea901b855122f Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 30 Oct 2013 13:36:44 +0000 Subject: [PATCH 130/183] Rename the 'title' attributes of various widgets to 'tiddler' The change is to avoid confusion with the HTML 'title' attribute. The name 'tiddler' better emphasises the purpose of the attribute, too. --- core/modules/new_widgets/checkbox.js | 4 +- core/modules/new_widgets/edit-bitmap.js | 2 +- core/modules/new_widgets/edit-text.js | 4 +- core/modules/new_widgets/edit.js | 4 +- core/modules/new_widgets/fieldmangler.js | 2 +- core/modules/new_widgets/list.js | 6 +-- core/modules/new_widgets/tiddler.js | 4 +- core/modules/new_widgets/transclude.js | 4 +- core/modules/new_widgets/view.js | 4 +- .../wikiparser/rules/transcludeblock.js | 4 +- .../wikiparser/rules/transcludeinline.js | 4 +- core/templates/static.tiddler.html.tid | 2 +- core/ui/EditTemplate.tid | 8 ++-- core/ui/FieldEditor.tid | 4 +- core/ui/MissingTemplate.tid | 2 +- core/ui/MoreSideBar.tid | 2 +- core/ui/SideBar.tid | 2 +- core/ui/TagTemplate.tid | 4 +- core/ui/TagsEditor.tid | 2 +- core/ui/TiddlerInfo.tid | 2 +- core/ui/ViewTemplate/title.tid | 4 +- core/wiki/ControlPanel.tid | 8 ++-- core/wiki/allfields.tid | 2 +- editions/test/tiddlers/tests/test-widget.js | 21 +++++----- editions/tw5.com/tiddlers/TiddlySpot.tid | 4 +- editions/tw5.com/tiddlers/howtos/UsingSVG.tid | 2 +- themes/tiddlywiki/snowwhite/ThemeTweaks.tid | 38 +++++++++---------- 27 files changed, 75 insertions(+), 74 deletions(-) diff --git a/core/modules/new_widgets/checkbox.js b/core/modules/new_widgets/checkbox.js index da08abb62..012b5062a 100644 --- a/core/modules/new_widgets/checkbox.js +++ b/core/modules/new_widgets/checkbox.js @@ -79,7 +79,7 @@ Compute the internal state of the widget */ CheckboxWidget.prototype.execute = function() { // Get the parameters from the attributes - this.checkboxTitle = this.getAttribute("title",this.getVariable("currentTiddler")); + this.checkboxTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.checkboxTag = this.getAttribute("tag"); this.checkboxClass = this.getAttribute("class"); // Make the child widgets @@ -91,7 +91,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ CheckboxWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.title || changedAttributes.tag || changedAttributes["class"]) { + if(changedAttributes.tiddler || changedAttributes.tag || changedAttributes["class"]) { this.refreshSelf(); return true; } else { diff --git a/core/modules/new_widgets/edit-bitmap.js b/core/modules/new_widgets/edit-bitmap.js index cf42f2b68..cbabb6001 100644 --- a/core/modules/new_widgets/edit-bitmap.js +++ b/core/modules/new_widgets/edit-bitmap.js @@ -84,7 +84,7 @@ Compute the internal state of the widget */ EditBitmapWidget.prototype.execute = function() { // Get our parameters - this.editTitle = this.getAttribute("title",this.getVariable("currentTiddler")); + this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.editClass = this.getAttribute("class"); }; diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index 939ae4e36..96da2a8b1 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -106,7 +106,7 @@ Compute the internal state of the widget */ EditTextWidget.prototype.execute = function() { // Get our parameters - this.editTitle = this.getAttribute("title",this.getVariable("currentTiddler")); + this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.editField = this.getAttribute("field","text"); this.editIndex = this.getAttribute("index"); this.editDefault = this.getAttribute("default",""); @@ -144,7 +144,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of EditTextWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); // Completely rerender if any of our attributes have changed - if(changedAttributes.title || changedAttributes.field || changedAttributes.index) { + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index) { this.refreshSelf(); return true; } else if(changedTiddlers[this.editTitle]) { diff --git a/core/modules/new_widgets/edit.js b/core/modules/new_widgets/edit.js index b8855824e..6fbee958a 100644 --- a/core/modules/new_widgets/edit.js +++ b/core/modules/new_widgets/edit.js @@ -49,7 +49,7 @@ Compute the internal state of the widget */ EditWidget.prototype.execute = function() { // Get our parameters - this.editTitle = this.getAttribute("title",this.getVariable("currentTiddler")); + this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.editField = this.getAttribute("field","text"); this.editIndex = this.getAttribute("index"); this.editClass = this.getAttribute("class"); @@ -81,7 +81,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ EditWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.title || changedAttributes.field || changedAttributes.index) { + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index) { this.refreshSelf(); return true; } else { diff --git a/core/modules/new_widgets/fieldmangler.js b/core/modules/new_widgets/fieldmangler.js index 06d696d7c..94f5f5eb9 100644 --- a/core/modules/new_widgets/fieldmangler.js +++ b/core/modules/new_widgets/fieldmangler.js @@ -44,7 +44,7 @@ Compute the internal state of the widget */ FieldManglerWidget.prototype.execute = function() { // Get our parameters - this.mangleTitle = this.getAttribute("title",this.getVariable("currentTiddler")); + this.mangleTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); // Construct the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/new_widgets/list.js b/core/modules/new_widgets/list.js index 42d2651ea..0885fef88 100755 --- a/core/modules/new_widgets/list.js +++ b/core/modules/new_widgets/list.js @@ -90,10 +90,10 @@ ListWidget.prototype.makeItemTemplate = function(title) { } // Compose the transclusion of the template if(this.hasAttribute("hackTemplate")) { - templateTree = [{type: "transclude", attributes: {title: {type: "string", value: title}}}]; + templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: title}}}]; } else { if(template) { - templateTree = [{type: "transclude", attributes: {title: {type: "string", value: template}}}]; + templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: template}}}]; } else { if(this.parseTreeNode.children && this.parseTreeNode.children.length > 0) { templateTree = this.parseTreeNode.children; @@ -105,7 +105,7 @@ ListWidget.prototype.makeItemTemplate = function(title) { } } if(!this.hasAttribute("hackCurrentTiddler")) { - templateTree = [{type: "tiddler", attributes: {title: {type: "string", value: title}}, children: templateTree}] + templateTree = [{type: "tiddler", attributes: {tiddler: {type: "string", value: title}}, children: templateTree}] } } // Return the list item diff --git a/core/modules/new_widgets/tiddler.js b/core/modules/new_widgets/tiddler.js index e307eda2a..e21a20505 100755 --- a/core/modules/new_widgets/tiddler.js +++ b/core/modules/new_widgets/tiddler.js @@ -41,7 +41,7 @@ Compute the internal state of the widget */ TiddlerWidget.prototype.execute = function() { // Get our parameters - this.tiddlerTitle = this.getAttribute("title",""); + this.tiddlerTitle = this.getAttribute("tiddler",""); // Set context variables this.setVariable("currentTiddler",this.tiddlerTitle); this.setVariable("missingTiddlerClass",(this.wiki.tiddlerExists(this.tiddlerTitle) || this.wiki.isShadowTiddler(this.tiddlerTitle)) ? "tw-tiddler-exists" : "tw-tiddler-missing"); @@ -56,7 +56,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ TiddlerWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.title) { + if(changedAttributes.tiddler) { this.refreshSelf(); return true; } else { diff --git a/core/modules/new_widgets/transclude.js b/core/modules/new_widgets/transclude.js index cdd8b9cd0..27ee5a6d0 100755 --- a/core/modules/new_widgets/transclude.js +++ b/core/modules/new_widgets/transclude.js @@ -38,7 +38,7 @@ Compute the internal state of the widget */ TranscludeWidget.prototype.execute = function() { // Get our parameters - this.transcludeTitle = this.getAttribute("title",this.getVariable("currentTiddler")); + this.transcludeTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.transcludeField = this.getAttribute("field"); this.transcludeIndex = this.getAttribute("index"); // Check for recursion @@ -82,7 +82,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ TranscludeWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.title || changedAttributes.field || changedAttributes.index || changedTiddlers[this.transcludeTitle]) { + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedTiddlers[this.transcludeTitle]) { this.refreshSelf(); return true; } else { diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index da17492bb..aeb9d8759 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -40,7 +40,7 @@ Compute the internal state of the widget */ ViewWidget.prototype.execute = function() { // Get parameters from our attributes - this.viewTitle = this.getAttribute("title",this.getVariable("currentTiddler")); + this.viewTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.viewField = this.getAttribute("field","text"); this.viewIndex = this.getAttribute("index"); this.viewFormat = this.getAttribute("format","text"); @@ -165,7 +165,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ ViewWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.title || changedAttributes.field || changedAttributes.index || changedAttributes.template || changedAttributes.format || changedTiddlers[this.viewTitle]) { + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.template || changedAttributes.format || changedTiddlers[this.viewTitle]) { this.refreshSelf(); return true; } else { diff --git a/core/modules/parsers/wikiparser/rules/transcludeblock.js b/core/modules/parsers/wikiparser/rules/transcludeblock.js index 39fe38123..bec5ed952 100644 --- a/core/modules/parsers/wikiparser/rules/transcludeblock.js +++ b/core/modules/parsers/wikiparser/rules/transcludeblock.js @@ -47,7 +47,7 @@ exports.parse = function() { type: "element", tag: "$transclude", attributes: { - title: {type: "string", value: template || targetTitle} + tiddler: {type: "string", value: template || targetTitle} }, isBlock: true }; @@ -55,7 +55,7 @@ exports.parse = function() { type: "element", tag: "$tiddler", attributes: { - title: {type: "string", value: targetTitle} + tiddler: {type: "string", value: targetTitle} }, isBlock: true, children: [transcludeNode] diff --git a/core/modules/parsers/wikiparser/rules/transcludeinline.js b/core/modules/parsers/wikiparser/rules/transcludeinline.js index 372646fab..7604ab52d 100644 --- a/core/modules/parsers/wikiparser/rules/transcludeinline.js +++ b/core/modules/parsers/wikiparser/rules/transcludeinline.js @@ -47,14 +47,14 @@ exports.parse = function() { type: "element", tag: "$transclude", attributes: { - title: {type: "string", value: template || targetTitle} + tiddler: {type: "string", value: template || targetTitle} } }; var tiddlerNode = { type: "element", tag: "$tiddler", attributes: { - title: {type: "string", value: targetTitle} + tiddler: {type: "string", value: targetTitle} }, children: [transcludeNode] }; diff --git a/core/templates/static.tiddler.html.tid b/core/templates/static.tiddler.html.tid index 4d1e5d9db..42272e3db 100644 --- a/core/templates/static.tiddler.html.tid +++ b/core/templates/static.tiddler.html.tid @@ -14,7 +14,7 @@ title: $:/core/templates/static.tiddler.html `{{$:/StaticBanner||$:/core/templates/html-tiddler}}`
-`<$view title="$:/core/ui/ViewTemplate" format="htmlwikified"/>` +`<$view tiddler="$:/core/ui/ViewTemplate" format="htmlwikified"/>`
diff --git a/core/ui/EditTemplate.tid b/core/ui/EditTemplate.tid index a2114a492..5a3cd6d7e 100644 --- a/core/ui/EditTemplate.tid +++ b/core/ui/EditTemplate.tid @@ -10,10 +10,10 @@ tw-tiddler-frame $(missingTiddlerClass)$ $(shadowTiddlerClass)$ $(systemTiddlerC <$edit-text field="draft.title" class="title tw-edit-texteditor"/> -<$transclude title="$:/core/ui/TagsEditor"/> +<$transclude tiddler="$:/core/ui/TagsEditor"/> <$reveal state="$:/ShowEditPreview" type="match" text="yes"> -<$transclude title="$:/core/ui/EditorHint"/> <$button type="set" set="$:/ShowEditPreview" setTo="no">hide preview +<$transclude tiddler="$:/core/ui/EditorHint"/> <$button type="set" set="$:/ShowEditPreview" setTo="no">hide preview
<$transclude /> @@ -24,8 +24,8 @@ tw-tiddler-frame $(missingTiddlerClass)$ $(shadowTiddlerClass)$ $(systemTiddlerC
<$reveal state="$:/ShowEditPreview" type="nomatch" text="yes"> -<$transclude title="$:/core/ui/EditorHint"/> <$button type="set" set="$:/ShowEditPreview" setTo="yes">show preview +<$transclude tiddler="$:/core/ui/EditorHint"/> <$button type="set" set="$:/ShowEditPreview" setTo="yes">show preview <$edit field="text" class="tw-edit-texteditor"/> -<$transclude title="$:/core/ui/FieldEditor"/>
+<$transclude tiddler="$:/core/ui/FieldEditor"/>
diff --git a/core/ui/FieldEditor.tid b/core/ui/FieldEditor.tid index 64a0381dd..5668c43b2 100644 --- a/core/ui/FieldEditor.tid +++ b/core/ui/FieldEditor.tid @@ -1,7 +1,7 @@ title: $:/core/ui/FieldEditor <$fieldmangler><$setvariable name="targetTiddler" value=<>>
-<$list filter="[is[current]fields[]] -title -tags -text -creator -created -modified -modifier -[[draft.title]] -[[draft.of]]"> +
<>:<$edit-text title=<> field=<> placeholder="field value"/><$button message="tw-remove-field" param=<> class="btn-invisible">{{$:/core/images/delete-button}}
<$list filter="[is[current]fields[]] -title -tags -text -creator -created -modified -modifier -[[draft.title]] -[[draft.of]]"> @@ -9,6 +9,6 @@ title: $:/core/ui/FieldEditor -
Add a new field: <$edit-text title="$:/NewFieldName" tag="input" default="" placeholder="field name" class="tw-edit-texteditor"/> <$button message="tw-add-field" param={{$:/NewFieldName}} set="$:/NewFieldName" setTo="" class="">add
+
Add a new field: <$edit-text tiddler="$:/NewFieldName" tag="input" default="" placeholder="field name" class="tw-edit-texteditor"/> <$button message="tw-add-field" param={{$:/NewFieldName}} set="$:/NewFieldName" setTo="" class="">add
diff --git a/core/ui/MissingTemplate.tid b/core/ui/MissingTemplate.tid index c7ab0ed19..70de5d55e 100644 --- a/core/ui/MissingTemplate.tid +++ b/core/ui/MissingTemplate.tid @@ -2,7 +2,7 @@ title: $:/core/ui/MissingTemplate
<$button popup="$:/state/missingpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-missing-tiddler-label"><$view field="title" format="text" /> <$reveal state="$:/state/missingpopup" type="popup" position="below" qualifyTiddlerTitles="yes">
-<$transclude title="$:/core/ui/ListItemTemplate"/> +<$transclude tiddler="$:/core/ui/ListItemTemplate"/>
<$list filter="[is[current]backlinks[]sort[title]]" template="$:/core/ui/ListItemTemplate"/>
diff --git a/core/ui/MoreSideBar.tid b/core/ui/MoreSideBar.tid index 5b35fb71e..48298f2bb 100644 --- a/core/ui/MoreSideBar.tid +++ b/core/ui/MoreSideBar.tid @@ -29,7 +29,7 @@ title: $:/core/ui/MoreSideBar <$reveal type="match" state="$:/state/moreSideBarTabSet" text="tagsTab" qualifyTiddlerTitles="yes"> <$list filter="[tags[]sort[title]]" itemClass="tw-menu-list-item"> -<$transclude title="$:/core/ui/TagTemplate"/> <$count filter="[is[current]tagging[]]"/> +<$transclude tiddler="$:/core/ui/TagTemplate"/> <$count filter="[is[current]tagging[]]"/> ---- {{$:/core/ui/UntaggedTemplate}} <$count filter="[untagged[]!is[system]] -[tags[]]"/> diff --git a/core/ui/SideBar.tid b/core/ui/SideBar.tid index 0aaa63c66..253977a6b 100644 --- a/core/ui/SideBar.tid +++ b/core/ui/SideBar.tid @@ -6,7 +6,7 @@ title: $:/core/ui/SideBar <$linkcatcher to="$:/temp/search"> - + diff --git a/core/ui/TagTemplate.tid b/core/ui/TagTemplate.tid index 17a8849ef..8ed63a69c 100644 --- a/core/ui/TagTemplate.tid +++ b/core/ui/TagTemplate.tid @@ -3,8 +3,8 @@ title: $:/core/ui/TagTemplate \define tag-styles() background-color:$(backgroundColor)$; \end -<$setvariable name="backgroundColor" value={{!!color}}><$button popup="$:/state/tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-tag-label" style=<>><$transclude title={{!!icon}}/> <$view field="title" format="text" /> -<$reveal state="$:/state/tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes">
<$transclude title="$:/core/ui/ListItemTemplate"/> +<$setvariable name="backgroundColor" value={{!!color}}><$button popup="$:/state/tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible tw-tag-label" style=<>><$transclude tiddler={{!!icon}}/> <$view field="title" format="text" /> +<$reveal state="$:/state/tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes">
<$transclude tiddler="$:/core/ui/ListItemTemplate"/>
<$list filter="[is[current]tagging[]]" template="$:/core/ui/ListItemTemplate"/>
diff --git a/core/ui/TagsEditor.tid b/core/ui/TagsEditor.tid index 97bbb3e24..90201ee91 100644 --- a/core/ui/TagsEditor.tid +++ b/core/ui/TagsEditor.tid @@ -6,7 +6,7 @@ background-color:$(backgroundColor)$; <$fieldmangler>
<$list filter="[is[current]tags[]sort[title]]" listview="pop" itemClass="tw-tag-editor-label"><$setvariable name="backgroundColor" value={{!!color}}>> class="tw-tag-label"><$view field="title" format="text" /><$button message="tw-remove-tag" param={{!!title}} class="btn-invisible tw-remove-tag-button">×
-
Add a new tag: <$edit-text title="$:/NewTagName" tag="input" default="" placeholder="tag name" focusSet="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="tw-edit-texteditor"/> <$button popup="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="btn-invisible">{{$:/core/images/down-arrow}} <$button message="tw-add-tag" param={{$:/NewTagName}} set="$:/NewTagName" setTo="" class="">add
+
Add a new tag: <$edit-text tiddler="$:/NewTagName" tag="input" default="" placeholder="tag name" focusSet="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="tw-edit-texteditor"/> <$button popup="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="btn-invisible">{{$:/core/images/down-arrow}} <$button message="tw-add-tag" param={{$:/NewTagName}} set="$:/NewTagName" setTo="" class="">add
<$reveal state="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" type="nomatch" text="" default=""> diff --git a/core/ui/TiddlerInfo.tid b/core/ui/TiddlerInfo.tid index 6cdf2b4dc..19e39d63a 100644 --- a/core/ui/TiddlerInfo.tid +++ b/core/ui/TiddlerInfo.tid @@ -18,7 +18,7 @@ title: $:/core/ui/TiddlerInfo <$list filter="[is[current]listed[]!is[system]]" itemClass="tw-menu-list-item" emptyMessage="This tiddler is not listed by any others" template="$:/core/ui/ListItemTemplate"/> <$reveal type="match" state="$:/state/tiddlerDropDownTabSet" text="fieldsTab" qualifyTiddlerTitles="yes"> -<$transclude title="$:/core/ui/TiddlerFields"/> +<$transclude tiddler="$:/core/ui/TiddlerFields"/>
diff --git a/core/ui/ViewTemplate/title.tid b/core/ui/ViewTemplate/title.tid index 7bb840817..f20534216 100644 --- a/core/ui/ViewTemplate/title.tid +++ b/core/ui/ViewTemplate/title.tid @@ -6,10 +6,10 @@ fill:$(foregroundColor)$; \end
<$button popup="$:/state/tiddlerInfo" qualifyTiddlerTitles="yes" class="btn-invisible" selectedClass="tw-selected">{{$:/core/images/info-button}}<$button message="tw-edit-tiddler" class="btn-invisible">{{$:/core/images/edit-button}}<$button message="tw-close-tiddler" class="btn-invisible">{{$:/core/images/close-button}} -<$setvariable name="foregroundColor" value={{!!color}}>>><$transclude title={{!!icon}}/> <$view field="title"/>
+<$setvariable name="foregroundColor" value={{!!color}}>>><$transclude tiddler={{!!icon}}/> <$view field="title"/>
<$reveal type="nomatch" text="" default="" state="$:/state/tiddlerInfo" qualifyTiddlerTitles="yes" class="tw-tiddler-info" animate="yes"> -<$transclude title="$:/core/ui/TiddlerInfo"/> +<$transclude tiddler="$:/core/ui/TiddlerInfo"/> \ No newline at end of file diff --git a/core/wiki/ControlPanel.tid b/core/wiki/ControlPanel.tid index f75f0af29..82971d359 100644 --- a/core/wiki/ControlPanel.tid +++ b/core/wiki/ControlPanel.tid @@ -2,10 +2,10 @@ title: $:/ControlPanel ''Initial setup'' -| ![[Title of this TiddlyWiki|SiteTitle]]|<$edit-text title="SiteTitle" default="" tag="input"/> | -| ![[Subtitle|SiteSubtitle]]|<$edit-text title="SiteSubtitle" default="" tag="input"/> | -| ![[Username for signing edits|$:/status/UserName]]|<$edit-text title="$:/status/UserName" default="" tag="input"/> | -| ![[Animation duration|$:/config/AnimationDuration]]|<$edit-text title="$:/config/AnimationDuration" default="" tag="input"/> | +| ![[Title of this TiddlyWiki|SiteTitle]]|<$edit-text tiddler="SiteTitle" default="" tag="input"/> | +| ![[Subtitle|SiteSubtitle]]|<$edit-text tiddler="SiteSubtitle" default="" tag="input"/> | +| ![[Username for signing edits|$:/status/UserName]]|<$edit-text tiddler="$:/status/UserName" default="" tag="input"/> | +| ![[Animation duration|$:/config/AnimationDuration]]|<$edit-text tiddler="$:/config/AnimationDuration" default="" tag="input"/> | Edit [[DefaultTiddlers|$:/DefaultTiddlers]] to choose which tiddlers are displayed at startup diff --git a/core/wiki/allfields.tid b/core/wiki/allfields.tid index 0b3ef765a..8eeba3420 100644 --- a/core/wiki/allfields.tid +++ b/core/wiki/allfields.tid @@ -3,5 +3,5 @@ title: $:/snippets/allfields \define renderfield(title)
\end -
<>:<$edit-text tiddler=<> field=<> placeholder="field value"/><$button message="tw-remove-field" param=<> class="btn-invisible">{{$:/core/images/delete-button}}
''$title$''://{{$:/docs/fields/$title$}}//
<$list filter="[fields[]sort[title]]"><$macrocall $name="renderfield" title=<>/> +
<$list filter="[fields[]sort[title]]"><$macrocall $name="renderfield" tiddler=<>/>
diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js index 938c9ad7b..441ba3a4e 100755 --- a/editions/test/tiddlers/tests/test-widget.js +++ b/editions/test/tiddlers/tests/test-widget.js @@ -101,11 +101,11 @@ describe("Widget module", function() { ]}, {type: "text", text: " and back in the outer DIV"}, {type: "transclude", attributes: { - "title": {type: "string", value: "TiddlerOne"} + "tiddler": {type: "string", value: "TiddlerOne"} }} ]}, {type: "transclude", attributes: { - "title": {type: "string", value: "TiddlerOne"} + "tiddler": {type: "string", value: "TiddlerOne"} }} ]}; // Construct the widget node @@ -150,12 +150,12 @@ describe("Widget module", function() { var wiki = new $tw.Wiki(); // Add a tiddler wiki.addTiddlers([ - {title: "TiddlerOne", text: "<$transclude title='TiddlerOne'/>\n"} + {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerOne'/>\n"} ]); // Test parse tree var parseTreeNode = {type: "widget", children: [ {type: "transclude", attributes: { - "title": {type: "string", value: "TiddlerOne"} + "tiddler": {type: "string", value: "TiddlerOne"} }} ]}; // Construct the widget node @@ -186,11 +186,11 @@ describe("Widget module", function() { // Add a tiddler wiki.addTiddlers([ {title: "TiddlerOne", text: "Jolly Old World"}, - {title: "TiddlerTwo", text: "<$transclude title={{TiddlerThree}}/>"}, + {title: "TiddlerTwo", text: "<$transclude tiddler={{TiddlerThree}}/>"}, {title: "TiddlerThree", text: "TiddlerOne"} ]); // Construct the widget node - var text = "My <$transclude title='TiddlerTwo'/> is Jolly" + var text = "My <$transclude tiddler='TiddlerTwo'/> is Jolly" var widgetNode = createWidgetNode(parseText(text,wiki),wiki); // Render the widget node to the DOM var wrapper = renderWidgetNode(widgetNode); @@ -205,7 +205,7 @@ describe("Widget module", function() { {title: "TiddlerOne", text: "Jolly Old World"} ]); // Construct the widget node - var text = "<$view title='TiddlerOne'/>"; + var text = "<$view tiddler='TiddlerOne'/>"; var widgetNode = createWidgetNode(parseText(text,wiki),wiki); // Render the widget node to the DOM var wrapper = renderWidgetNode(widgetNode); @@ -234,12 +234,12 @@ describe("Widget module", function() { // Add some tiddlers wiki.addTiddlers([ {title: "TiddlerOne", text: "Jolly Old World"}, - {title: "TiddlerTwo", text: "<$transclude title={{TiddlerThree}}/>"}, + {title: "TiddlerTwo", text: "<$transclude tiddler={{TiddlerThree}}/>"}, {title: "TiddlerThree", text: "TiddlerOne"}, {title: "TiddlerFour", text: "TiddlerTwo"} ]); // Construct the widget node - var text = "My <$setvariable name='currentTiddler' value={{TiddlerFour}}><$transclude title={{!!title}}/> is Jolly" + var text = "My <$setvariable name='currentTiddler' value={{TiddlerFour}}><$transclude tiddler={{!!title}}/> is Jolly" var widgetNode = createWidgetNode(parseText(text,wiki),wiki); // Render the widget node to the DOM var wrapper = renderWidgetNode(widgetNode); @@ -449,7 +449,7 @@ describe("Widget module", function() { var wiki = new $tw.Wiki(); // Add some tiddlers wiki.addTiddlers([ - {title: "$:/myTemplate", text: "<$tiddler title=<>><$view field='title'/>"}, + {title: "$:/myTemplate", text: "<$tiddler tiddler=<>><$view field='title'/>"}, {title: "TiddlerOne", text: "Jolly Old World"}, {title: "TiddlerTwo", text: "Worldly Old Jelly"}, {title: "TiddlerThree", text: "Golly Gosh"}, @@ -460,6 +460,7 @@ describe("Widget module", function() { var widgetNode = createWidgetNode(parseText(text,wiki),wiki); // Render the widget node to the DOM var wrapper = renderWidgetNode(widgetNode); +console.log(require("util").inspect(widgetNode,{depth:8,colors:true})); // Test the rendering expect(wrapper.innerHTML).toBe("

TiddlerFourTiddlerOneTiddlerThreeTiddlerTwo

"); }); diff --git a/editions/tw5.com/tiddlers/TiddlySpot.tid b/editions/tw5.com/tiddlers/TiddlySpot.tid index 2d88fb366..c0e2127ba 100644 --- a/editions/tw5.com/tiddlers/TiddlySpot.tid +++ b/editions/tw5.com/tiddlers/TiddlySpot.tid @@ -18,7 +18,7 @@ TiddlyWiki5 isn't yet built in to TiddlySpot but you can use it by following the ! TiddlySpot details -Wiki name: <$edit-text title="$:/UploadName" default="" tag="input"/> +Wiki name: <$edit-text tiddler="$:/UploadName" default="" tag="input"/> Password: <$password name="upload"/> @@ -26,6 +26,6 @@ Password: <$password name="upload"/> If you're using a server other than TiddlySpot, you can set the server URL here: -Service URL: <$edit-text title="$:/UploadURL" default="" tag="input"/> +Service URL: <$edit-text tiddler="$:/UploadURL" default="" tag="input"/> //(by default, the server URL is `http://.tiddlyspot.com/store.cgi`)// diff --git a/editions/tw5.com/tiddlers/howtos/UsingSVG.tid b/editions/tw5.com/tiddlers/howtos/UsingSVG.tid index 333ad1067..d0861f157 100644 --- a/editions/tw5.com/tiddlers/howtos/UsingSVG.tid +++ b/editions/tw5.com/tiddlers/howtos/UsingSVG.tid @@ -46,4 +46,4 @@ When embedding SVG elements you can also use WikiText features like transclusion -You can edit the value of the radius here: <$edit-text title="$:/SVGExampleRadius" tag="input"/> +You can edit the value of the radius here: <$edit-text tiddler="$:/SVGExampleRadius" tag="input"/> diff --git a/themes/tiddlywiki/snowwhite/ThemeTweaks.tid b/themes/tiddlywiki/snowwhite/ThemeTweaks.tid index 075ee30d5..b3d2a0ef2 100644 --- a/themes/tiddlywiki/snowwhite/ThemeTweaks.tid +++ b/themes/tiddlywiki/snowwhite/ThemeTweaks.tid @@ -6,32 +6,32 @@ You can tweak certain aspects of the ''Snow White'' theme. ! Colours -* Primary colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="primary" default="" tag="input" type="color"/> -* Background colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="background" default="" tag="input" type="color"/> -* Foreground colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="foreground" default="" tag="input" type="color"/> -* Page background colour: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="pagebackground" default="" tag="input" type="color"/> -* Medium contrast color: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colours" index="medium" default="" tag="input" type="color"/> +* Primary colour: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="primary" default="" tag="input" type="color"/> +* Background colour: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="background" default="" tag="input" type="color"/> +* Foreground colour: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="foreground" default="" tag="input" type="color"/> +* Page background colour: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="pagebackground" default="" tag="input" type="color"/> +* Medium contrast color: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colours" index="medium" default="" tag="input" type="color"/> ! Colour mappings -* Tiddler background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="tiddlerbackground" default="" tag="input"/> -* Tiddler foreground colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="foreground" default="" tag="input"/> -* Page background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="pagebackground" default="" tag="input"/> -* Link background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkbackground" default="" tag="input"/> -* Link foreground colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkforeground" default="" tag="input"/> -* Dropdown background colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownbackground" default="" tag="input"/> -* Dropdown border colour mapping: <$edit-text title="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownborder" default="" tag="input"/> +* Tiddler background colour mapping: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="tiddlerbackground" default="" tag="input"/> +* Tiddler foreground colour mapping: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="foreground" default="" tag="input"/> +* Page background colour mapping: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="pagebackground" default="" tag="input"/> +* Link background colour mapping: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkbackground" default="" tag="input"/> +* Link foreground colour mapping: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="linkforeground" default="" tag="input"/> +* Dropdown background colour mapping: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownbackground" default="" tag="input"/> +* Dropdown border colour mapping: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/colourmappings" index="dropdownborder" default="" tag="input"/> ! Settings -* Font family: <$edit-text title="$:/themes/tiddlywiki/snowwhite/settings" index="fontfamily" default="" tag="input"/> +* Font family: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/settings" index="fontfamily" default="" tag="input"/> ! Sizes -* Font size: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="fontsize" default="" tag="input"/> -* Line height: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="lineheight" default="" tag="input"/> -* Story left position //(the distance between the left of the screen and the left margin of the story river or tiddler area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storyleft" default="" tag="input"/> -* Story top position //(the distance between the top of the screen ad the top margin of the story river or tiddler area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storytop" default="" tag="input"/> -* Story right //(the distance between the left side of the screen and the left margin of the sidebar area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storyright" default="" tag="input"/> -* Story width //(the width of the story river or tiddler area)//: <$edit-text title="$:/themes/tiddlywiki/snowwhite/metrics" index="storywidth" default="" tag="input"/> +* Font size: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="fontsize" default="" tag="input"/> +* Line height: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="lineheight" default="" tag="input"/> +* Story left position //(the distance between the left of the screen and the left margin of the story river or tiddler area)//: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storyleft" default="" tag="input"/> +* Story top position //(the distance between the top of the screen ad the top margin of the story river or tiddler area)//: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storytop" default="" tag="input"/> +* Story right //(the distance between the left side of the screen and the left margin of the sidebar area)//: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storyright" default="" tag="input"/> +* Story width //(the width of the story river or tiddler area)//: <$edit-text tiddler="$:/themes/tiddlywiki/snowwhite/metrics" index="storywidth" default="" tag="input"/> From 8084837f487eddf856048914092c9d3baf0fcb05 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 30 Oct 2013 14:27:55 +0000 Subject: [PATCH 131/183] Fix problem with default text handling in edit-text widget --- core/modules/new_widgets/edit-text.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index 96da2a8b1..41d216045 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -95,7 +95,9 @@ EditTextWidget.prototype.getEditInfo = function() { value = ""; break; } - value = this.editDefault; + if(this.editDefault) { + value = this.editDefault; + } } } return {tiddler: tiddler, value: value}; From 693d1803bf5e6ed1947f248558b00235bed2f828 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 30 Oct 2013 14:28:20 +0000 Subject: [PATCH 132/183] Fix problem with codemirror plugin and handling of ordinary input fields --- plugins/tiddlywiki/codemirror/codemirroreditor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tiddlywiki/codemirror/codemirroreditor.js b/plugins/tiddlywiki/codemirror/codemirroreditor.js index 08ae8d71c..df6d0ea95 100644 --- a/plugins/tiddlywiki/codemirror/codemirroreditor.js +++ b/plugins/tiddlywiki/codemirror/codemirroreditor.js @@ -45,7 +45,7 @@ EditTextWidget.prototype.updateEditor = function(text) { this.codemirrorInstance.setValue(text); } } else { - this.updateEditorDomNode(); + this.updateEditorDomNode(this.getEditInfo().value); } }; From d04962f6e90d81ad420606c168ad3383ff00cfa5 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 30 Oct 2013 14:30:11 +0000 Subject: [PATCH 133/183] Update clientserver edition to include the codemirror plugin We don't want to include it in the standalone edition because the plugin is currently 243KB. --- editions/clientserver/tiddlywiki.info | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editions/clientserver/tiddlywiki.info b/editions/clientserver/tiddlywiki.info index 7a110be9a..edccd6c58 100644 --- a/editions/clientserver/tiddlywiki.info +++ b/editions/clientserver/tiddlywiki.info @@ -1,7 +1,8 @@ { "plugins": [ "tiddlywiki/tiddlyweb", - "tiddlywiki/filesystem" + "tiddlywiki/filesystem", + "tiddlywiki/codemirror" ], "themes": [ "tiddlywiki/snowwhite" From deae9e1f7ea6156954984cf47e5e36da9d0a76b4 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 30 Oct 2013 14:30:22 +0000 Subject: [PATCH 134/183] Update some widget docs --- editions/tw5.com/tiddlers/widgets/TiddlerWidget.tid | 4 ++-- editions/tw5.com/tiddlers/widgets/TranscludeWidget.tid | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/editions/tw5.com/tiddlers/widgets/TiddlerWidget.tid b/editions/tw5.com/tiddlers/widgets/TiddlerWidget.tid index 2a3c9853b..40677137c 100644 --- a/editions/tw5.com/tiddlers/widgets/TiddlerWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/TiddlerWidget.tid @@ -1,6 +1,6 @@ created: 201308241543 creator: JeremyRuston -modified: 201308241543 +modified: 201310301351 modifier: JeremyRuston tags: widget title: TiddlerWidget @@ -12,5 +12,5 @@ The TiddlerWidget sets the ContextTiddler that applies for processing its conten ! Content and Attributes |!Attribute |!Description | -|title |The title of the tiddler to become the new ContextTiddler | +|tiddler |The title of the tiddler to become the new ContextTiddler | |class |CSS classes to added to the generated elements | diff --git a/editions/tw5.com/tiddlers/widgets/TranscludeWidget.tid b/editions/tw5.com/tiddlers/widgets/TranscludeWidget.tid index f0c6368a0..0289da968 100644 --- a/editions/tw5.com/tiddlers/widgets/TranscludeWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/TranscludeWidget.tid @@ -1,6 +1,6 @@ created: 201308241425 creator: JeremyRuston -modified: 201308241530 +modified: 201310301349 modifier: JeremyRuston tags: widget title: TranscludeWidget @@ -12,7 +12,7 @@ The TranscludeWidget dynamically imports content from another tiddler. ! Attributes |!Attribute |!Description | -|title |The title of the tiddler to transclude (defaults to the current tiddler) | +|tiddler |The title of the tiddler to transclude (defaults to the current tiddler) | |field |The field name of the current tiddler (defaults to "text"; if present takes precedence over the index attribute) | |index |The index of a property in a [[DataTiddler|DataTiddlers]] | |class |CSS classes to added to the generated elements | From f9c95dda46709100b9e89d5afd7b4117eae93c61 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 30 Oct 2013 16:05:49 +0000 Subject: [PATCH 135/183] Allow browsing for multiple files at once --- core/modules/new_widgets/browse.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/modules/new_widgets/browse.js b/core/modules/new_widgets/browse.js index 7005ad3f8..7a95b3725 100644 --- a/core/modules/new_widgets/browse.js +++ b/core/modules/new_widgets/browse.js @@ -36,6 +36,7 @@ BrowseWidget.prototype.render = function(parent,nextSibling) { // 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) { From 4646ba4e30ecf515892e58ea155ec081ae281698 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 30 Oct 2013 16:28:57 +0000 Subject: [PATCH 136/183] Remove unused class attribute from checkbox widget --- core/modules/new_widgets/checkbox.js | 1 - 1 file changed, 1 deletion(-) diff --git a/core/modules/new_widgets/checkbox.js b/core/modules/new_widgets/checkbox.js index 012b5062a..4166599da 100644 --- a/core/modules/new_widgets/checkbox.js +++ b/core/modules/new_widgets/checkbox.js @@ -81,7 +81,6 @@ CheckboxWidget.prototype.execute = function() { // Get the parameters from the attributes this.checkboxTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.checkboxTag = this.getAttribute("tag"); - this.checkboxClass = this.getAttribute("class"); // Make the child widgets this.makeChildWidgets(); }; From 4d4a84c5bf6a1608f4e2d7b11c3032c4b80f16cf Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 12:36:38 +0000 Subject: [PATCH 137/183] Remove unused class attribute from edit-bitmap widget --- core/modules/new_widgets/edit-bitmap.js | 1 - 1 file changed, 1 deletion(-) diff --git a/core/modules/new_widgets/edit-bitmap.js b/core/modules/new_widgets/edit-bitmap.js index cbabb6001..c0ea3194f 100644 --- a/core/modules/new_widgets/edit-bitmap.js +++ b/core/modules/new_widgets/edit-bitmap.js @@ -85,7 +85,6 @@ Compute the internal state of the widget EditBitmapWidget.prototype.execute = function() { // Get our parameters this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); - this.editClass = this.getAttribute("class"); }; /* From 0ec2cf20a9913e03455589e50980e87e6cd4917e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 12:37:22 +0000 Subject: [PATCH 138/183] Rename "focusSet" attribute of edit-text widget to "focusPopup" --- core/modules/new_widgets/edit-text.js | 10 +++++----- core/ui/TagsEditor.tid | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/modules/new_widgets/edit-text.js b/core/modules/new_widgets/edit-text.js index 41d216045..516950b94 100644 --- a/core/modules/new_widgets/edit-text.js +++ b/core/modules/new_widgets/edit-text.js @@ -114,13 +114,13 @@ EditTextWidget.prototype.execute = function() { this.editDefault = this.getAttribute("default",""); this.editClass = this.getAttribute("class"); this.editPlaceholder = this.getAttribute("placeholder"); - this.editFocusSet = this.getAttribute("focusSet"); + this.editFocusPopup = this.getAttribute("focusPopup"); this.qualifyTiddlerTitles = this.getAttribute("qualifyTiddlerTitles");; // Qualify tiddler titles if required if(this.qualifyTiddlerTitles) { var qualifier = this.getStateQualifier(); - if(this.editFocusSet) { - this.editFocusSet = this.editFocusSet + "-" + qualifier; + if(this.editFocusPopup) { + this.editFocusPopup = this.editFocusPopup + "-" + qualifier; } } // Get the editor element tag and type @@ -217,10 +217,10 @@ EditTextWidget.prototype.handleInputEvent = function(event) { }; EditTextWidget.prototype.handleFocusEvent = function(event) { - if(this.editFocusSet) { + if(this.editFocusPopup) { $tw.popup.triggerPopup({ domNode: this.domNodes[0], - title: this.editFocusSet, + title: this.editFocusPopup, wiki: this.wiki, force: true }); diff --git a/core/ui/TagsEditor.tid b/core/ui/TagsEditor.tid index 90201ee91..637e2a127 100644 --- a/core/ui/TagsEditor.tid +++ b/core/ui/TagsEditor.tid @@ -6,7 +6,7 @@ background-color:$(backgroundColor)$; <$fieldmangler>
<$list filter="[is[current]tags[]sort[title]]" listview="pop" itemClass="tw-tag-editor-label"><$setvariable name="backgroundColor" value={{!!color}}>> class="tw-tag-label"><$view field="title" format="text" /><$button message="tw-remove-tag" param={{!!title}} class="btn-invisible tw-remove-tag-button">×
-
Add a new tag: <$edit-text tiddler="$:/NewTagName" tag="input" default="" placeholder="tag name" focusSet="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="tw-edit-texteditor"/> <$button popup="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="btn-invisible">{{$:/core/images/down-arrow}} <$button message="tw-add-tag" param={{$:/NewTagName}} set="$:/NewTagName" setTo="" class="">add
+
Add a new tag: <$edit-text tiddler="$:/NewTagName" tag="input" default="" placeholder="tag name" focusPopup="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="tw-edit-texteditor"/> <$button popup="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" class="btn-invisible">{{$:/core/images/down-arrow}} <$button message="tw-add-tag" param={{$:/NewTagName}} set="$:/NewTagName" setTo="" class="">add
<$reveal state="$:/state/tagsAutoComplete" qualifyTiddlerTitles="yes" type="nomatch" text="" default=""> From 88dd798f2b5ca13a4dc5115a13a3c25b66969af9 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 12:37:47 +0000 Subject: [PATCH 139/183] Test for transcluding tiddler title containing spaces --- editions/test/tiddlers/tests/test-wikitext-parser.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/editions/test/tiddlers/tests/test-wikitext-parser.js b/editions/test/tiddlers/tests/test-wikitext-parser.js index e458661e0..8452dfaa6 100644 --- a/editions/test/tiddlers/tests/test-wikitext-parser.js +++ b/editions/test/tiddlers/tests/test-wikitext-parser.js @@ -77,6 +77,11 @@ describe("WikiText parser tests", function() { [ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'indirect', name : 'attribute', textReference : 'TiddlerTitle!!field', start : 4, end : 38 } }, children : [ { type : 'text', text : 'some text' } ], start : 0, end : 39 } ] } ] + ); + expect(parse("
some text
")).toEqual( + + [ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'indirect', name : 'attribute', textReference : 'Tiddler Title!!field', start : 4, end : 39 } }, children : [ { type : 'text', text : 'some text' } ], start : 0, end : 40 } ] } ] + ); expect(parse("
\nsome text
")).toEqual( From 1cc27b25986ee800e4acbcc1acb1fddfba73ed73 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 21:59:01 +0000 Subject: [PATCH 140/183] Correct typo --- core/modules/new_widgets/fields.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/new_widgets/fields.js b/core/modules/new_widgets/fields.js index 362520a74..9e71ef150 100755 --- a/core/modules/new_widgets/fields.js +++ b/core/modules/new_widgets/fields.js @@ -3,7 +3,7 @@ title: $:/core/modules/new_widgets/fields.js type: application/javascript module-type: new_widget -View widget +Fields widget \*/ (function(){ From 1ca473fd2f8bd3dc9307fbfe48b213b1f6ce52f5 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 21:59:15 +0000 Subject: [PATCH 141/183] Remove unused preserveCurrentTiddler attribute from list widget --- core/modules/new_widgets/list.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/modules/new_widgets/list.js b/core/modules/new_widgets/list.js index 0885fef88..7b58af661 100755 --- a/core/modules/new_widgets/list.js +++ b/core/modules/new_widgets/list.js @@ -44,7 +44,6 @@ ListWidget.prototype.execute = function() { // Get our attributes this.template = this.getAttribute("template"); this.editTemplate = this.getAttribute("editTemplate"); - this.preserveCurrentTiddler = this.getAttribute("preserveCurrentTiddler","no") === "yes"; // Compose the list elements this.list = this.getTiddlerList(); var members = [], @@ -118,7 +117,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of ListWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); // Completely refresh if any of our attributes have changed - if(changedAttributes.filter || changedAttributes.preserveCurrentTiddler) { + if(changedAttributes.filter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage) { this.refreshSelf(); return true; } else { From 45f0f46fdf5ebc5cea029e86a4c78dd682458e4c Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 21:59:37 +0000 Subject: [PATCH 142/183] Fix macrocall widget to not pass $name as a parameter --- core/modules/new_widgets/macrocall.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/modules/new_widgets/macrocall.js b/core/modules/new_widgets/macrocall.js index a488917e0..5b13197d8 100644 --- a/core/modules/new_widgets/macrocall.js +++ b/core/modules/new_widgets/macrocall.js @@ -40,7 +40,9 @@ MacroCallWidget.prototype.execute = function() { // 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) { - params.push({name: name, value: attribute}); + if(name !== "$name") { + params.push({name: name, value: attribute}); + } }); // Get the macro value var text = this.getVariable(this.parseTreeNode.name || this.getAttribute("$name"),{params: params}); From 01a0c9481e6ebce2208bcfc2306ac60e31c9c4f6 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 21:59:53 +0000 Subject: [PATCH 143/183] Remove unused class attribute from reveal widget --- core/modules/new_widgets/reveal.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/modules/new_widgets/reveal.js b/core/modules/new_widgets/reveal.js index ed9b082d2..370d44f4d 100755 --- a/core/modules/new_widgets/reveal.js +++ b/core/modules/new_widgets/reveal.js @@ -84,7 +84,6 @@ RevealWidget.prototype.execute = function() { this.position = this.getAttribute("position"); this["default"] = this.getAttribute("default",""); this.qualifyTiddlerTitles = this.getAttribute("qualifyTiddlerTitles"); - this["class"] = this.getAttribute("class"); this.animate = this.getAttribute("animate","no"); // Compute the title of the state tiddler and read it this.stateTitle = this.state; @@ -148,7 +147,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ RevealWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes["default"] || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || changedAttributes.animate || changedTiddlers[this.stateTitle]) { + if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes["default"] || changedAttributes.qualifyTiddlerTitles || changedAttributes.animate || changedTiddlers[this.stateTitle]) { this.refreshSelf(); return true; } else { From e870e28cf3451f0b78635737cee445bc69307dfa Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 22:01:12 +0000 Subject: [PATCH 144/183] Remove unused wikified format from viewwidget --- core/modules/new_widgets/view.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/modules/new_widgets/view.js b/core/modules/new_widgets/view.js index aeb9d8759..f787c61ca 100755 --- a/core/modules/new_widgets/view.js +++ b/core/modules/new_widgets/view.js @@ -46,9 +46,6 @@ ViewWidget.prototype.execute = function() { this.viewFormat = this.getAttribute("format","text"); this.viewTemplate = this.getAttribute("template",""); switch(this.viewFormat) { - case "wikified": - this.text = this.getValueAsWikified(); - break; case "htmlwikified": this.text = this.getValueAsHtmlWikified(); break; From 100b1edd4dd489307ff8edc09f1828aeac2ff634 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 22:01:35 +0000 Subject: [PATCH 145/183] Remove unused extraneous attribute --- core/ui/MoreSideBar.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/MoreSideBar.tid b/core/ui/MoreSideBar.tid index 48298f2bb..edb90162e 100644 --- a/core/ui/MoreSideBar.tid +++ b/core/ui/MoreSideBar.tid @@ -46,7 +46,7 @@ title: $:/core/ui/MoreSideBar <$reveal type="match" state="$:/state/moreSideBarTabSet" text="typeTab" qualifyTiddlerTitles="yes"> <$list filter="[!is[system]has[type]each[type]sort[type]]">
-<$view field="type" default="untyped"/> +<$view field="type"/> <$list filter="[type{!!type}!is[system]sort[title]]">
<$link to={{!!title}}><$view field="title"/> From 33de87ec8cf52bcb328fc8c1c8fb998735f493dd Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 22:01:47 +0000 Subject: [PATCH 146/183] Fix attribute name --- core/wiki/allfields.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/wiki/allfields.tid b/core/wiki/allfields.tid index 8eeba3420..0b3ef765a 100644 --- a/core/wiki/allfields.tid +++ b/core/wiki/allfields.tid @@ -3,5 +3,5 @@ title: $:/snippets/allfields \define renderfield(title) ''$title$''://{{$:/docs/fields/$title$}}// \end -<$list filter="[fields[]sort[title]]"><$macrocall $name="renderfield" tiddler=<>/> +
<$list filter="[fields[]sort[title]]"><$macrocall $name="renderfield" title=<>/>
From fbc1f5e5f6c02b8a19217b6d166219892d96a241 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 31 Oct 2013 22:03:40 +0000 Subject: [PATCH 147/183] Lots and lots and lots of docs updates Now we've got up-to-date skeleton documentation for all the widgets --- .../mechanisms/EncryptionMechanism.tid | 6 +- .../tw5.com/tiddlers/widgets/BrowseWidget.tid | 14 +++ .../tw5.com/tiddlers/widgets/ButtonWidget.tid | 29 +++++++ .../tiddlers/widgets/CheckboxWidget.tid | 18 ++++ .../tw5.com/tiddlers/widgets/CountWidget.tid | 17 ++++ .../tiddlers/widgets/DropzoneWidget.tid | 28 ++++++ .../tiddlers/widgets/EditBitmapWidget.tid | 17 ++++ .../tiddlers/widgets/EditTextWidget.tid | 28 ++++++ .../tw5.com/tiddlers/widgets/EditWidget.tid | 20 +++++ .../tiddlers/widgets/EncryptWidget.tid | 17 ++++ .../tiddlers/widgets/FieldManglerWidget.tid | 23 +++++ .../tw5.com/tiddlers/widgets/FieldsWidget.tid | 31 +++++++ .../tiddlers/widgets/LinkCatcherWidget.tid | 24 ++++++ .../tw5.com/tiddlers/widgets/LinkWidget.tid | 2 +- .../tw5.com/tiddlers/widgets/ListWidget.tid | 50 +++++------ .../tiddlers/widgets/MacroCallWidget.tid | 26 ++++++ .../tiddlers/widgets/NavigatorWidget.tid | 86 +++++++++++++++++++ .../tiddlers/widgets/PasswordWidget.tid | 17 ++++ .../tw5.com/tiddlers/widgets/RevealWidget.tid | 26 ++++++ .../tiddlers/widgets/SetVariableWidget.tid | 18 ++++ .../tiddlers/widgets/VersionWidget.tid | 14 +++ .../tw5.com/tiddlers/widgets/ViewWidget.tid | 33 +++++++ 22 files changed, 513 insertions(+), 31 deletions(-) create mode 100644 editions/tw5.com/tiddlers/widgets/BrowseWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/ButtonWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/CheckboxWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/CountWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/DropzoneWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/EditBitmapWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/EditTextWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/EditWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/EncryptWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/FieldManglerWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/FieldsWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/LinkCatcherWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/MacroCallWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/NavigatorWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/PasswordWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/RevealWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/SetVariableWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/VersionWidget.tid create mode 100644 editions/tw5.com/tiddlers/widgets/ViewWidget.tid diff --git a/editions/tw5.com/tiddlers/mechanisms/EncryptionMechanism.tid b/editions/tw5.com/tiddlers/mechanisms/EncryptionMechanism.tid index e0c745e5c..b975a335e 100644 --- a/editions/tw5.com/tiddlers/mechanisms/EncryptionMechanism.tid +++ b/editions/tw5.com/tiddlers/mechanisms/EncryptionMechanism.tid @@ -1,6 +1,6 @@ created: 201308251542 creator: JeremyRuston -modified: 201308251543 +modified: 201310311543 modifier: JeremyRuston tags: mechanism title: EncryptionMechanism @@ -11,10 +11,10 @@ For instructions on how to use TiddlyWiki5's encryption features, see [[How to u The EncryptionMechanism is implemented with the following elements: -* A crypto "password vault" within the BootMechanism that holds the current encryption password +* A PasswordVault within the BootMechanism that holds the current encryption password * The ability of the BootMechanism to read a block of encrypted tiddlers from the TiddlyWiki file, to prompt the user for a password, and to decrypt the tiddlers * Handlers for the messages SetPasswordMessage and ClearPasswordMessage that handle the user interface for password changes -* The `<$encrypt>` widget within the main file template that encrypts a filtered list of tiddlers with the currently held password +* The EncryptWidget within the main file template that encrypts a filtered list of tiddlers with the currently held password * The [[$:/isEncrypted]] tiddler that contains "yes" or "no" according to whether there is a password in the password vault ** The availability of this tiddler allows the RevealWidget to be used to selectively display user interface elements according to whether encryption is in force * The [[$:/snippets/encryptionstatus]] snippet displays the current encryption status diff --git a/editions/tw5.com/tiddlers/widgets/BrowseWidget.tid b/editions/tw5.com/tiddlers/widgets/BrowseWidget.tid new file mode 100644 index 000000000..e01a53cc2 --- /dev/null +++ b/editions/tw5.com/tiddlers/widgets/BrowseWidget.tid @@ -0,0 +1,14 @@ +title: BrowseWidget +created: 201310241419 +creator: JeremyRuston +modified: 201310300837 +modifier: JeremyRuston +tags: widget + +! Introduction + +The browse widget displays an HTML file browser button that allows the user to choose one or more files to import. It sends a [[WidgetMessage: tw-import-tiddlers]] carrying a JSON representation of the tiddlers imported from the files up through its parents. This message usually trapped by the NavigatorWidget which adds the tiddlers to the store and updates the story to display them. + +! Content and Attributes + +The browse widget has no attributes, and ignores any contained content. diff --git a/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid b/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid new file mode 100644 index 000000000..3e9b9c471 --- /dev/null +++ b/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid @@ -0,0 +1,29 @@ +title: ButtonWidget +created: 201310241419 +creator: JeremyRuston +modified: 201310300837 +modifier: JeremyRuston +tags: widget + +! Introduction + +The button widget displays an HTML `