Initial Commit

Carried over from the abortive pull request #169
This commit is contained in:
Jeremy Ruston 2013-10-12 17:05:13 +01:00
parent 6ea264f3bc
commit ed35d91be6
25 changed files with 2403 additions and 15 deletions

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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<fields.length; f++) {
fieldName = fields[f];
if(exclude.indexOf(fieldName) === -1) {
var row = this.template,
value = tiddler.getFieldString(fieldName);
if(this.stripTitlePrefix && fieldName === "title") {
var reStrip = /^\{[^\}]+\}(.+)/mg,
reMatch = reStrip.exec(value);
if(reMatch) {
value = reMatch[1];
}
}
row = row.replace("$name$",fieldName);
row = row.replace("$value$",value);
row = row.replace("$encoded_value$",$tw.utils.htmlEncode(value));
text.push(row)
}
}
}
this.text = text.join("");
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
FieldsWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.template || changedAttributes.exclude || changedAttributes.stripTitlePrefix || changedTiddlers[this.tiddlerTitle]) {
this.refreshSelf();
return true;
} else {
return false;
}
};
/*
Remove any DOM nodes created by this widget
*/
FieldsWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.fields = FieldsWidget;
})();

117
core/modules/new_widgets/link.js Executable file
View File

@ -0,0 +1,117 @@
/*\
title: $:/core/modules/new_widgets/link.js
type: application/javascript
module-type: new_widget
Link widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var LinkWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
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
this.computeAttributes();
// Execute our logic
this.execute();
// Create our element
var domNode = this.document.createElement("a");
// Assign classes
$tw.utils.addClass(domNode,"tw-tiddlylink");
if(this.isShadow) {
$tw.utils.addClass(domNode,"tw-tiddlylink-shadow");
}
if(this.isMissing && !this.isShadow) {
$tw.utils.addClass(domNode,"tw-tiddlylink-missing");
} else {
if(!this.isMissing) {
$tw.utils.addClass(domNode,"tw-tiddlylink-resolves");
}
}
// Set an href
domNode.setAttribute("href",this.to);
// 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);
// Insert the link into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
/*
Compute the internal state of the widget
*/
LinkWidget.prototype.execute = function() {
// Get the target tiddler title
this.to = this.getAttribute("to",this.getVariable("tiddlerTitle"));
// Determine the link characteristics
this.isMissing = !this.wiki.tiddlerExists(this.to);
this.isShadow = this.wiki.isShadowTiddler(this.to);
// 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
*/
LinkWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.to || changedTiddlers[this.to]) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
/*
Remove any DOM nodes created by this widget or its children
*/
LinkWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.link = LinkWidget;
})();

258
core/modules/new_widgets/list.js Executable file
View File

@ -0,0 +1,258 @@
/*\
title: $:/core/modules/new_widgets/list.js
type: application/javascript
module-type: new_widget
List and list item widgets
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
/*
The list widget creates list element sub-widgets that reach back into the list widget for their configuration
*/
var ListWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
ListWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
ListWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
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 = [],
self = this;
// Check for an empty list
if(this.list.length === 0) {
members = this.getEmptyMessage();
} else {
$tw.utils.each(this.list,function(title,index) {
members.push(self.makeItemTemplate(title));
});
}
// Construct the child widgets
this.makeChildWidgets(members);
};
ListWidget.prototype.getTiddlerList = function() {
var defaultFilter = "[!is[system]sort[title]]";
return this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this.getVariable("tiddlerTitle"));
};
ListWidget.prototype.getEmptyMessage = function() {
var emptyMessage = this.getAttribute("emptyMessage",""),
parser = this.wiki.new_parseText("text/vnd.tiddlywiki",emptyMessage,{parseAsInline: true});
if(parser) {
return parser.tree;
} else {
return [];
}
};
/*
Compose the template for a list item
*/
ListWidget.prototype.makeItemTemplate = function(title) {
// Check if the tiddler is a draft
var tiddler = this.wiki.getTiddler(title),
isDraft = tiddler && tiddler.hasField("draft.of"),
template = this.template,
templateTree;
if(isDraft && this.editTemplate) {
template = this.editTemplate;
}
// Compose the transclusion of the template
if(this.hasAttribute("hackTemplate")) {
templateTree = [{type: "transclude", attributes: {title: {type: "string", value: title}}}];
} else {
if(template) {
templateTree = [{type: "transclude", attributes: {title: {type: "string", value: template}}}];
} else {
if(this.parseTreeNode.children && this.parseTreeNode.children.length > 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<this.list.length; t++) {
var index = this.findListItem(t,this.list[t]);
if(index === undefined) {
// The list item must be inserted
this.insertListItem(t,this.list[t]);
hasRefreshed = true;
} else {
// There are intervening list items that must be removed
for(var n=index-1; n>=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;
})();

View File

@ -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<this.storyList.length; t++) {
if(this.storyList[t] === title) {
return t;
}
}
return defaultIndex;
};
/*
Handle a tw-navigate event
*/
NavigatorWidget.prototype.handleNavigateEvent = function(event) {
if(this.storyTitle) {
// Update the story tiddler if specified
this.getStoryList();
// See if the tiddler is already there
var slot = this.findTitleInStory(event.navigateTo,-1);
// If not we need to add it
if(slot === -1) {
// First we try to find the position of the story element we navigated from
slot = this.findTitleInStory(event.navigateFromTitle,-1) + 1;
// Add the tiddler
this.storyList.splice(slot,0,event.navigateTo);
// Save the story
this.saveStoryList();
}
}
// Add a new record to the top of the history stack
if(this.historyTitle) {
var historyList = this.wiki.getTiddlerData(this.historyTitle,[]);
historyList.push({title: event.navigateTo, fromPageRect: event.navigateFromClientRect});
this.wiki.setTiddlerData(this.historyTitle,historyList);
}
return false;
};
exports.navigator = NavigatorWidget;
})();

View File

@ -0,0 +1,134 @@
/*\
title: $:/core/modules/new_widgets/reveal.js
type: application/javascript
module-type: new_widget
Reveal widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var RevealWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
RevealWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
RevealWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
var domNode = this.document.createElement("div");
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
/*
Compute the internal state of the widget
*/
RevealWidget.prototype.execute = function() {
// Get our parameters
this.state = this.getAttribute("state");
this.type = this.getAttribute("type");
this.text = this.getAttribute("text");
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;
if(this.qualifyTiddlerTitles) {
this.stateTitle = this.stateTitle + "-" + this.getStateQualifier();
}
this.readState();
// Construct the child widgets
var childNodes = this.isOpen ? this.parseTreeNode.children : [];
this.makeChildWidgets(childNodes);
};
/*
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"));
switch(this.type) {
case "popup":
this.readPopupState(state);
break;
case "match":
this.readMatchState(state);
break;
case "nomatch":
this.readMatchState(state);
this.isOpen = !this.isOpen;
break;
}
}
};
RevealWidget.prototype.readMatchState = function(state) {
this.isOpen = state === this.text;
};
RevealWidget.prototype.readPopupState = function(state) {
var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/,
match = popupLocationRegExp.exec(state);
// Check if the state matches the location regexp
if(match) {
// If so, we're open
this.isOpen = true;
// Get the location
this.popup = {
left: parseFloat(match[1]),
top: parseFloat(match[2]),
width: parseFloat(match[3]),
height: parseFloat(match[4])
};
} else {
// If not, we're closed
this.isOpen = false;
}
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
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]) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
/*
Remove any DOM nodes created by this widget or its children
*/
RevealWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.reveal = RevealWidget;
})();

View File

@ -0,0 +1,64 @@
/*\
title: $:/core/modules/new_widgets/setvariable.js
type: application/javascript
module-type: new_widget
Setvariable widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var SetVariableWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
SetVariableWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
SetVariableWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
SetVariableWidget.prototype.execute = function() {
// Get our parameters
this.setName = this.getAttribute("name","tiddlerTitle");
this.setValue = this.getAttribute("value");
// Set context variable
this.setVariable(this.setName,this.setValue);
// 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
*/
SetVariableWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.name || changedAttributes.value) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
exports.setvariable = SetVariableWidget;
})();

View File

@ -0,0 +1,22 @@
/*\
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.button = Widget;
exports.linkcatcher = Widget;
exports.setstyle = Widget;
exports["import"] = Widget;
})();

View File

@ -0,0 +1,63 @@
/*\
title: $:/core/modules/new_widgets/text.js
type: application/javascript
module-type: new_widget
Text node widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var TextNodeWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
TextNodeWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
TextNodeWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.execute();
var textNode = this.document.createTextNode(this.parseTreeNode.text);
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
};
/*
Compute the internal state of the widget
*/
TextNodeWidget.prototype.execute = function() {
// Nothing to do for a text node
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
TextNodeWidget.prototype.refresh = function(changedTiddlers) {
return false;
};
/*
Remove any DOM nodes created by this widget
*/
TextNodeWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.text = TextNodeWidget;
})();

View File

@ -0,0 +1,75 @@
/*\
title: $:/core/modules/new_widgets/tiddler.js
type: application/javascript
module-type: new_widget
Tiddler widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var TiddlerWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
this.addEventListeners([
{type: "tw-navigate", handler: "handleNavigateEvent"}
]);
};
/*
Inherit from the base widget class
*/
TiddlerWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
TiddlerWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
TiddlerWidget.prototype.execute = function() {
// Get our parameters
this.tiddlerTitle = this.getAttribute("title","");
// Set context variable
this.setVariable("tiddlerTitle",this.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
*/
TiddlerWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.name || changedAttributes.value) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
/*
Handle a tw-navigate event
*/
TiddlerWidget.prototype.handleNavigateEvent = function(event) {
console.log("Setting navigateFromTitle to",this.tiddlerTitle)
event.navigateFromTitle = this.tiddlerTitle;
return true;
};
exports.tiddler = TiddlerWidget;
})();

View File

@ -0,0 +1,93 @@
/*\
title: $:/core/modules/new_widgets/transclude.js
type: application/javascript
module-type: new_widget
Transclude widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var TranscludeWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
TranscludeWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
TranscludeWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
TranscludeWidget.prototype.execute = function() {
// Get our parameters
this.transcludeTitle = this.getAttribute("title",this.getVariable("tiddlerTitle"));
this.transcludeField = this.getAttribute("field");
this.transcludeIndex = this.getAttribute("index");
// Check for recursion
var recursionMarker = this.makeRecursionMarker();;
if(this.parentWidget && this.parentWidget.hasVariable("transclusion",recursionMarker)) {
this.makeChildWidgets([{type: "text", text: "Tiddler recursion error in transclude widget"}]);
return;
}
// Set context variables for recursion detection
this.setVariable("transclusion",recursionMarker);
// Parse the text reference
var parser = this.wiki.new_parseTextReference(
this.transcludeTitle,
this.transcludeField,
this.transcludeIndex,
{parseAsInline: !this.parseTreeNode.isBlock}),
parseTreeNodes = parser ? parser.tree : [];
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
};
/*
Compose a string comprising the title, field and/or index to identify this transclusion for recursion detection
*/
TranscludeWidget.prototype.makeRecursionMarker = function() {
var output = [];
output.push("{");
output.push(this.transcludeTitle || "");
output.push("|");
output.push(this.transcludeField || "");
output.push("|");
output.push(this.transcludeIndex || "");
output.push("}");
return output.join("");
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
TranscludeWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.title || changedAttributes.field || changedAttributes.index || changedTiddlers[this.transcludeTitle]) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
exports.transclude = TranscludeWidget;
})();

View File

@ -0,0 +1,62 @@
/*\
title: $:/core/modules/new_widgets/version.js
type: application/javascript
module-type: new_widget
Version widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var VersionWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
VersionWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
VersionWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.execute();
var textNode = this.document.createTextNode($tw.version);
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
};
/*
Compute the internal state of the widget
*/
VersionWidget.prototype.execute = function() {
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
VersionWidget.prototype.refresh = function(changedTiddlers) {
return false;
};
/*
Remove any DOM nodes created by this widget
*/
VersionWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.version = VersionWidget;
})();

143
core/modules/new_widgets/view.js Executable file
View File

@ -0,0 +1,143 @@
/*\
title: $:/core/modules/new_widgets/view.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 ViewWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
ViewWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
ViewWidget.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
*/
ViewWidget.prototype.execute = function() {
// Get parameters from our attributes
this.viewTitle = this.getAttribute("title",this.getVariable("tiddlerTitle"));
this.viewField = this.getAttribute("field","text");
this.viewIndex = this.getAttribute("index");
this.viewFormat = this.getAttribute("format","text");
switch(this.viewFormat) {
case "wikified":
this.text = this.getValueAsWikified();
break;
case "htmlwikified":
this.text = this.getValueAsHtmlWikified();
break;
case "htmlencoded":
this.text = this.getValueAsHtmlEncoded();
break;
case "date":
this.text = this.getValueAsDate(this.viewFormat);
break;
case "relativedate":
this.text = this.getValueAsRelativeDate();
break;
default: // "text"
this.text = this.getValueAsText();
break;
}
};
/*
The various formatter functions are baked into this widget for the moment. Eventually they will be replaced by macro functions
*/
ViewWidget.prototype.getValueAsText = function() {
// Get the value to display
var text,
tiddler = this.wiki.getTiddler(this.viewTitle);
if(tiddler) {
if(this.viewField === "text") {
// Calling getTiddlerText() triggers lazy loading of skinny tiddlers
text = this.wiki.getTiddlerText(this.viewTitle);
} else {
text = tiddler.fields[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;
}
}
return text;
};
ViewWidget.prototype.getValueAsHtmlWikified = function() {
return this.wiki.new_renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText());
};
ViewWidget.prototype.getValueAsHtmlEncoded = function() {
return $tw.utils.htmlEncode(this.getValueAsText());
};
ViewWidget.prototype.getValueAsDate = function(format) {
return $tw.utils.formatDateString(this.getValueAsText(),format);
};
ViewWidget.prototype.getValueAsRelativeDate = function(format) {
var d = new Date(this.getValueAsText());
return $tw.utils.getRelativeDate((new Date()) - d).description;
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ViewWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.title || changedAttributes.field || changedAttributes.index || changedTiddlers[this.viewTitle]) {
this.refreshSelf();
return true;
} else {
return false;
}
};
/*
Remove any DOM nodes created by this widget
*/
ViewWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.view = ViewWidget;
})();

View File

@ -0,0 +1,378 @@
/*\
title: $:/core/modules/new_widgets/widget.js
type: application/javascript
module-type: new_widget
Widget base class
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Create a widget object for a parse tree node
parseTreeNode: reference to the parse tree node to be rendered
options: see below
Options include:
wiki: mandatory reference to wiki associated with this render tree
variables: optional hashmap of context variables (see below)
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
templateTitle: title of the tiddler providing the current template
macroDefinitions: hashmap of macro definitions
*/
var Widget = function(parseTreeNode,options) {
if(arguments.length > 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<defParams.length; p++) {
// Check if we've got a macro call parameter with the same name
paramInfo = defParams[p];
paramValue = undefined;
for(var m=0; m<params.length; m++) {
if(params[m].name === paramInfo.name) {
paramValue = params[m].value;
}
}
// If not, use the next available anonymous macro call parameter
while(nextAnonParameter < params.length && params[nextAnonParameter].name) {
nextAnonParameter++;
}
if(paramValue === undefined && nextAnonParameter < params.length) {
paramValue = params[nextAnonParameter++].value;
}
// If we've still not got a value, use the default, if any
paramValue = paramValue || paramInfo["default"] || "";
// Replace any instances of this parameter
value = value.replace(new RegExp("\\$" + $tw.utils.escapeRegExp(paramInfo.name) + "\\$","mg"),paramValue);
}
}
return value;
};
/*
Set the value of a context variable
name: name of the variable
value: value of the variable
params: array of {name:, default:} for each parameter
*/
Widget.prototype.setVariable = function(name,value,params) {
this.variables[name] = {value: value, params: params};
};
/*
Check whether a given context variable value exists in the parent chain
*/
Widget.prototype.hasVariable = function(name,value) {
var node = this;
while(node) {
if($tw.utils.hop(node.variables,name) && node.variables[name].value === value) {
return true;
}
node = node.parentWidget;
}
return false;
};
/*
Construct a qualifying string based on concatenating the values of a given variable in the parent chain
*/
Widget.prototype.getStateQualifier = function(name) {
name = name || "transclusion";
var output = [],
node = this;
while(node) {
if($tw.utils.hop(node.variables,name)) {
output.push(node.getVariable(name));
}
node = node.parentWidget;
}
return output.join("");
};
/*
Compute the current values of the attributes of the widget. Returns a hashmap of the names of the attributes that have changed
*/
Widget.prototype.computeAttributes = function() {
var changedAttributes = {},
self = this,
value;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
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);
} 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;
};
/*
Check for the presence of an attribute
*/
Widget.prototype.hasAttribute = function(name) {
return $tw.utils.hop(this.attributes,name);
};
/*
Get the value of an attribute
*/
Widget.prototype.getAttribute = function(name,defaultText) {
if($tw.utils.hop(this.attributes,name)) {
return this.attributes[name];
} else {
return defaultText;
}
};
/*
Assign the computed attributes of the widget to a domNode
*/
Widget.prototype.assignAttributes = function(domNode) {
var self = this;
$tw.utils.each(this.attributes,function(v,a) {
if(v !== undefined) {
// Setting certain attributes can cause a DOM error (eg xmlns on the svg element)
try {
domNode.setAttributeNS(null,a,v);
} catch(e) {
}
}
});
};
/*
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) {
self.children.push(self.makeChildWidget(childNode));
});
};
/*
Construct the widget object for a parse tree node
*/
Widget.prototype.makeChildWidget = function(parseTreeNode) {
var WidgetClass = this.widgetClasses[parseTreeNode.type];
if(!WidgetClass) {
WidgetClass = this.widgetClasses["text"];
parseTreeNode = {type: "text", text: "Undefined widget '" + parseTreeNode.type + "'"};
}
return new WidgetClass(parseTreeNode,{
wiki: this.wiki,
variables: {},
parentWidget: this,
document: this.document
});
};
/*
Render the children of this widget into the DOM
*/
Widget.prototype.renderChildren = function(parent,nextSibling) {
$tw.utils.each(this.children,function(childWidget) {
childWidget.render(parent,nextSibling);
});
};
/*
Add a list of event listeners from an array [{type:,handler:},...]
*/
Widget.prototype.addEventListeners = function(listeners) {
var self = this;
$tw.utils.each(listeners,function(listenerInfo) {
self.addEventListener(listenerInfo.type,listenerInfo.handler);
});
};
/*
Add an event listener
*/
Widget.prototype.addEventListener = function(type,handler) {
var self = this;
if(typeof handler === "string") { // The handler is a method name on this widget
this.eventListeners[type] = function(event) {
return self[handler].call(self,event);
};
}
};
/*
Dispatch an event to a widget. If the widget doesn't handle the event then it is also dispatched to the parent widget
*/
Widget.prototype.dispatchEvent = function(event) {
// Dispatch the event if this widget handles it
var listener = this.eventListeners[event.type];
if(listener) {
// Don't propagate the event if the listener returned false
if(!listener(event)) {
return false;
}
}
// Dispatch the event to the parent widget
if(this.parentWidget) {
return this.parentWidget.dispatchEvent(event);
}
return true;
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
Widget.prototype.refresh = function(changedTiddlers) {
return this.refreshChildren(changedTiddlers);
};
/*
Rebuild a previously rendered widget
*/
Widget.prototype.refreshSelf = function() {
var nextSibling = this.findNextSibling();
this.removeChildDomNodes();
this.render(this.parentDomNode,nextSibling);
};
/*
Refresh all the children of a widget
*/
Widget.prototype.refreshChildren = function(changedTiddlers) {
var self = this,
refreshed = false;
$tw.utils.each(this.children,function(childWidget) {
refreshed = childWidget.refresh(changedTiddlers) || refreshed;
});
return refreshed;
};
/*
Find the next sibling in the DOM to this widget. This is done by scanning the widget tree through all next siblings and their descendents that share the same parent DOM node
*/
Widget.prototype.findNextSibling = function(startIndex) {
// Refer to this widget by its index within its parents children
var parent = this.parentWidget,
index = startIndex !== undefined ? startIndex : parent.children.indexOf(this);
if(index === -1) {
throw "node not found in parents children";
}
// Look for a DOM node in the later siblings
while(++index < parent.children.length) {
var domNode = parent.children[index].findFirstDomNode();
if(domNode) {
return domNode;
}
}
// Go back and look for later siblings of our parent if it has the same parent dom node
parent = parent.parentWidget;
if(parent && parent.parentWidget && parent.parentWidget.parentDomNode === this.parentDomNode) {
index = parent.parentWidget.children.indexOf(parent);
return parent.findNextSibling(index);
}
return null;
};
/*
Find the first DOM node generated by a widget or its children
*/
Widget.prototype.findFirstDomNode = function() {
// Return the first dom node of this widget, if we've got one
if(this.domNodes.length > 0) {
return this.domNodes[0];
}
// Otherwise, recursively call our children
for(var t=0; t<this.children.length; t++) {
var domNode = this.children[t].findFirstDomNode();
if(domNode) {
return domNode;
}
}
return null;
};
/*
Remove any DOM nodes created by this widget or its children
*/
Widget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.children,function(childWidget) {
childWidget.removeChildDomNodes();
});
};
exports.widget = Widget;
})();

33
core/modules/startup.js Normal file → Executable file
View File

@ -12,6 +12,8 @@ This is the main application logic for both the client and server
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/new_widgets/widget.js");
exports.startup = function() {
var modules,n,m,f,commander;
// Load modules
@ -102,16 +104,31 @@ exports.startup = function() {
$tw.stylesheetManager = new $tw.utils.StylesheetManager($tw.wiki);
// Display the PageTemplate
var templateTitle = "$:/core/ui/PageTemplate",
parser = $tw.wiki.parseTiddler(templateTitle),
renderTree = new $tw.WikiRenderTree(parser,{wiki: $tw.wiki, context: {tiddlerTitle: 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);
parser = $tw.wiki.new_parseTiddler(templateTitle),
parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined,
widgetNode = new widget.widget(parseTreeNode,{
wiki: $tw.wiki,
document: document
});
$tw.new_pageContainer = document.createElement("div");
$tw.utils.addClass($tw.new_pageContainer,"tw-page-container");
document.body.insertBefore($tw.new_pageContainer,document.body.firstChild);
widgetNode.render($tw.new_pageContainer,null);
$tw.wiki.addEventListener("change",function(changes) {
renderTree.refreshInDom(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",{

42
core/modules/utils/fakedom.js Normal file → Executable file
View File

@ -12,16 +12,27 @@ A barebones implementation of DOM interfaces needed by the rendering mechanism.
/*global $tw: false */
"use strict";
var TW_TextNode = function(text) {
this.textContent = text;
// Sequence number used to enable us to track objects for testing
var sequenceNumber = null;
var bumpSequenceNumber = function(object) {
if(sequenceNumber !== null) {
object.sequenceNumber = sequenceNumber++;
}
}
var TW_TextNode = function(text) {
bumpSequenceNumber(this);
this.textContent = text;
};
var TW_Element = function(tag) {
bumpSequenceNumber(this);
this.tag = tag;
this.attributes = {};
this.isRaw = false;
this.children = [];
}
};
TW_Element.prototype.setAttribute = function(name,value) {
if(this.isRaw) {
@ -39,6 +50,19 @@ TW_Element.prototype.appendChild = function(node) {
node.parentNode = this;
};
TW_Element.prototype.insertBefore = function(node,nextSibling) {
if(nextSibling) {
var p = this.children.indexOf(nextSibling);
if(p !== -1) {
this.children.splice(p,0,node);
} else {
this.appendChild(node);
}
} else {
this.appendChild(node);
}
}
TW_Element.prototype.removeChild = function(node) {
var p = this.children.indexOf(node);
if(p !== -1) {
@ -60,6 +84,15 @@ TW_Element.prototype.addEventListener = function(type,listener,useCapture) {
// Do nothing
};
Object.defineProperty(TW_Element.prototype, "className", {
get: function() {
return this.attributes["class"] || "";
},
set: function(value) {
this.attributes["class"] = value;
}
});
Object.defineProperty(TW_Element.prototype, "outerHTML", {
get: function() {
var output = [],attr,a,v;
@ -123,6 +156,9 @@ Object.defineProperty(TW_Element.prototype, "textContent", {
});
var document = {
setSequenceNumber: function(value) {
sequenceNumber = value;
},
createElementNS: function(namespace,tag) {
return new TW_Element(tag);
},

87
core/modules/wiki.js Normal file → Executable file
View File

@ -24,6 +24,8 @@ last dispatched. Each entry is a hashmap containing two fields:
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/new_widgets/widget.js");
var USER_NAME_TITLE = "$:/status/UserName";
/*
@ -545,6 +547,10 @@ exports.getTiddlerList = function(title) {
// Return the named cache object for a tiddler. If the cache doesn't exist then the initializer function is invoked to create it
exports.getCacheForTiddler = function(title,cacheName,initializer) {
// Temporarily disable caching so that tweakParseTreeNode() works
return initializer();
this.caches = this.caches || {};
var caches = this.caches[title];
if(caches && caches[cacheName]) {
@ -621,6 +627,52 @@ exports.parseTiddler = function(title,options) {
}) : null;
};
// 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);
};
exports.new_parseText = function(type,text,options) {
var parser = this.parseText(type,text,options);
if(parser) {
tweakParseTreeNodes(parser.tree)
};
return parser;
};
exports.new_parseTiddler = function(title,options) {
var parser = this.parseTiddler(title,options);
if(parser) {
tweakParseTreeNodes(parser.tree)
};
return parser;
};
exports.new_parseTextReference = function(title,field,index,options) {
if(field === "text" || (!field && !index)) {
return this.new_parseTiddler(title,options);
} else {
var tiddler,text;
if(field) {
tiddler = this.getTiddler(title);
text = tiddler ? tiddler.fields[field] : "";
if(text === undefined) {
text = "";
}
return this.new_parseText("text/vnd.tiddlywiki",text,options);
} else if(index) {
text = this.extractTiddlerDataItem(title,index,"");
return this.new_parseText("text/vnd.tiddlywiki",text,options);
}
}
};
/*
Parse text in a specified format and render it into another format
outputType: content type for the output
@ -636,6 +688,24 @@ exports.renderText = function(outputType,textType,text,context) {
return outputType === "text/html" ? container.innerHTML : container.textContent;
};
/*
Parse text in a specified format and render it into another format
outputType: content type for the output
textType: content type of the input text
text: input text
*/
exports.new_renderText = function(outputType,textType,text,context) {
var parser = $tw.wiki.new_parseText(textType,text),
parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined,
widgetNode = new widget.widget(parseTreeNode,{
wiki: this,
document: $tw.document
});
var container = $tw.document.createElement("div");
widgetNode.render(container,null);
return outputType === "text/html" ? container.innerHTML : container.textContent;
};
/*
Parse text from a tiddler and render it into another format
outputType: content type for the output
@ -650,6 +720,23 @@ exports.renderTiddler = function(outputType,title,context) {
return outputType === "text/html" ? container.innerHTML : container.textContent;
};
/*
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) {
var parser = $tw.wiki.new_parseTiddler(title),
parseTreeNode = parser ? {type: "widget", children: parser.tree} : undefined,
widgetNode = new widget.widget(parseTreeNode,{
wiki: this,
document: $tw.document
});
var container = $tw.document.createElement("div");
widgetNode.render(container,null);
return outputType === "text/html" ? container.innerHTML : container.textContent;
};
/*
Select the appropriate saver modules and set them up
*/

View File

@ -1,3 +1,3 @@
title: $:/core/templates/wikified-tiddler
<$view field="text" format="wikified" />
<$transclude />

View File

@ -15,7 +15,7 @@ modifier: JeremyRuston
<$transclude title="$:/core/ui/EditorHint"/> <$button type="set" set="$:/ShowEditPreview" setTo="no">hide preview</$button>
<div class="tw-tiddler-preview">
<div class="tw-tiddler-preview-preview">
<$view field="text" format="wikified"/>
<$transclude />
</div>
<div class="tw-tiddler-preview-edit">
<$edit field="text"/>

View File

@ -2,5 +2,5 @@ title: $:/core/ui/ViewTemplate/body
tags: $:/tags/ViewTemplate
<div class="body">
<$view field="text" format="wikified"/>
<$transclude />
</div>

View File

@ -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<div class='myClass' title='myTitle'>\n and the content of a DIV<div>\n and an inner DIV</div> and back in the outer DIV</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<div class='myClass' title='the quick brown fox'>\n and the content of a DIV<div>\n and an inner DIV</div> and back in the outer DIVthe quick brown fox</div>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<div class='myClass' title='jumps over the lazy dog'>\n and the content of a DIV<div>\n and an inner DIV</div> and back in the outer DIVjumps over the lazy dog</div>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("<p>\nMy Jolly Old World is Jolly</p>");
});
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("<p>\nJolly Old World</p>");
// 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("<p>\nWorld-wide Jelly</p>");
// 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}}/></$setvariable> 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("<p>\nMy Jolly Old World is Jolly</p>");
// 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("<p>\nMy Jolly Old World is Jolly</p>");
// 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 = "<div class=<<myMacro 'something' three:'thing'>>>Content</div>";
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("<p>\n<div class='My something something, or other thing'>\nContent</div></p>");
});
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'/></$list>";
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("<p>\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo</p>");
// 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("<p>\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo</p>");
// 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("<p>\nTiddlerFiveTiddlerFourTiddlerOneTiddlerTwo</p>");
// 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("<p>\nTiddlerFiveTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo</p>");
// 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=<<listItem>>><$view field='title'/></$tiddler>"},
{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'></$list>";
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("<p>\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo</p>");
});
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'/></$list>";
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("<p>\nnothing</p>");
});
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'/></$list>";
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("<p>\nTiddlerFourTiddlerOneTiddlerThreeTiddlerTwo</p>");
// 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("<p>\nnothing</p>");
});
});
});
})();

View File

@ -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 />
</$list>

28
nbld.sh Executable file
View File

@ -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