diff --git a/core/modules/commands/savetiddlers.js b/core/modules/commands/savetiddlers.js index d4eab787c..18d432432 100644 --- a/core/modules/commands/savetiddlers.js +++ b/core/modules/commands/savetiddlers.js @@ -39,9 +39,11 @@ Command.prototype.execute = function() { 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}}); + var renderTree = new $tw.WikiRenderTree(parser,{wiki: wiki, context: {tiddlerTitle: title}, document: $tw.document}); renderTree.execute(); - var text = renderTree.render(type); + 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; diff --git a/core/modules/parsers/wikiparser/rules/typedblock.js b/core/modules/parsers/wikiparser/rules/typedblock.js index f5ecbea67..0355b1f90 100644 --- a/core/modules/parsers/wikiparser/rules/typedblock.js +++ b/core/modules/parsers/wikiparser/rules/typedblock.js @@ -63,14 +63,17 @@ 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}); + var renderTree = new $tw.WikiRenderTree(parser,{wiki: $tw.wiki, document: $tw.document}); renderTree.execute(); + var container = $tw.document.createElement("div"); + renderTree.renderInDom(container); + var text = renderType === "text/html" ? container.innerHTML : container.textContent; return [{ type: "element", tag: "pre", children: [{ type: "text", - text: renderTree.render(renderType) + text: text }] }]; } diff --git a/core/modules/rendertree/renderers/element.js b/core/modules/rendertree/renderers/element.js index ce0317a63..3e2ef93fe 100644 --- a/core/modules/rendertree/renderers/element.js +++ b/core/modules/rendertree/renderers/element.js @@ -126,53 +126,11 @@ ElementRenderer.prototype.getAttribute = function(name,defaultValue) { } }; -ElementRenderer.prototype.render = function(type) { - var isHtml = type === "text/html", - output = [],attr,a,v; - if(isHtml) { - output.push("<",this.widget.tag); - if(this.widget.attributes) { - attr = []; - for(a in this.widget.attributes) { - attr.push(a); - } - attr.sort(); - for(a=0; a\n"); - } - if($tw.config.htmlVoidElements.indexOf(this.widget.tag) === -1) { - $tw.utils.each(this.widget.children,function(node) { - if(node.render) { - output.push(node.render(type)); - } - }); - if(isHtml) { - output.push("",this.widget.tag,">"); - } - } - return output.join(""); -}; - ElementRenderer.prototype.renderInDom = function() { // Check if our widget is providing an element if(this.widget.tag) { // Create the element - this.domNode = document.createElementNS(this.namespace,this.widget.tag); + 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 @@ -184,8 +142,8 @@ ElementRenderer.prototype.renderInDom = function() { self.domNode.appendChild(node.renderInDom()); } }); - // Call postRenderInDom if the widget provides it - if(this.widget.postRenderInDom) { + // 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 diff --git a/core/modules/rendertree/renderers/entity.js b/core/modules/rendertree/renderers/entity.js index e755aa4c3..7556513f7 100644 --- a/core/modules/rendertree/renderers/entity.js +++ b/core/modules/rendertree/renderers/entity.js @@ -22,12 +22,8 @@ var EntityRenderer = function(renderTree,parentRenderer,parseTreeNode) { this.parseTreeNode = parseTreeNode; }; -EntityRenderer.prototype.render = function(type) { - return type === "text/html" ? this.parseTreeNode.entity : $tw.utils.entityDecode(this.parseTreeNode.entity); -}; - EntityRenderer.prototype.renderInDom = function() { - return document.createTextNode($tw.utils.entityDecode(this.parseTreeNode.entity)); + 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 index 4e25ab90a..7e4ae7288 100644 --- a/core/modules/rendertree/renderers/macrocall.js +++ b/core/modules/rendertree/renderers/macrocall.js @@ -69,19 +69,9 @@ MacroCallRenderer.prototype.substituteParameters = function(text,macroCallParseT return text; }; -MacroCallRenderer.prototype.render = function(type) { - var output = []; - $tw.utils.each(this.children,function(node) { - if(node.render) { - output.push(node.render(type)); - } - }); - return output.join(""); -}; - MacroCallRenderer.prototype.renderInDom = function() { // Create the element - this.domNode = document.createElement(this.parseTreeNode.isBlock ? "div" : "span"); + 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; diff --git a/core/modules/rendertree/renderers/raw.js b/core/modules/rendertree/renderers/raw.js index 00def1e5b..98c77e788 100644 --- a/core/modules/rendertree/renderers/raw.js +++ b/core/modules/rendertree/renderers/raw.js @@ -22,12 +22,8 @@ var RawRenderer = function(renderTree,parentRenderer,parseTreeNode) { this.parseTreeNode = parseTreeNode; }; -RawRenderer.prototype.render = function(type) { - return this.parseTreeNode.html; -}; - RawRenderer.prototype.renderInDom = function() { - var domNode = document.createElement("div"); + var domNode = this.renderTree.document.createElement("div"); domNode.innerHTML = this.parseTreeNode.html; return domNode; }; diff --git a/core/modules/rendertree/renderers/text.js b/core/modules/rendertree/renderers/text.js index 3ac0e8684..b915d938c 100644 --- a/core/modules/rendertree/renderers/text.js +++ b/core/modules/rendertree/renderers/text.js @@ -22,12 +22,8 @@ var TextRenderer = function(renderTree,parentRenderer,parseTreeNode) { this.parseTreeNode = parseTreeNode; }; -TextRenderer.prototype.render = function(type) { - return type === "text/html" ? $tw.utils.htmlEncode(this.parseTreeNode.text) : this.parseTreeNode.text; -}; - TextRenderer.prototype.renderInDom = function() { - return document.createTextNode(this.parseTreeNode.text); + 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 index 73841a93a..b5f7ddf1c 100644 --- a/core/modules/rendertree/wikirendertree.js +++ b/core/modules/rendertree/wikirendertree.js @@ -20,6 +20,7 @@ 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 @@ -30,6 +31,7 @@ var WikiRenderTree = function(parser,options) { this.wiki = options.wiki; this.context = options.context || {}; this.parentRenderer = options.parentRenderer; + this.document = options.document || (typeof(document) === "object" ? document : null); // Hashmap of the renderer classes if(!this.rendererClasses) { WikiRenderTree.prototype.rendererClasses = $tw.modules.applyMethods("wikirenderer"); @@ -64,19 +66,6 @@ WikiRenderTree.prototype.createRenderer = function(parentRenderer,parseTreeNode) return new RenderNodeClass(this,parentRenderer,parseTreeNode); }; -/* -Render as a string -*/ -WikiRenderTree.prototype.render = function(type) { - var output = []; - $tw.utils.each(this.rendererTree,function(node) { - if(node.render) { - output.push(node.render(type)); - } - }); - return output.join(""); -}; - /* Render to the DOM */ diff --git a/core/modules/utils/dom/styles.js b/core/modules/utils/dom/styles.js index 7d35df3c7..68dd2dd2c 100644 --- a/core/modules/utils/dom/styles.js +++ b/core/modules/utils/dom/styles.js @@ -37,7 +37,9 @@ StylesheetManager.prototype.addStylesheet = function(title) { var parser = this.wiki.parseTiddler(title), renderTree = new $tw.WikiRenderTree(parser,{wiki: this.wiki, context: {tiddlerTitle: title}}); renderTree.execute(); - var text = renderTree.render("text/plain"); + var container = $tw.document.createElement("div"); + renderTree.renderInDom(container); + var text = container.textContent; // Create a style element and put it in the document var styleNode = document.createElement("style"); styleNode.setAttribute("type","text/css"); diff --git a/core/modules/utils/fakedom.js b/core/modules/utils/fakedom.js new file mode 100644 index 000000000..d4e96d1ce --- /dev/null +++ b/core/modules/utils/fakedom.js @@ -0,0 +1,122 @@ +/*\ +title: $:/core/modules/utils/fakedom.js +type: application/javascript +module-type: global + +A barebones implementation of DOM interfaces needed by the rendering mechanism. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var TW_TextNode = function(text) { + this.textContent = text; +} + +var TW_Element = function(tag) { + this.tag = tag; + this.attributes = {}; + this.isRaw = false; + this.children = []; +} + +TW_Element.prototype.setAttribute = function(name,value) { + if(this.isRaw) { + throw "Cannot setAttribute on a raw TW_Element"; + } + this.attributes[name] = value; +}; + +TW_Element.prototype.setAttributeNS = function(namespace,name,value) { + this.setAttribute(name,value); +}; + +TW_Element.prototype.appendChild = function(node) { + this.children.push(node); + node.parentNode = this; +}; + +TW_Element.prototype.addEventListener = function(type,listener,useCapture) { + // Do nothing +}; + +Object.defineProperty(TW_Element.prototype, "outerHTML", { + get: function() { + var output = [],attr,a,v; + output.push("<",this.tag); + if(this.attributes) { + attr = []; + for(a in this.attributes) { + attr.push(a); + } + attr.sort(); + for(a=0; a \n"); + if($tw.config.htmlVoidElements.indexOf(this.tag) === -1) { + output.push(this.innerHTML); + output.push("",this.tag,">"); + } + return output.join(""); + } +}); + +Object.defineProperty(TW_Element.prototype, "innerHTML", { + get: function() { + if(this.isRaw) { + return this.rawHTML; + } else { + var b = []; + $tw.utils.each(this.children,function(node) { + if(node instanceof TW_Element) { + b.push(node.outerHTML); + } else if(node instanceof TW_TextNode) { + b.push($tw.utils.htmlEncode(node.textContent)); + } + }); + return b.join(""); + } + }, + set: function(value) { + this.isRaw = true; + this.rawHTML = value; + } +}); + +Object.defineProperty(TW_Element.prototype, "textContent", { + get: function() { + if(this.isRaw) { + throw "Cannot get textContent on a raw TW_Element"; + } else { + var b = []; + $tw.utils.each(this.children,function(node) { + b.push(node.textContent); + }); + return b.join(""); + } + } +}); + +var document = { + createElementNS: function(namespace,tag) { + return new TW_Element(tag); + }, + createElement: function(tag) { + return new TW_Element(tag); + }, + createTextNode: function(text) { + return new TW_TextNode(text); + }, +}; + +exports.document = document; + +})(); diff --git a/core/modules/widgets/edit/editors/bitmapeditor.js b/core/modules/widgets/edit/editors/bitmapeditor.js index 7f145c7ab..7818f6523 100644 --- a/core/modules/widgets/edit/editors/bitmapeditor.js +++ b/core/modules/widgets/edit/editors/bitmapeditor.js @@ -49,7 +49,7 @@ BitmapEditor.prototype.postRenderInDom = function() { var ctx = canvas.getContext("2d"); ctx.drawImage(currImage,0,0); // And also copy the current bitmap to the off-screen canvas - self.currCanvas = document.createElement("canvas"); + self.currCanvas = self.editWidget.renderer.renderTree.document.createElement("canvas"); self.currCanvas.width = currImage.width; self.currCanvas.height = currImage.height; ctx = self.currCanvas.getContext("2d"); diff --git a/core/modules/widgets/edit/editors/texteditor.js b/core/modules/widgets/edit/editors/texteditor.js index 3dbbbabdc..54ff839e5 100644 --- a/core/modules/widgets/edit/editors/texteditor.js +++ b/core/modules/widgets/edit/editors/texteditor.js @@ -127,12 +127,12 @@ TextEditor.prototype.fixHeight = function() { $tw.utils.nextTick(function() { // Resize the textarea to fit its content var textarea = self.editWidget.children[0].domNode, - scrollTop = document.body.scrollTop; + scrollTop = self.editWidget.renderer.renderTree.document.body.scrollTop; textarea.style.height = "auto"; var newHeight = Math.max(textarea.scrollHeight + textarea.offsetHeight - textarea.clientHeight,MIN_TEXT_AREA_HEIGHT); if(newHeight !== textarea.offsetHeight) { textarea.style.height = newHeight + "px"; - document.body.scrollTop = scrollTop; + self.editWidget.renderer.renderTree.document.body.scrollTop = scrollTop; } }); } @@ -143,7 +143,7 @@ TextEditor.prototype.postRenderInDom = function() { }; TextEditor.prototype.refreshInDom = function() { - if(document.activeElement !== this.editWidget.children[0].domNode) { + if(this.editWidget.renderer.renderTree.document.activeElement !== this.editWidget.children[0].domNode) { var editInfo = this.getEditInfo(); this.editWidget.children[0].domNode.value = editInfo.value; } diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index cd8c3cb56..5537af5c0 100644 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -123,10 +123,10 @@ LinkWidget.prototype.handleDragStartEvent = function(event) { // Set the dragging class on the element being dragged $tw.utils.addClass(event.target,"tw-tiddlylink-dragging"); // Create the drag image element - this.dragImage = document.createElement("div"); + this.dragImage = this.listWidget.renderer.renderTree.document.createElement("div"); this.dragImage.className = "tw-tiddler-dragger"; - this.dragImage.appendChild(document.createTextNode(this.to)); - document.body.appendChild(this.dragImage); + this.dragImage.appendChild(this.listWidget.renderer.renderTree.document.createTextNode(this.to)); + this.listWidget.renderer.renderTree.document.body.appendChild(this.dragImage); // Set the data transfer properties var dataTransfer = event.dataTransfer; dataTransfer.effectAllowed = "copy"; diff --git a/core/modules/widgets/list/listviews/classic.js b/core/modules/widgets/list/listviews/classic.js index ba73aca0a..5427786c5 100644 --- a/core/modules/widgets/list/listviews/classic.js +++ b/core/modules/widgets/list/listviews/classic.js @@ -24,7 +24,7 @@ ClassicListView.prototype.navigateTo = function(historyInfo) { var listElementNode = this.listWidget.children[listElementIndex], targetElement = listElementNode.domNode; // Scroll the node into view - var scrollEvent = document.createEvent("Event"); + var scrollEvent = this.listWidget.renderer.renderTree.document.createEvent("Event"); scrollEvent.initEvent("tw-scroll",true,true); targetElement.dispatchEvent(scrollEvent); }; diff --git a/core/modules/widgets/list/listviews/scroller.js b/core/modules/widgets/list/listviews/scroller.js index 30a4ef68d..26d116e6d 100644 --- a/core/modules/widgets/list/listviews/scroller.js +++ b/core/modules/widgets/list/listviews/scroller.js @@ -21,7 +21,7 @@ ScrollerListView.prototype.navigateTo = function(historyInfo) { listElementNode = this.listWidget.children[listElementIndex], targetElement = listElementNode.domNode; // Scroll the node into view - var scrollEvent = document.createEvent("Event"); + var scrollEvent = this.listWidget.renderer.renderTree.document.createEvent("Event"); scrollEvent.initEvent("tw-scroll",true,true); targetElement.dispatchEvent(scrollEvent); }; diff --git a/core/modules/widgets/view/viewers/htmlwikified.js b/core/modules/widgets/view/viewers/htmlwikified.js index c7900ce42..2572a1422 100644 --- a/core/modules/widgets/view/viewers/htmlwikified.js +++ b/core/modules/widgets/view/viewers/htmlwikified.js @@ -23,9 +23,11 @@ HtmlWikifiedViewer.prototype.render = function() { // Parse the field text var wiki = this.viewWidget.renderer.renderTree.wiki, parser = wiki.parseText("text/vnd.tiddlywiki",this.value), - renderTree = new $tw.WikiRenderTree(parser,{wiki: wiki, parentRenderer: this.viewWidget.renderer}); + renderTree = new $tw.WikiRenderTree(parser,{wiki: wiki, parentRenderer: this.viewWidget.renderer, document: this.viewWidget.renderer.renderTree.document}); renderTree.execute(); - var text = renderTree.render("text/html"); + var container = this.viewWidget.renderer.renderTree.document.createElement("div"); + renderTree.renderInDom(container) + var text = container.innerHTML; // Set the element details this.viewWidget.tag = "pre"; this.viewWidget.attributes = { diff --git a/core/modules/widgets/view/viewers/relativedate.js b/core/modules/widgets/view/viewers/relativedate.js index 8f2c5fbf4..4cc939d73 100644 --- a/core/modules/widgets/view/viewers/relativedate.js +++ b/core/modules/widgets/view/viewers/relativedate.js @@ -51,7 +51,7 @@ RelativeDateViewer.prototype.setTimer = function() { if(this.relativeDate.updatePeriod < 24 * 60 * 60 * 1000) { window.setTimeout(function() { // Only call the update function if the dom node is still in the document - if($tw.utils.domContains(document,self.viewWidget.renderer.domNode)) { + if($tw.utils.domContains(self.listWidget.renderer.renderTree.document,self.viewWidget.renderer.domNode)) { self.update.call(self); } },this.relativeDate.updatePeriod); @@ -67,7 +67,7 @@ RelativeDateViewer.prototype.update = function() { while(this.viewWidget.renderer.domNode.hasChildNodes()) { this.viewWidget.renderer.domNode.removeChild(this.viewWidget.renderer.domNode.firstChild); } - this.viewWidget.renderer.domNode.appendChild(document.createTextNode(this.relativeDate.description)); + this.viewWidget.renderer.domNode.appendChild(this.viewWidget.renderer.renderTree.document.createTextNode(this.relativeDate.description)); this.setTimer(); } }; diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 66aa40685..4149eb290 100644 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -553,11 +553,13 @@ Parse text in a specified format and render it into another format textType: content type of the input text text: input text */ -exports.renderText = function(outputType,textType,text) { +exports.renderText = function(outputType,textType,text,context) { var parser = this.parseText(textType,text), - renderTree = new $tw.WikiRenderTree(parser,{wiki: this}); + renderTree = new $tw.WikiRenderTree(parser,{wiki: this, context: context, document: $tw.document}); renderTree.execute(); - return renderTree.render(outputType); + var container = $tw.document.createElement("div"); + renderTree.renderInDom(container) + return outputType === "text/html" ? container.innerHTML : container.textContent; }; /* @@ -567,9 +569,11 @@ Parse text from a tiddler and render it into another format */ exports.renderTiddler = function(outputType,title,context) { var parser = this.parseTiddler(title), - renderTree = new $tw.WikiRenderTree(parser,{wiki: this, context: context}); + renderTree = new $tw.WikiRenderTree(parser,{wiki: this, context: context, document: $tw.document}); renderTree.execute(); - return renderTree.render(outputType); + var container = $tw.document.createElement("div"); + renderTree.renderInDom(container) + return outputType === "text/html" ? container.innerHTML : container.textContent; }; /*