mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-12-24 00:50:28 +00:00
Major refactoring of rendering mechanism
We now use a fake DOM implementation on the server to let us share more rendering code between the text output vs. DOM output paths.
This commit is contained in:
parent
bf4fede34e
commit
551ebdc005
@ -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;
|
||||
|
@ -63,14 +63,17 @@ exports.parse = function() {
|
||||
return parser.tree;
|
||||
} else {
|
||||
// Otherwise, render to the rendertype and return in a <PRE> 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
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
@ -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<attr.length; a++) {
|
||||
v = this.widget.attributes[attr[a]];
|
||||
if(v !== undefined) {
|
||||
if($tw.utils.isArray(v)) {
|
||||
v = v.join(" ");
|
||||
} else if(typeof v === "object") {
|
||||
var s = [];
|
||||
for(var p in v) {
|
||||
s.push(p + ":" + v[p] + ";");
|
||||
}
|
||||
v = s.join("");
|
||||
}
|
||||
output.push(" ",attr[a],"='",$tw.utils.htmlEncode(v),"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
output.push(">\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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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");
|
||||
|
122
core/modules/utils/fakedom.js
Normal file
122
core/modules/utils/fakedom.js
Normal file
@ -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<attr.length; a++) {
|
||||
v = this.attributes[attr[a]];
|
||||
if(v !== undefined) {
|
||||
output.push(" ",attr[a],"='",$tw.utils.htmlEncode(v),"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
output.push(">\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;
|
||||
|
||||
})();
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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 = {
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user