1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-07-05 11:43:16 +00:00

Merge pull request #178 from Jermolene/widgets_redux2

Rejigged Widgets Redux - finally. Couldn't come soon enough.
This commit is contained in:
Jeremy Ruston 2013-11-08 00:35:34 -08:00
commit bec4403e0f
240 changed files with 26449 additions and 6154 deletions

View File

@ -11,7 +11,7 @@ mkdir -p tmp/tw2
node ./tiddlywiki.js \
editions/tw5.com \
--verbose \
--rendertiddler TiddlyWiki2ReadMe editions/tw2/readme.md text/html \
--new_rendertiddler TiddlyWiki2ReadMe editions/tw2/readme.md text/html \
|| exit 1
# cook the TiddlyWiki 2.x.x index file
@ -20,7 +20,7 @@ node ./tiddlywiki.js \
editions/tw2 \
--verbose \
--load editions/tw2/source/tiddlywiki.com/index.html.recipe \
--rendertiddler $:/core/templates/tiddlywiki2.template.html ./tmp/tw2/index.html text/plain \
--new_rendertiddler $:/core/templates/tiddlywiki2.template.html ./tmp/tw2/index.html text/plain \
|| exit 1
opendiff tmp/tw2/index.html editions/tw2/target/index.2.6.5.html
diff -q tmp/tw2/index.html editions/tw2/target/prebuilt.html

38
bld.sh
View File

@ -35,12 +35,12 @@ rm $TW5_BUILD_OUTPUT/static/*
node ./tiddlywiki.js \
./editions/tw5.com \
--verbose \
--rendertiddler ReadMe ./readme.md text/html \
--rendertiddler ContributingTemplate ./contributing.md text/html \
--rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \
--rendertiddler $:/core/templates/static.template.html $TW5_BUILD_OUTPUT/static.html text/plain \
--rendertiddler $:/core/templates/static.template.css $TW5_BUILD_OUTPUT/static/static.css text/plain \
--rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html $TW5_BUILD_OUTPUT/static text/plain \
--new_rendertiddler ReadMe ./readme.md text/html \
--new_rendertiddler ContributingTemplate ./contributing.md text/html \
--new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/index.html text/plain \
--new_rendertiddler $:/core/templates/static.template.html $TW5_BUILD_OUTPUT/static.html text/plain \
--new_rendertiddler $:/core/templates/static.template.css $TW5_BUILD_OUTPUT/static/static.css text/plain \
--new_rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html $TW5_BUILD_OUTPUT/static text/plain \
|| exit 1
# Second, encrypted.html: a version of the main file encrypted with the password "password"
@ -49,7 +49,7 @@ node ./tiddlywiki.js \
./editions/tw5.com \
--verbose \
--password password \
--rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \
--new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/encrypted.html text/plain \
|| exit 1
# Third, empty.html: empty wiki for reuse
@ -57,7 +57,7 @@ node ./tiddlywiki.js \
node ./tiddlywiki.js \
./editions/empty \
--verbose \
--rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/empty.html text/plain \
--new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/empty.html text/plain \
|| exit 1
# Fourth, tahoelafs.html: empty wiki with plugin for Tahoe-LAFS
@ -65,7 +65,7 @@ node ./tiddlywiki.js \
node ./tiddlywiki.js \
./editions/tahoelafs \
--verbose \
--rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/tahoelafs.html text/plain \
--new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/tahoelafs.html text/plain \
|| exit 1
# Fifth, d3demo.html: wiki to demo d3 plugin
@ -73,9 +73,25 @@ node ./tiddlywiki.js \
node ./tiddlywiki.js \
./editions/d3demo \
--verbose \
--rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/d3demo.html text/plain \
--new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/d3demo.html text/plain \
|| exit 1
# Sixth, run the test edition to run the node.js tests and to generate test.html for tests in the browser
# Sixth, codemirrordemo.html: wiki to demo codemirror plugin
node ./tiddlywiki.js \
./editions/codemirrordemo \
--verbose \
--new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/codemirrordemo.html text/plain \
|| exit 1
# Seventh, codemirrordemo.html: wiki to demo codemirror plugin
node ./tiddlywiki.js \
./editions/markdowndemo \
--verbose \
--new_rendertiddler $:/core/templates/tiddlywiki5.template.html $TW5_BUILD_OUTPUT/markdowndemo.html text/plain \
|| exit 1
# Eighth, run the test edition to run the node.js tests and to generate test.html for tests in the browser
./test.sh

View File

@ -107,6 +107,7 @@ children: array of further child nodes
innerHTML: optional HTML for element
class: class name(s)
document: defaults to current document
eventListeners: array of event listeners (this option won't work until $tw.utils.addEventListeners() has been loaded)
*/
$tw.utils.domMaker = function(tag,options) {
var doc = options.document || document;
@ -126,6 +127,9 @@ $tw.utils.domMaker = function(tag,options) {
$tw.utils.each(options.attributes,function(attribute,name) {
element.setAttribute(name,attribute);
});
if(options.eventListeners) {
$tw.utils.addEventListeners(element,options.eventListeners);
}
return element;
};
@ -227,7 +231,8 @@ $tw.utils.stringifyDate = function(value) {
$tw.utils.pad(value.getUTCMonth() + 1) +
$tw.utils.pad(value.getUTCDate()) +
$tw.utils.pad(value.getUTCHours()) +
$tw.utils.pad(value.getUTCMinutes());
$tw.utils.pad(value.getUTCMinutes()) +
$tw.utils.pad(value.getUTCMilliseconds(),3);
};
// Parse a date from a UTC YYYYMMDDHHMMSSMMM format string
@ -722,6 +727,7 @@ $tw.modules.define("$:/boot/tiddlerfields/created","tiddlerfield",{
});
$tw.modules.define("$:/boot/tiddlerfields/color","tiddlerfield",{
name: "color",
editTag: "input",
editType: "color"
});
$tw.modules.define("$:/boot/tiddlerfields/tags","tiddlerfield",{

View File

@ -1,60 +1,8 @@
<h1 class=''>
Contributing to <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>
TiddlyWiki5</a></h1><div class='tw-tiddler'>
<div class='tw-transclude'>
<p>
<a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>
TiddlyWiki5</a> welcomes contributions to its code and documentation via <a class='tw-tiddlylink tw-tiddlylink-external' href='https://github.com/Jermolene/TiddlyWiki5'>
GitHub</a>. Please take a moment to read these notes to help make the process as smooth as possible.</p><h2 class=''>
Bug Reports</h2><p>
From the perspective of the developers, a bug report that says little more than &quot;it doesn't work&quot; can be frustrating. For effective debugging, we need as much information as possible. At a minimum, please try to include:</p><ul>
<li>
A descriptive title</li><li>
A summary</li><li>
Steps to reproduce</li><li>
Expected behaviour</li><li>
Context (OS, browser etc.)</li></ul><p>
There's a lot of good material on the web about bug reports:</p><ul>
<li>
<a class='tw-tiddlylink tw-tiddlylink-external' href='http://mhay68.tumblr.com/post/1648223018/what-makes-a-good-bug-report'>
http://mhay68.tumblr.com/post/1648223018/what-makes-a-good-bug-report</a></li><li>
<a class='tw-tiddlylink tw-tiddlylink-external' href='http://www.chiark.greenend.org.uk/~sgtatham/bugs.html'>
http://www.chiark.greenend.org.uk/~sgtatham/bugs.html</a></li></ul><h2 class=''>
Pull Requests</h2><p>
Like other <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/OpenSource.html'>
OpenSource</a> projects, <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>
TiddlyWiki5</a> needs a signed <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-missing' href='http://five.tiddlywiki.com/static/ContributorLicenseAgreement.html'>
ContributorLicenseAgreement</a> from individual contributors before contributions of code can be accepted. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-missing' href='http://five.tiddlywiki.com/static/UnaMesa.html'>
UnaMesa</a> Association (the legal entity that owns <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki.html'>
TiddlyWiki</a> on behalf of the community).</p><ul>
<li>
For individuals use: <a class='tw-tiddlylink tw-tiddlylink-external' href='https://github.com/Jermolene/TiddlyWiki5/tree/master/licenses/cla-individual.md'>
CLA-individual</a></li><li>
For entities use: <a class='tw-tiddlylink tw-tiddlylink-external' href='https://github.com/Jermolene/TiddlyWiki5/tree/master/licenses/cla-entity.md'>
CLA-entity</a></li></ul><p>
<em>
This is a first pass at a CLA for <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki.html'>
TiddlyWiki</a>. Please let us know if we missed something important. If we do have to make essential changes to the CLA, there is a possibility that all contributors will need to sign it again</em></p><h3 class=''>
How to sign the CLA</h3><pre>
git clone https://github.com/Jermolene/TiddlyWiki5.git TiddlyWiki5
<h1 class=''>Contributing to <a class=' tw-tiddlylink tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>TiddlyWiki5</a></h1><p><a class=' tw-tiddlylink tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>TiddlyWiki5</a> welcomes contributions to its code and documentation via <a href='https://github.com/Jermolene/TiddlyWiki5' target='_blank'>GitHub</a>. Please take a moment to read these notes to help make the process as smooth as possible.</p><h2 class=''>Bug Reports</h2><p>From the perspective of the developers, a bug report that says little more than &quot;it doesn't work&quot; can be frustrating. For effective debugging, we need as much information as possible. At a minimum, please try to include:</p><ul><li>A descriptive title</li><li>A summary</li><li>Steps to reproduce</li><li>Expected behaviour</li><li>Context (OS, browser etc.)</li></ul><p>There's a lot of good material on the web about bug reports:</p><ul><li><a href='http://mhay68.tumblr.com/post/1648223018/what-makes-a-good-bug-report' target='_blank'>http://mhay68.tumblr.com/post/1648223018/what-makes-a-good-bug-report</a></li><li><a href='http://www.chiark.greenend.org.uk/~sgtatham/bugs.html' target='_blank'>http://www.chiark.greenend.org.uk/~sgtatham/bugs.html</a></li></ul><h2 class=''>Pull Requests</h2><p>Like other <a class=' tw-tiddlylink tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/OpenSource.html'>OpenSource</a> projects, <a class=' tw-tiddlylink tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>TiddlyWiki5</a> needs a signed contributor license agreement from individual contributors before contributions of code can be accepted. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the <a class=' tw-tiddlylink tw-tiddlylink-missing' href='http://five.tiddlywiki.com/static/UnaMesa.html'>UnaMesa</a> Association (the legal entity that owns <a class=' tw-tiddlylink tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki.html'>TiddlyWiki</a> on behalf of the community).</p><ul><li>For individuals use: <a href='https://github.com/Jermolene/TiddlyWiki5/tree/master/licenses/cla-individual.md' target='_blank'>CLA-individual</a></li><li>For entities use: <a href='https://github.com/Jermolene/TiddlyWiki5/tree/master/licenses/cla-entity.md' target='_blank'>CLA-entity</a></li></ul><p><em>This is a first pass at a CLA for <a class=' tw-tiddlylink tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki.html'>TiddlyWiki</a>. Please let us know if we missed something important. If we do have to make essential changes to the CLA, there is a possibility that all contributors will need to sign it again</em></p><h3 class=''>How to sign the CLA</h3><pre>git clone https://github.com/Jermolene/TiddlyWiki5.git TiddlyWiki5
cd TiddlyWiki5
git checkout -b sign-cla</pre><p>
<strong>
Add your name and the date to cla-individual.md or cla-entity.md</strong>. Date format (YYYY/MM/DD)
eg: <code>
Jeremy Ruston, @Jermolene, 2011/11/22</code></p><pre>
git add .
git checkout -b sign-cla</pre><p><strong>Add your name and the date to cla-individual.md or cla-entity.md</strong>. Date format (YYYY/MM/DD)
eg: <code>Jeremy Ruston, @Jermolene, 2011/11/22</code></p><pre>git add .
git commit -m &quot;sign contributor license agreement&quot;
git push origin sign-cla</pre><p>
<strong>
Go to your github repo and create a pull request.</strong></p><p>
<strong>
Thank you!</strong></p><h4 class=''>
Attribution</h4><p>
The CLA documents used for this project where created using <a class='tw-tiddlylink tw-tiddlylink-external' href='http://www.harmonyagreements.org'>
Harmony Project Templates</a>. &quot;HA-CLA-I-LIST Version 1.0&quot; for &quot;CLA-individual&quot; and &quot;HA-CLA-E-LIST Version 1.0&quot; for &quot;CLA-entity&quot;
</p></div></div><p>
<em>
This file was automatically generated by <a class='tw-tiddlylink tw-tiddlylink-internal tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>
TiddlyWiki5</a></em>
git push origin sign-cla</pre><p><strong>Go to your github repo and create a pull request.</strong></p><p><strong>Thank you!</strong></p><h4 class=''>Attribution</h4><p>The CLA documents used for this project where created using <a href='http://www.harmonyagreements.org' target='_blank'>Harmony Project Templates</a>. &quot;HA-CLA-I-LIST Version 1.0&quot; for &quot;CLA-individual&quot; and &quot;HA-CLA-E-LIST Version 1.0&quot; for &quot;CLA-entity&quot;
</p><p><em>This file was automatically generated by <a class=' tw-tiddlylink tw-tiddlylink-resolves' href='http://five.tiddlywiki.com/static/TiddlyWiki5.html'>TiddlyWiki5</a></em>
</p>

View File

@ -1,5 +1,5 @@
/*\
title: $:/core/modules/commands/rendertiddler.js
title: $:/core/modules/commands/new_rendertiddler.js
type: application/javascript
module-type: command
@ -13,7 +13,7 @@ Command to render a tiddler and save it to a file
"use strict";
exports.info = {
name: "rendertiddler",
name: "new_rendertiddler",
synchronous: false
};
@ -33,7 +33,7 @@ Command.prototype.execute = function() {
title = this.params[0],
filename = this.params[1],
type = this.params[2] || "text/html";
fs.writeFile(filename,this.commander.wiki.renderTiddler(type,title),"utf8",function(err) {
fs.writeFile(filename,this.commander.wiki.new_renderTiddler(type,title),"utf8",function(err) {
self.callback(err);
});
return null;

View File

@ -1,5 +1,5 @@
/*\
title: $:/core/modules/commands/rendertiddlers.js
title: $:/core/modules/commands/new_rendertiddlers.js
type: application/javascript
module-type: command
@ -12,8 +12,10 @@ Command to render several tiddlers to a folder of files
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/new_widgets/widget.js");
exports.info = {
name: "rendertiddlers",
name: "new_rendertiddlers",
synchronous: true
};
@ -36,13 +38,12 @@ Command.prototype.execute = function() {
pathname = this.params[2],
type = this.params[3] || "text/html",
extension = this.params[4] || ".html",
parser = wiki.parseTiddler(template),
tiddlers = wiki.filterTiddlers(filter);
$tw.utils.each(tiddlers,function(title) {
var renderTree = new $tw.WikiRenderTree(parser,{wiki: wiki, context: {tiddlerTitle: title}, document: $tw.document});
renderTree.execute();
var parser = wiki.new_parseTiddler(template),
widgetNode = wiki.makeWidget(parser,{variables: {currentTiddler: title}});
var container = $tw.document.createElement("div");
renderTree.renderInDom(container);
widgetNode.render(container,null);
var text = type === "text/html" ? container.innerHTML : container.textContent;
fs.writeFileSync(path.resolve(pathname,encodeURIComponent(title) + extension),text,"utf8");
});

View File

@ -147,7 +147,7 @@ var Command = function(params,commander,callback) {
path: /^\/$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": state.server.get("serveType")});
var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
var text = state.wiki.new_renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
response.end(text,"utf8");
}
});

View File

@ -0,0 +1,39 @@
/*\
title: $:/core/modules/filters/modules.js
type: application/javascript
module-type: filteroperator
Filter operator for returning the titles of the modules of a given type in this wiki
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.modules = function(source,operator,options) {
var results = [],
pushModules = function(type) {
$tw.utils.each($tw.modules.types[type],function(moduleInfo,moduleName) {
results.push(moduleName);
});
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
pushModules(title);
});
} else {
$tw.utils.each(source,function(element,title) {
pushModules(title);
});
}
results.sort();
return results;
};
})();

View File

@ -0,0 +1,27 @@
/*\
title: $:/core/modules/filters/moduletypes.js
type: application/javascript
module-type: filteroperator
Filter operator for returning the names of the module types in this wiki
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.moduletypes = function(source,operator,options) {
var results = [];
$tw.utils.each($tw.modules.types,function(moduleInfo,type) {
results.push(type);
});
results.sort();
return results;
};
})();

View File

@ -0,0 +1,30 @@
/*\
title: $:/core/modules/macros/changecount.js
type: application/javascript
module-type: macro
Macro to return the changecount for the current tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Information about this macro
*/
exports.name = "changecount";
exports.params = [];
/*
Run the macro
*/
exports.run = function() {
return this.wiki.getChangeCount(this.getVariable("currentTiddler")) + "";
};
})();

View File

@ -0,0 +1,44 @@
/*\
title: $:/core/modules/macros/makedatauri.js
type: application/javascript
module-type: macro
Macro to convert the content of a tiddler to a data URI
<<makedatauri text:"Text to be converted" type:"text/vnd.tiddlywiki">>
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Information about this macro
*/
exports.name = "makedatauri";
exports.params = [
{name: "text"},
{name: "type"}
];
/*
Run the macro
*/
exports.run = function(text,type) {
type = type || "text/vnd.tiddlywiki";
var typeInfo = $tw.config.contentTypeInfo[type] || $tw.config.contentTypeInfo["text/plain"],
isBase64 = typeInfo.encoding === "base64",
parts = [];
parts.push("data:");
parts.push(type);
parts.push(isBase64 ? ";base64" : "");
parts.push(",");
parts.push(isBase64 ? text : encodeURIComponent(text));
return parts.join("");
};
})();

View File

@ -0,0 +1,32 @@
/*\
title: $:/core/modules/macros/qualify.js
type: application/javascript
module-type: macro
Macro to qualify a state tiddler title according
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Information about this macro
*/
exports.name = "qualify";
exports.params = [
{name: "title"}
];
/*
Run the macro
*/
exports.run = function(title) {
return title + "-" + this.getStateQualifier();
};
})();

View File

@ -0,0 +1,30 @@
/*\
title: $:/core/modules/macros/version.js
type: application/javascript
module-type: macro
Macro to return the TiddlyWiki core version number
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Information about this macro
*/
exports.name = "version";
exports.params = [];
/*
Run the macro
*/
exports.run = function() {
return $tw.version;
};
})();

View File

@ -0,0 +1,78 @@
/*\
title: $:/core/modules/new_widgets/browse.js
type: application/javascript
module-type: new_widget
Browse widget for browsing for files to import
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var BrowseWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
BrowseWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
BrowseWidget.prototype.render = function(parent,nextSibling) {
var self = this;
// Remember parent
this.parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
// Create element
var domNode = this.document.createElement("input");
domNode.setAttribute("type","file");
domNode.setAttribute("multiple","multiple");
// Add a click event handler
domNode.addEventListener("change",function (event) {
self.wiki.readFiles(event.target.files,function(tiddlerFields) {
self.dispatchEvent({type: "tw-import-tiddlers", param: JSON.stringify([tiddlerFields])});
});
return false;
},false);
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
/*
Compute the internal state of the widget
*/
BrowseWidget.prototype.execute = function() {
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
BrowseWidget.prototype.refresh = function(changedTiddlers) {
return false;
};
/*
Remove any DOM nodes created by this widget or its children
*/
BrowseWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.browse = BrowseWidget;
})();

View File

@ -0,0 +1,151 @@
/*\
title: $:/core/modules/new_widgets/button.js
type: application/javascript
module-type: new_widget
Button widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var ButtonWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
ButtonWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
ButtonWidget.prototype.render = function(parent,nextSibling) {
var self = this;
// Remember parent
this.parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
// Create element
var domNode = this.document.createElement("button");
// Assign classes
var classes = this["class"].split(" ") || [];
if(this.selectedClass) {
if(this.set && this.setTo && this.isSelected()) {
$tw.utils.pushTop(classes,this.selectedClass.split(" "));
}
if(this.popup && this.isPoppedUp()) {
$tw.utils.pushTop(classes,this.selectedClass.split(" "));
}
}
domNode.className = classes.join(" ");
// Assign classes
if(this.style) {
domNode.setAttribute("style",this.style);
}
// Add a click event handler
domNode.addEventListener("click",function (event) {
var handled = false;
if(self.message) {
self.dispatchMessage(event);
handled = true;
}
if(self.popup) {
self.triggerPopup(event);
handled = true;
}
if(self.set) {
self.setTiddler();
handled = true;
}
if(handled) {
event.preventDefault();
event.stopPropagation();
}
return handled;
},false);
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
ButtonWidget.prototype.isSelected = function() {
var tiddler = this.wiki.getTiddler(this.set);
return tiddler ? tiddler.fields.text === this.setTo : this.defaultSetValue === this.setTo;
};
ButtonWidget.prototype.isPoppedUp = function() {
var tiddler = this.wiki.getTiddler(this.popup);
var result = tiddler && tiddler.fields.text ? $tw.popup.readPopupState(this.popup,tiddler.fields.text) : false;
return result;
};
ButtonWidget.prototype.dispatchMessage = function(event) {
this.dispatchEvent({type: this.message, param: this.param, tiddlerTitle: this.getVariable("currentTiddler")});
};
ButtonWidget.prototype.triggerPopup = function(event) {
$tw.popup.triggerPopup({
domNode: this.domNodes[0],
title: this.popup,
wiki: this.wiki
});
};
ButtonWidget.prototype.setTiddler = function() {
var tiddler = this.wiki.getTiddler(this.set);
this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: this.set, text: this.setTo}));
};
/*
Compute the internal state of the widget
*/
ButtonWidget.prototype.execute = function() {
// Get attributes
this.message = this.getAttribute("message");
this.param = this.getAttribute("param");
this.set = this.getAttribute("set");
this.setTo = this.getAttribute("setTo");
this.popup = this.getAttribute("popup");
this.hover = this.getAttribute("hover");
this["class"] = this.getAttribute("class","");
this.style = this.getAttribute("style");
this.selectedClass = this.getAttribute("selectedClass");
this.defaultSetValue = this.getAttribute("default");
// Make child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ButtonWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes["class"] || changedAttributes.selectedClass || changedAttributes.style || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
/*
Remove any DOM nodes created by this widget or its children
*/
ButtonWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.button = ButtonWidget;
})();

View File

@ -0,0 +1,118 @@
/*\
title: $:/core/modules/new_widgets/checkbox.js
type: application/javascript
module-type: new_widget
Checkbox widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var CheckboxWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
CheckboxWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
CheckboxWidget.prototype.render = function(parent,nextSibling) {
// Save the parent dom node
this.parentDomNode = parent;
// Compute our attributes
this.computeAttributes();
// Execute our logic
this.execute();
// Create our elements
this.labelDomNode = this.document.createElement("label");
this.inputDomNode = this.document.createElement("input");
this.inputDomNode.setAttribute("type","checkbox");
if(this.getValue()) {
this.inputDomNode.setAttribute("checked","true");
}
this.labelDomNode.appendChild(this.inputDomNode);
this.spanDomNode = this.document.createElement("span");
this.labelDomNode.appendChild(this.spanDomNode);
// Add a click event handler
$tw.utils.addEventListeners(this.inputDomNode,[
{name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"}
]);
// Insert the label into the DOM and render any children
parent.insertBefore(this.labelDomNode,nextSibling);
this.renderChildren(this.spanDomNode,null);
this.domNodes.push(this.labelDomNode);
};
CheckboxWidget.prototype.getValue = function() {
var tiddler = this.wiki.getTiddler(this.checkboxTitle);
return tiddler ? tiddler.hasTag(this.checkboxTag) : false;
};
CheckboxWidget.prototype.handleChangeEvent = function(event) {
var checked = this.inputDomNode.checked,
tiddler = this.wiki.getTiddler(this.checkboxTitle);
if(tiddler && tiddler.hasTag(this.checkboxTag) !== checked) {
var newTags = tiddler.fields.tags.slice(0),
pos = newTags.indexOf(this.checkboxTag);
if(pos !== -1) {
newTags.splice(pos,1);
}
if(checked) {
newTags.push(this.checkboxTag);
}
this.wiki.addTiddler(new $tw.Tiddler(tiddler,{tags: newTags},this.wiki.getModificationFields()));
}
};
/*
Compute the internal state of the widget
*/
CheckboxWidget.prototype.execute = function() {
// Get the parameters from the attributes
this.checkboxTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.checkboxTag = this.getAttribute("tag");
// Make the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
CheckboxWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.tag || changedAttributes["class"]) {
this.refreshSelf();
return true;
} else {
var refreshed = false;
if(changedTiddlers[this.checkboxTitle]) {
this.inputDomNode.checked = this.getValue();
refreshed = true;
}
return this.refreshChildren(changedTiddlers) || refreshed;
}
};
/*
Remove any DOM nodes created by this widget or its children
*/
CheckboxWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.checkbox = CheckboxWidget;
})();

View File

@ -0,0 +1,81 @@
/*\
title: $:/core/modules/new_widgets/count.js
type: application/javascript
module-type: new_widget
Count widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var CountWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
CountWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
CountWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
var textNode = this.document.createTextNode(this.currentCount);
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
};
/*
Compute the internal state of the widget
*/
CountWidget.prototype.execute = function() {
// Get parameters from our attributes
this.filter = this.getAttribute("filter");
// Execute the filter
if(this.filter) {
this.currentCount = this.wiki.filterTiddlers(this.filter,this.getVariable("currentTiddler")).length;
} else {
this.currentCount = undefined;
}
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
CountWidget.prototype.refresh = function(changedTiddlers) {
// Re-execute the filter to get the count
var oldCount = this.currentCount;
this.execute();
if(this.currentCount !== oldCount) {
// Regenerate and rerender the widget and replace the existing DOM node
this.refreshSelf();
return true;
} else {
return false;
}
};
/*
Remove any DOM nodes created by this widget
*/
CountWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.count = CountWidget;
})();

View File

@ -0,0 +1,190 @@
/*\
title: $:/core/modules/new_widgets/dropzone.js
type: application/javascript
module-type: new_widget
Dropzone widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var DropZoneWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
DropZoneWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
DropZoneWidget.prototype.render = function(parent,nextSibling) {
var self = this;
// Remember parent
this.parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
// Create element
var domNode = this.document.createElement("div");
domNode.className = "tw-dropzone";
// Add event handlers
$tw.utils.addEventListeners(domNode,[
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"},
{name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"}
]);
domNode.addEventListener("click",function (event) {
},false);
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
DropZoneWidget.prototype.handleDragEnterEvent = function(event) {
// We count enter/leave events
this.dragEnterCount = (this.dragEnterCount || 0) + 1;
// If we're entering for the first time we need to apply highlighting
if(this.dragEnterCount === 1) {
$tw.utils.addClass(this.domNodes[0],"tw-dragover");
}
// Tell the browser that we're ready to handle the drop
event.preventDefault();
// Tell the browser not to ripple the drag up to any parent drop handlers
event.stopPropagation();
};
DropZoneWidget.prototype.handleDragOverEvent = function(event) {
// Tell the browser that we're still interested in the drop
event.preventDefault();
event.dataTransfer.dropEffect = "copy"; // Explicitly show this is a copy
};
DropZoneWidget.prototype.handleDragLeaveEvent = function(event) {
// Reduce the enter count
this.dragEnterCount = (this.dragEnterCount || 0) - 1;
// Remove highlighting if we're leaving externally
if(this.dragEnterCount <= 0) {
$tw.utils.removeClass(this.domNodes[0],"tw-dragover");
}
};
DropZoneWidget.prototype.handleDropEvent = function(event) {
var self = this,
dataTransfer = event.dataTransfer;
// Reset the enter count
this.dragEnterCount = 0;
// Remove highlighting
$tw.utils.removeClass(this.domNodes[0],"tw-dragover");
// Try to import the various data types we understand
this.importData(dataTransfer);
// Import any files in the drop
this.wiki.readFiles(dataTransfer.files,function(tiddlerFields) {
self.dispatchEvent({type: "tw-import-tiddlers", param: JSON.stringify([tiddlerFields])});
});
// Tell the browser that we handled the drop
event.preventDefault();
// Stop the drop ripple up to any parent handlers
event.stopPropagation();
};
DropZoneWidget.prototype.importData = function(dataTransfer) {
for(var t=0; t<this.importDataTypes.length; t++) {
var dataType = this.importDataTypes[t];
var data = dataTransfer.getData(dataType.type);
if(data !== "") {
var tiddlerFields = dataType.convertToFields(data);
if(!tiddlerFields.title) {
tiddlerFields.title = this.generateTitle("Untitled");
}
this.dispatchEvent({type: "tw-import-tiddlers", param: JSON.stringify([tiddlerFields])});
return;
}
};
};
DropZoneWidget.prototype.importDataTypes = [
{type: "text/vnd.tiddler", convertToFields: function(data) {
return JSON.parse(data);
}},
{type: "text/plain", convertToFields: function(data) {
return {
text: data
};
}},
{type: "text/uri-list", convertToFields: function(data) {
return {
text: data
};
}}
];
DropZoneWidget.prototype.handlePasteEvent = function(event) {
// Let the browser handle it if we're in a textarea or input box
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1) {
var self = this,
items = event.clipboardData.items;
// Enumerate the clipboard items
for(var t = 0; t<items.length; t++) {
var item = items[t];
if(item.kind === "file") {
// Import any files
this.wiki.readFile(item.getAsFile(),function(tiddlerFields) {
self.dispatchEvent({type: "tw-import-tiddlers", param: JSON.stringify([tiddlerFields])});
});
} else if(item.kind === "string") {
// Create tiddlers from string items
item.getAsString(function(str) {
var tiddlerFields = {
title: self.wiki.generateNewTitle("Untitled"),
text: str
};
self.dispatchEvent({type: "tw-import-tiddlers", param: JSON.stringify([tiddlerFields])});
});
}
}
// Tell the browser that we've handled the paste
event.stopPropagation();
event.preventDefault();
}
};
/*
Compute the internal state of the widget
*/
DropZoneWidget.prototype.execute = function() {
// Make child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
DropZoneWidget.prototype.refresh = function(changedTiddlers) {
return this.refreshChildren(changedTiddlers);
};
/*
Remove any DOM nodes created by this widget or its children
*/
DropZoneWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.dropzone = DropZoneWidget;
})();

View File

@ -0,0 +1,312 @@
/*\
title: $:/core/modules/new_widgets/edit-bitmap.js
type: application/javascript
module-type: new_widget
Edit-bitmap widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
// Default image sizes
var DEFAULT_IMAGE_WIDTH = 300,
DEFAULT_IMAGE_HEIGHT = 185;
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var EditBitmapWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
EditBitmapWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
EditBitmapWidget.prototype.render = function(parent,nextSibling) {
var self = this;
// Save the parent dom node
this.parentDomNode = parent;
// Compute our attributes
this.computeAttributes();
// Execute our logic
this.execute();
// Create our element
this.canvasDomNode = $tw.utils.domMaker("canvas",{
document: this.document,
"class":"tw-edit-bitmapeditor",
eventListeners: [{
name: "touchstart", handlerObject: this, handlerMethod: "handleTouchStartEvent"
},{
name: "touchmove", handlerObject: this, handlerMethod: "handleTouchMoveEvent"
},{
name: "touchend", handlerObject: this, handlerMethod: "handleTouchEndEvent"
},{
name: "mousedown", handlerObject: this, handlerMethod: "handleMouseDownEvent"
},{
name: "mousemove", handlerObject: this, handlerMethod: "handleMouseMoveEvent"
},{
name: "mouseup", handlerObject: this, handlerMethod: "handleMouseUpEvent"
}]
});
this.widthDomNode = $tw.utils.domMaker("input",{
document: this.document,
"class":"tw-edit-bitmapeditor-width",
eventListeners: [{
name: "change", handlerObject: this, handlerMethod: "handleWidthChangeEvent"
}]
});
this.heightDomNode = $tw.utils.domMaker("input",{
document: this.document,
"class":"tw-edit-bitmapeditor-height",
eventListeners: [{
name: "change", handlerObject: this, handlerMethod: "handleHeightChangeEvent"
}]
});
// Insert the elements into the DOM
parent.insertBefore(this.canvasDomNode,nextSibling);
parent.insertBefore(this.widthDomNode,nextSibling);
parent.insertBefore(this.heightDomNode,nextSibling);
this.domNodes.push(this.canvasDomNode,this.widthDomNode,this.heightDomNode);
// Load the image into the canvas
this.loadCanvas();
};
/*
Compute the internal state of the widget
*/
EditBitmapWidget.prototype.execute = function() {
// Get our parameters
this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
};
/*
Note that the bitmap editor intentionally doesn't try to refresh itself because it would be confusing to have the image changing spontaneously while editting it
*/
EditBitmapWidget.prototype.refresh = function(changedTiddlers) {
return false;
};
/*
Remove any DOM nodes created by this widget or its children
*/
EditBitmapWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
EditBitmapWidget.prototype.loadCanvas = function() {
var tiddler = this.wiki.getTiddler(this.editTitle),
currImage = new Image();
// Set up event handlers for loading the image
var self = this;
currImage.onload = function() {
// Copy the image to the on-screen canvas
self.initCanvas(self.canvasDomNode,currImage.width,currImage.height,currImage);
// And also copy the current bitmap to the off-screen canvas
self.currCanvas = self.document.createElement("canvas");
self.initCanvas(self.currCanvas,currImage.width,currImage.height,currImage);
// Set the width and height input boxes
self.updateSize();
};
currImage.onerror = function() {
// Set the on-screen canvas size and clear it
self.initCanvas(self.canvasDomNode,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
// Set the off-screen canvas size and clear it
self.currCanvas = self.document.createElement("canvas");
self.initCanvas(self.currCanvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
// Set the width and height input boxes
self.updateSize();
}
// Get the current bitmap into an image object
currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
};
EditBitmapWidget.prototype.initCanvas = function(canvas,width,height,image) {
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
if(image) {
ctx.drawImage(image,0,0);
} else {
ctx.fillStyle = "#fff";
ctx.fillRect(0,0,canvas.width,canvas.height);
}
}
/*
** Update the input boxes with the actual size of the canvas
*/
EditBitmapWidget.prototype.updateSize = function() {
this.widthDomNode.value = this.currCanvas.width;
this.heightDomNode.value = this.currCanvas.height;
};
/*
** Change the size of the canvas, preserving the current image
*/
EditBitmapWidget.prototype.changeCanvasSize = function(newWidth,newHeight) {
// Create and size a new canvas
var newCanvas = this.document.createElement("canvas");
this.initCanvas(newCanvas,newWidth,newHeight);
// Copy the old image
var ctx = newCanvas.getContext("2d");
ctx.drawImage(this.currCanvas,0,0);
// Set the new canvas as the current one
this.currCanvas = newCanvas;
// Set the size of the onscreen canvas
this.canvasDomNode.width = newWidth;
this.canvasDomNode.height = newHeight;
// Paint the onscreen canvas with the offscreen canvas
ctx = this.canvasDomNode.getContext("2d");
ctx.drawImage(this.currCanvas,0,0);
};
EditBitmapWidget.prototype.handleWidthChangeEvent = function(event) {
// Get the new width
var newWidth = parseInt(this.widthDomNode.value,10);
// Update if necessary
if(newWidth > 0 && newWidth !== this.currCanvas.width) {
this.changeCanvasSize(newWidth,this.currCanvas.height);
}
// Update the input controls
this.updateSize();
};
EditBitmapWidget.prototype.handleHeightChangeEvent = function(event) {
// Get the new width
var newHeight = parseInt(this.heightDomNode.value,10);
// Update if necessary
if(newHeight > 0 && newHeight !== this.currCanvas.height) {
this.changeCanvasSize(this.currCanvas.width,newHeight);
}
// Update the input controls
this.updateSize();
};
EditBitmapWidget.prototype.handleTouchStartEvent = function(event) {
this.brushDown = true;
this.strokeStart(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
event.stopPropagation();
return false;
};
EditBitmapWidget.prototype.handleTouchMoveEvent = function(event) {
if(this.brushDown) {
this.strokeMove(event.touches[0].clientX,event.touches[0].clientY);
}
event.preventDefault();
event.stopPropagation();
return false;
};
EditBitmapWidget.prototype.handleTouchEndEvent = function(event) {
if(this.brushDown) {
this.brushDown = false;
this.strokeEnd();
}
event.preventDefault();
event.stopPropagation();
return false;
};
EditBitmapWidget.prototype.handleMouseDownEvent = function(event) {
this.strokeStart(event.clientX,event.clientY);
this.brushDown = true;
event.preventDefault();
event.stopPropagation();
return false;
};
EditBitmapWidget.prototype.handleMouseMoveEvent = function(event) {
if(this.brushDown) {
this.strokeMove(event.clientX,event.clientY);
event.preventDefault();
event.stopPropagation();
return false;
}
return true;
};
EditBitmapWidget.prototype.handleMouseUpEvent = function(event) {
if(this.brushDown) {
this.brushDown = false;
this.strokeEnd();
event.preventDefault();
event.stopPropagation();
return false;
}
return true;
};
EditBitmapWidget.prototype.adjustCoordinates = function(x,y) {
var canvasRect = this.canvasDomNode.getBoundingClientRect(),
scale = this.canvasDomNode.width/canvasRect.width;
return {x: (x - canvasRect.left) * scale, y: (y - canvasRect.top) * scale};
};
EditBitmapWidget.prototype.strokeStart = function(x,y) {
// Start off a new stroke
this.stroke = [this.adjustCoordinates(x,y)];
};
EditBitmapWidget.prototype.strokeMove = function(x,y) {
var ctx = this.canvasDomNode.getContext("2d"),
t;
// Add the new position to the end of the stroke
this.stroke.push(this.adjustCoordinates(x,y));
// Redraw the previous image
ctx.drawImage(this.currCanvas,0,0);
// Render the stroke
ctx.strokeStyle = "#ff0";
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.moveTo(this.stroke[0].x,this.stroke[0].y);
for(t=1; t<this.stroke.length-1; t++) {
var s1 = this.stroke[t],
s2 = this.stroke[t-1],
tx = (s1.x + s2.x)/2,
ty = (s1.y + s2.y)/2;
ctx.quadraticCurveTo(s2.x,s2.y,tx,ty);
}
ctx.stroke();
};
EditBitmapWidget.prototype.strokeEnd = function() {
// Copy the bitmap to the off-screen canvas
var ctx = this.currCanvas.getContext("2d");
ctx.drawImage(this.canvasDomNode,0,0);
// Save the image into the tiddler
this.saveChanges();
};
EditBitmapWidget.prototype.saveChanges = function() {
var tiddler = this.wiki.getTiddler(this.editTitle);
if(tiddler) {
// data URIs look like "data:<type>;base64,<text>"
var dataURL = this.canvasDomNode.toDataURL(tiddler.fields.type,1.0),
posColon = dataURL.indexOf(":"),
posSemiColon = dataURL.indexOf(";"),
posComma = dataURL.indexOf(","),
type = dataURL.substring(posColon+1,posSemiColon),
text = dataURL.substring(posComma+1);
var update = {type: type, text: text};
this.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
};
exports["edit-bitmap"] = EditBitmapWidget;
})();

View File

@ -0,0 +1,259 @@
/*\
title: $:/core/modules/new_widgets/edit-text.js
type: application/javascript
module-type: new_widget
Edit-text widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var MIN_TEXT_AREA_HEIGHT = 100; // Minimum height of textareas in pixels
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var EditTextWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
EditTextWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
EditTextWidget.prototype.render = function(parent,nextSibling) {
var self = this;
// Save the parent dom node
this.parentDomNode = parent;
// Compute our attributes
this.computeAttributes();
// Execute our logic
this.execute();
// Create our element
var domNode = this.document.createElement(this.editTag);
if(this.editType) {
domNode.setAttribute("type",this.editType);
}
if(this.editPlaceholder) {
domNode.setAttribute("placeholder",this.editPlaceholder);
}
// Assign classes
domNode.className = this.editClass;
// Set the text
var editInfo = this.getEditInfo();
if(this.editTag === "textarea") {
domNode.appendChild(this.document.createTextNode(editInfo.value));
} else {
domNode.setAttribute("value",editInfo.value)
}
// Add an input event handler
$tw.utils.addEventListeners(domNode,[
{name: "focus", handlerObject: this, handlerMethod: "handleFocusEvent"},
{name: "input", handlerObject: this, handlerMethod: "handleInputEvent"}
]);
// Insert the element into the DOM
parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
if(this.postRender) {
this.postRender();
}
// Fix height
this.fixHeight();
};
/*
Get the tiddler being edited and current value
*/
EditTextWidget.prototype.getEditInfo = function() {
// Get the edit value
var tiddler = this.wiki.getTiddler(this.editTitle),
value;
if(this.editIndex) {
value = this.wiki.extractTiddlerDataItem(this.editTitle,this.editIndex,this.editDefault);
} else {
// Get the current tiddler and the field name
if(tiddler) {
// If we've got a tiddler, the value to display is the field string value
value = tiddler.getFieldString(this.editField);
} else {
// Otherwise, we need to construct a default value for the editor
switch(this.editField) {
case "text":
value = "Type the text for the tiddler '" + this.editTitle + "'";
break;
case "title":
value = this.editTitle;
break;
default:
value = "";
break;
}
if(this.editDefault !== undefined) {
value = this.editDefault;
}
}
}
return {tiddler: tiddler, value: value};
};
/*
Compute the internal state of the widget
*/
EditTextWidget.prototype.execute = function() {
// Get our parameters
this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.editField = this.getAttribute("field","text");
this.editIndex = this.getAttribute("index");
this.editDefault = this.getAttribute("default");
this.editClass = this.getAttribute("class");
this.editPlaceholder = this.getAttribute("placeholder");
this.editFocusPopup = this.getAttribute("focusPopup");
// Get the editor element tag and type
var tag,type;
if(this.editField === "text") {
tag = "textarea";
} else {
tag = "input";
var fieldModule = $tw.Tiddler.fieldModules[this.editField];
if(fieldModule && fieldModule.editTag) {
tag = fieldModule.editTag;
}
if(fieldModule && fieldModule.editType) {
type = fieldModule.editType;
}
type = type || "text";
}
// Get the rest of our parameters
this.editTag = this.getAttribute("tag",tag);
this.editType = this.getAttribute("type",type);
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
EditTextWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
// Completely rerender if any of our attributes have changed
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index) {
this.refreshSelf();
return true;
} else if(changedTiddlers[this.editTitle]) {
this.updateEditor(this.getEditInfo().value);
return true;
}
return false;
};
/*
Update the editor with new text. This method is separate from updateEditorDomNode()
so that subclasses can override updateEditor() and still use updateEditorDomNode()
*/
EditTextWidget.prototype.updateEditor = function(text) {
this.updateEditorDomNode(text);
};
/*
Update the editor dom node with new text
*/
EditTextWidget.prototype.updateEditorDomNode = function(text) {
// Replace the edit value if the tiddler we're editing has changed
var domNode = this.domNodes[0];
if(!domNode.isTiddlyWikiFakeDom) {
if(this.document.activeElement !== domNode) {
domNode.value = text;
}
// Fix the height if needed
this.fixHeight();
}
};
/*
Fix the height of textareas to fit their content
*/
EditTextWidget.prototype.fixHeight = function() {
var self = this,
domNode = this.domNodes[0];
if(domNode && !domNode.isTiddlyWikiFakeDom && this.editTag === "textarea") {
$tw.utils.nextTick(function() {
// Resize the textarea to fit its content, preserving scroll position
var scrollPosition = $tw.utils.getScrollPosition(),
scrollTop = scrollPosition.y;
// Set its height to auto so that it snaps to the correct height
domNode.style.height = "auto";
// Calculate the revised height
var newHeight = Math.max(domNode.scrollHeight + domNode.offsetHeight - domNode.clientHeight,MIN_TEXT_AREA_HEIGHT);
// Only try to change the height if it has changed
if(newHeight !== domNode.offsetHeight) {
domNode.style.height = newHeight + "px";
// Make sure that the dimensions of the textarea are recalculated
$tw.utils.forceLayout(domNode);
// Check that the scroll position is still visible before trying to scroll back to it
scrollTop = Math.min(scrollTop,self.document.body.scrollHeight - window.innerHeight);
window.scrollTo(scrollPosition.x,scrollTop);
}
});
}
};
/*
Handle a dom "input" event
*/
EditTextWidget.prototype.handleInputEvent = function(event) {
this.saveChanges(this.domNodes[0].value);
this.fixHeight();
return true;
};
EditTextWidget.prototype.handleFocusEvent = function(event) {
if(this.editFocusPopup) {
$tw.popup.triggerPopup({
domNode: this.domNodes[0],
title: this.editFocusPopup,
wiki: this.wiki,
force: true
});
}
return true;
};
EditTextWidget.prototype.saveChanges = function(text) {
if(this.editField) {
var tiddler = this.wiki.getTiddler(this.editTitle);
if(!tiddler) {
tiddler = new $tw.Tiddler({title: this.editTitle});
}
var oldValue = tiddler.getFieldString(this.editField);
if(text !== oldValue) {
var update = {};
update[this.editField] = text;
this.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
} else {
var data = this.wiki.getTiddlerData(this.editTitle,{});
if(data[this.editIndex] !== text) {
data[this.editIndex] = text;
this.wiki.setTiddlerData(this.editTitle,data);
}
}
};
/*
Remove any DOM nodes created by this widget or its children
*/
EditTextWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports["edit-text"] = EditTextWidget;
})();

View File

@ -0,0 +1,94 @@
/*\
title: $:/core/modules/new_widgets/edit.js
type: application/javascript
module-type: new_widget
Edit widget is a meta-widget chooses the appropriate actual editting widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var EditWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
EditWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
EditWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
// Mappings from content type to editor type
// TODO: This information should be configurable/extensible
var editorTypeMappings = {
"text/vnd.tiddlywiki": "text",
"image/svg+xml": "text",
"image/jpg": "bitmap",
"image/jpeg": "bitmap",
"image/gif": "bitmap",
"image/png": "bitmap"
};
/*
Compute the internal state of the widget
*/
EditWidget.prototype.execute = function() {
// Get our parameters
this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.editField = this.getAttribute("field","text");
this.editIndex = this.getAttribute("index");
this.editClass = this.getAttribute("class");
// Get the content type of the thing we're editing
var type;
if(this.editField === "text") {
var tiddler = this.wiki.getTiddler(this.editTitle);
if(tiddler) {
type = tiddler.fields.type;
}
}
type = type || "text/vnd.tiddlywiki";
// Choose the appropriate edit widget
var editorType = editorTypeMappings[type] || "text";
// Make the child widgets
this.makeChildWidgets([{
type: "edit-" + editorType,
attributes: {
title: {type: "string", value: this.editTitle},
field: {type: "string", value: this.editField},
index: {type: "string", value: this.editIndex},
"class": {type: "string", value: this.editClass}
}
}]);
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
EditWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
exports.edit = EditWidget;
})();

View File

@ -0,0 +1,85 @@
/*\
title: $:/core/modules/new_widgets/element.js
type: application/javascript
module-type: new_widget
Element widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var ElementWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
ElementWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
ElementWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
var domNode = this.document.createElementNS(this.namespace,this.parseTreeNode.tag);
this.assignAttributes(domNode);
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
/*
Compute the internal state of the widget
*/
ElementWidget.prototype.execute = function() {
// Select the namespace for the tag
var tagNamespaces = {
svg: "http://www.w3.org/2000/svg",
math: "http://www.w3.org/1998/Math/MathML",
body: "http://www.w3.org/1999/xhtml"
};
this.namespace = tagNamespaces[this.parseTreeNode.tag];
if(this.namespace) {
this.setVariable("namespace",this.namespace);
} else {
this.namespace = this.getVariable("namespace",{defaultValue: "http://www.w3.org/1999/xhtml"});
}
// Make the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ElementWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(),
hasChangedAttributes = $tw.utils.count(changedAttributes) > 0;
if(hasChangedAttributes) {
// Update our attributes
this.assignAttributes(this.domNodes[0]);
}
return this.refreshChildren(changedTiddlers) || hasChangedAttributes;
};
/*
Remove any DOM nodes created by this widget or its children
*/
ElementWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.element = ElementWidget;
})();

View File

@ -0,0 +1,85 @@
/*\
title: $:/core/modules/new_widgets/encrypt.js
type: application/javascript
module-type: new_widget
Encrypt widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var EncryptWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
EncryptWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
EncryptWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
var textNode = this.document.createTextNode(this.encryptedText);
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
};
/*
Compute the internal state of the widget
*/
EncryptWidget.prototype.execute = function() {
// Get parameters from our attributes
this.filter = this.getAttribute("filter","[!is[system]]");
// Encrypt the filtered tiddlers
var tiddlers = this.wiki.filterTiddlers(this.filter),
json = {},
self = this;
$tw.utils.each(tiddlers,function(title) {
var tiddler = self.wiki.getTiddler(title),
jsonTiddler = {};
for(var f in tiddler.fields) {
jsonTiddler[f] = tiddler.getFieldString(f);
}
json[title] = jsonTiddler;
});
this.encryptedText = $tw.utils.htmlEncode($tw.crypto.encrypt(JSON.stringify(json)));
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
EncryptWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(),
affectedTiddlers = this.wiki.filterTiddlers(this.filter,null,changedTiddlers);
if(changedAttributes.filter || affectedTiddlers.length > 0) {
this.refreshSelf();
return true;
} else {
return false;
}
};
/*
Remove any DOM nodes created by this widget
*/
EncryptWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.encrypt = EncryptWidget;
})();

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,110 @@
/*\
title: $:/core/modules/new_widgets/fieldmangler.js
type: application/javascript
module-type: new_widget
Field mangler widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var FieldManglerWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
this.addEventListeners([
{type: "tw-remove-field", handler: "handleRemoveFieldEvent"},
{type: "tw-add-field", handler: "handleAddFieldEvent"},
{type: "tw-remove-tag", handler: "handleRemoveTagEvent"},
{type: "tw-add-tag", handler: "handleAddTagEvent"}
]);
};
/*
Inherit from the base widget class
*/
FieldManglerWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
FieldManglerWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
FieldManglerWidget.prototype.execute = function() {
// Get our parameters
this.mangleTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
// Construct the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
FieldManglerWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
FieldManglerWidget.prototype.handleRemoveFieldEvent = function(event) {
var tiddler = this.wiki.getTiddler(this.mangleTitle),
deletion = {};
deletion[event.param] = undefined;
this.wiki.addTiddler(new $tw.Tiddler(tiddler,deletion));
return true;
};
FieldManglerWidget.prototype.handleAddFieldEvent = function(event) {
var tiddler = this.wiki.getTiddler(this.mangleTitle);
if(tiddler && typeof event.param === "string" && event.param !== "" && !$tw.utils.hop(tiddler.fields,event.param)) {
var addition = {};
addition[event.param] = "";
this.wiki.addTiddler(new $tw.Tiddler(tiddler,addition));
}
return true;
};
FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) {
var tiddler = this.wiki.getTiddler(this.mangleTitle);
if(tiddler && tiddler.fields.tags) {
var p = tiddler.fields.tags.indexOf(event.param);
if(p !== -1) {
var modification = {};
modification.tags = (tiddler.fields.tags || []).slice(0);
modification.tags.splice(p,1);
this.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
}
}
return true;
};
FieldManglerWidget.prototype.handleAddTagEvent = function(event) {
var tiddler = this.wiki.getTiddler(this.mangleTitle);
if(tiddler && typeof event.param === "string" && event.param !== "") {
var modification = {};
modification.tags = (tiddler.fields.tags || []).slice(0);
$tw.utils.pushTop(modification.tags,event.param);
this.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
}
return true;
};
exports.fieldmangler = FieldManglerWidget;
})();

View File

@ -0,0 +1,113 @@
/*\
title: $:/core/modules/new_widgets/fields.js
type: application/javascript
module-type: new_widget
Fields widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var FieldsWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
FieldsWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
FieldsWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
var textNode = this.document.createTextNode(this.text);
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
};
/*
Compute the internal state of the widget
*/
FieldsWidget.prototype.execute = function() {
// Get parameters from our attributes
this.tiddlerTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.template = this.getAttribute("template");
this.exclude = this.getAttribute("exclude");
this.stripTitlePrefix = this.getAttribute("stripTitlePrefix","no") === "yes";
// Get the value to display
var tiddler = this.wiki.getTiddler(this.tiddlerTitle);
// Get the exclusion list
var exclude;
if(this.exclude) {
exclude = this.exclude.split(" ");
} else {
exclude = ["text"];
}
// Compose the template
var text = [];
if(this.template && tiddler) {
var fields = [];
for(var fieldName in tiddler.fields) {
if(exclude.indexOf(fieldName) === -1) {
fields.push(fieldName);
}
}
fields.sort();
for(var f=0; f<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;
})();

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

@ -0,0 +1,184 @@
/*\
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) {
// Save the parent dom node
this.parentDomNode = parent;
// Compute our attributes
this.computeAttributes();
// Execute our logic
this.execute();
// Get the value of the tw-wikilinks configuration macro
var wikiLinksMacro = this.getVariable("tw-wikilinks"),
useWikiLinks = wikiLinksMacro ? !(wikiLinksMacro.trim() === "no") : true;
// Render the link if required
if(useWikiLinks) {
this.renderLink(parent,nextSibling);
} else {
// Just insert the link text
var domNode = this.document.createElement("span");
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
}
};
/*
Render this widget into the DOM
*/
LinkWidget.prototype.renderLink = function(parent,nextSibling) {
var self = this;
// 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
var wikiLinkTemplateMacro = this.getVariable("tw-wikilink-template"),
wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$",
wikiLinkText = wikiLinkTemplate.replace("$uri_encoded$",encodeURIComponent(this.to));
wikiLinkText = wikiLinkText.replace("$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
domNode.setAttribute("href",wikiLinkText);
// Add a click event handler
$tw.utils.addEventListeners(domNode,[
{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"},
{name: "dragstart", handlerObject: this, handlerMethod: "handleDragStartEvent"},
{name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"}
]);
// Insert the link into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
LinkWidget.prototype.handleClickEvent = function (event) {
// Send the click on it's way as a navigate event
var bounds = this.domNodes[0].getBoundingClientRect();
this.dispatchEvent({
type: "tw-navigate",
navigateTo: this.to,
navigateFromTitle: this.getVariable("currentTiddler"),
navigateFromNode: this,
navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height
}
});
event.preventDefault();
event.stopPropagation();
return false;
};
LinkWidget.prototype.handleDragStartEvent = function(event) {
if(this.to) {
// Set the dragging class on the element being dragged
$tw.utils.addClass(event.target,"tw-tiddlylink-dragging");
// Create the drag image elements
this.dragImage = this.document.createElement("div");
this.dragImage.className = "tw-tiddler-dragger";
var inner = this.document.createElement("div");
inner.className = "tw-tiddler-dragger-inner";
inner.appendChild(this.document.createTextNode(this.to));
this.dragImage.appendChild(inner);
this.document.body.appendChild(this.dragImage);
// Astoundingly, we need to cover the dragger up: http://www.kryogenix.org/code/browser/custom-drag-image.html
var bounds = this.dragImage.firstChild.getBoundingClientRect(),
cover = this.document.createElement("div");
cover.className = "tw-tiddler-dragger-cover";
cover.style.left = (bounds.left - 16) + "px";
cover.style.top = (bounds.top - 16) + "px";
cover.style.width = (bounds.width + 32) + "px";
cover.style.height = (bounds.height + 32) + "px";
this.dragImage.appendChild(cover);
// Set the data transfer properties
var dataTransfer = event.dataTransfer;
dataTransfer.effectAllowed = "copy";
dataTransfer.setDragImage(this.dragImage.firstChild,-16,-16);
dataTransfer.clearData();
dataTransfer.setData("text/vnd.tiddler",this.wiki.getTiddlerAsJson(this.to));
dataTransfer.setData("text/plain",this.wiki.getTiddlerText(this.to,""));
event.stopPropagation();
} else {
event.preventDefault();
}
};
LinkWidget.prototype.handleDragEndEvent = function(event) {
// Remove the dragging class on the element being dragged
$tw.utils.removeClass(event.target,"tw-tiddlylink-dragging");
// Delete the drag image element
if(this.dragImage) {
this.dragImage.parentNode.removeChild(this.dragImage);
}
};
/*
Compute the internal state of the widget
*/
LinkWidget.prototype.execute = function() {
// Get the target tiddler title
this.to = this.getAttribute("to",this.getVariable("currentTiddler"));
// Determine the link characteristics
this.isMissing = !this.wiki.tiddlerExists(this.to);
this.isShadow = this.wiki.isShadowTiddler(this.to);
// 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;
})();

View File

@ -0,0 +1,87 @@
/*\
title: $:/core/modules/new_widgets/linkcatcher.js
type: application/javascript
module-type: new_widget
Linkcatcher widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var LinkCatcherWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
this.addEventListeners([
{type: "tw-navigate", handler: "handleNavigateEvent"}
]);
};
/*
Inherit from the base widget class
*/
LinkCatcherWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
LinkCatcherWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
LinkCatcherWidget.prototype.execute = function() {
// Get our parameters
this.catchTo = this.getAttribute("to");
this.catchMessage = this.getAttribute("message");
this.catchSet = this.getAttribute("set");
this.catchSetTo = this.getAttribute("setTo");
// Construct the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
LinkCatcherWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.to || changedAttributes.message || changedAttributes.set || changedAttributes.setTo) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
/*
Handle a tw-navigate event
*/
LinkCatcherWidget.prototype.handleNavigateEvent = function(event) {
if(this.catchTo) {
this.wiki.setTextReference(this.catchTo,event.navigateTo,this.getVariable("currentTiddler"));
}
if(this.catchMessage) {
this.dispatchEvent({
type: this.catchMessage,
param: event.navigateTo
});
}
if(this.catchSet) {
var tiddler = this.wiki.getTiddler(this.catchSet);
this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: this.catchSet, text: this.catchSetTo}));
}
return false;
};
exports.linkcatcher = LinkCatcherWidget;
})();

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

@ -0,0 +1,305 @@
/*\
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) {
// Initialise the storyviews if they've not been done already
if(!this.storyViews) {
ListWidget.prototype.storyViews = {};
$tw.modules.applyMethods("storyview",this.storyViews);
}
// Main initialisation inherited from widget.js
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);
// Construct the storyview
var StoryView = this.storyViews[this.storyViewName];
this.storyview = StoryView ? new StoryView(this) : null;
};
/*
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.variableName = this.getAttribute("variable","currentTiddler");
this.storyViewName = this.getAttribute("storyview");
this.historyTitle = this.getAttribute("history");
// 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);
// Clear the last history
this.history = [];
};
ListWidget.prototype.getTiddlerList = function() {
var defaultFilter = "[!is[system]sort[title]]";
return this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this.getVariable("currentTiddler"));
};
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(template) {
templateTree = [{type: "transclude", attributes: {tiddler: {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: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [
{type: "text", text: title}
]}]}];
}
}
// Return the list item
return {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree};
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ListWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
// Completely refresh if any of our attributes have changed
if(changedAttributes.filter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) {
this.refreshSelf();
return true;
} else {
// Handle any changes to the list
var hasChanged = this.handleListChanges(changedTiddlers);
// Handle any changes to the history stack
if(this.historyTitle && changedTiddlers[this.historyTitle]) {
this.handleHistoryChanges();
}
return hasChanged;
}
};
/*
Handle any changes to the history list
*/
ListWidget.prototype.handleHistoryChanges = function() {
// Get the history data
var newHistory = this.wiki.getTiddlerData(this.historyTitle,[]);
// Ignore any entries of the history that match the previous history
var entry = 0;
while(entry < newHistory.length && entry < this.history.length && newHistory[entry].title === this.history[entry].title) {
entry++;
}
// Navigate forwards to each of the new tiddlers
while(entry < newHistory.length) {
if(this.storyview && this.storyview.navigateTo) {
this.storyview.navigateTo(newHistory[entry]);
}
entry++;
}
// Update the history
this.history = newHistory;
};
/*
Process any changes to the list
*/
ListWidget.prototype.handleListChanges = function(changedTiddlers) {
// Get the new list
var prevList = this.list;
this.list = this.getTiddlerList();
// Check for an empty list
if(this.list.length === 0) {
// Check if it was empty before
if(prevList.length === 0) {
// If so, just refresh the empty message
return this.refreshChildren(changedTiddlers);
} else {
// Replace the previous content with the empty message
for(t=this.children.length-1; t>=0; t--) {
this.removeListItem(t);
}
var nextSibling = this.findNextSiblingDomNode();
this.makeChildWidgets(this.getEmptyMessage());
this.renderChildren(this.parentDomNode,nextSibling);
return true;
}
} else {
// If the list was empty then we need to remove the empty message
if(prevList.length === 0) {
this.removeChildDomNodes();
this.children = [];
}
// Cycle through the list, inserting and removing list items as needed
var hasRefreshed = false;
for(var t=0; t<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) {
// Create, insert and render the new child widgets
var widget = this.makeChildWidget(this.makeItemTemplate(title));
widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work
this.children.splice(index,0,widget);
var nextSibling = widget.findNextSiblingDomNode();
widget.render(this.parentDomNode,nextSibling);
// Animate the insertion if required
if(this.storyview && this.storyview.insert) {
this.storyview.insert(widget);
}
return true;
};
/*
Remove the specified list item
*/
ListWidget.prototype.removeListItem = function(index) {
var widget = this.children[index];
// Animate the removal if required
if(this.storyview && this.storyview.remove) {
this.storyview.remove(widget);
} else {
widget.removeChildDomNodes();
}
// Remove the child widget
this.children.splice(index,1);
};
exports.list = ListWidget;
var ListItemWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
ListItemWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
ListItemWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
ListItemWidget.prototype.execute = function() {
// Set the current list item title
this.setVariable(this.parseTreeNode.variableName,this.parseTreeNode.itemTitle);
// Construct the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ListItemWidget.prototype.refresh = function(changedTiddlers) {
return this.refreshChildren(changedTiddlers);
};
exports.listitem = ListItemWidget;
})();

View File

@ -0,0 +1,84 @@
/*\
title: $:/core/modules/new_widgets/macrocall.js
type: application/javascript
module-type: new_widget
Macrocall widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var MacroCallWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
MacroCallWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
MacroCallWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
MacroCallWidget.prototype.execute = function() {
// Get the parse type if specified
this.parseType = this.getAttribute("$type","text/vnd.tiddlywiki");
this.renderOutput = this.getAttribute("$output","text/html");
// Merge together the parameters specified in the parse tree with the specified attributes
var params = this.parseTreeNode.params ? this.parseTreeNode.params.slice(0) : [];
$tw.utils.each(this.attributes,function(attribute,name) {
if(name.charAt(0) !== "$") {
params.push({name: name, value: attribute});
}
});
// Get the macro value
var text = this.getVariable(this.parseTreeNode.name || this.getAttribute("$name"),{params: params}),
parseTreeNodes;
// Are we rendering to HTML?
if(this.renderOutput === "text/html") {
// If so we'll return the parsed macro
var parser = this.wiki.new_parseText(this.parseType,text,
{parseAsInline: !this.parseTreeNode.isBlock});
parseTreeNodes = parser ? parser.tree : [];
} else {
// Otherwise, we'll render the text
var plainText = this.wiki.new_renderText("text/plain",this.parseType,text,{parentWidget: this});
parseTreeNodes = [{type: "text", text: plainText}];
}
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
MacroCallWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) > 0) {
// Rerender ourselves
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
exports.macrocall = MacroCallWidget;
})();

View File

@ -1,9 +1,9 @@
/*\
title: $:/core/modules/widgets/navigator.js
title: $:/core/modules/new_widgets/navigator.js
type: application/javascript
module-type: widget
module-type: new_widget
Implements the navigator widget.
Navigator widget
\*/
(function(){
@ -12,51 +12,69 @@ Implements the navigator widget.
/*global $tw: false */
"use strict";
var NavigatorWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var NavigatorWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
this.addEventListeners([
{type: "tw-navigate", handler: "handleNavigateEvent"},
{type: "tw-edit-tiddler", handler: "handleEditTiddlerEvent"},
{type: "tw-delete-tiddler", handler: "handleDeleteTiddlerEvent"},
{type: "tw-save-tiddler", handler: "handleSaveTiddlerEvent"},
{type: "tw-cancel-tiddler", handler: "handleCancelTiddlerEvent"},
{type: "tw-close-tiddler", handler: "handleCloseTiddlerEvent"},
{type: "tw-close-all-tiddlers", handler: "handleCloseAllTiddlersEvent"},
{type: "tw-new-tiddler", handler: "handleNewTiddlerEvent"},
{type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"},
]);
};
NavigatorWidget.prototype.generate = function() {
/*
Inherit from the base widget class
*/
NavigatorWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
NavigatorWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
NavigatorWidget.prototype.execute = function() {
// Get our parameters
this.storyTitle = this.renderer.getAttribute("story");
this.historyTitle = this.renderer.getAttribute("history");
// Set the element
this.tag = "div";
this.attributes = {
"class": "tw-navigator"
};
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
this.events = [
{name: "tw-navigate", handlerObject: this, handlerMethod: "handleNavigateEvent"},
{name: "tw-edit-tiddler", handlerObject: this, handlerMethod: "handleEditTiddlerEvent"},
{name: "tw-delete-tiddler", handlerObject: this, handlerMethod: "handleDeleteTiddlerEvent"},
{name: "tw-save-tiddler", handlerObject: this, handlerMethod: "handleSaveTiddlerEvent"},
{name: "tw-cancel-tiddler", handlerObject: this, handlerMethod: "handleCancelTiddlerEvent"},
{name: "tw-close-tiddler", handlerObject: this, handlerMethod: "handleCloseTiddlerEvent"},
{name: "tw-close-all-tiddlers", handlerObject: this, handlerMethod: "handleCloseAllTiddlersEvent"},
{name: "tw-new-tiddler", handlerObject: this, handlerMethod: "handleNewTiddlerEvent"}
];
this.storyTitle = this.getAttribute("story");
this.historyTitle = this.getAttribute("history");
// Construct the child widgets
this.makeChildWidgets();
};
NavigatorWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
NavigatorWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.story || changedAttributes.history) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
NavigatorWidget.prototype.getStoryList = function() {
this.storyList = this.renderer.renderTree.wiki.getTiddlerList(this.storyTitle);
this.storyList = this.wiki.getTiddlerList(this.storyTitle);
};
NavigatorWidget.prototype.saveStoryList = function() {
var storyTiddler = this.renderer.renderTree.wiki.getTiddler(this.storyTitle);
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler({
var storyTiddler = this.wiki.getTiddler(this.storyTitle);
this.wiki.addTiddler(new $tw.Tiddler({
title: this.storyTitle
},storyTiddler,{list: this.storyList}));
};
@ -70,7 +88,9 @@ NavigatorWidget.prototype.findTitleInStory = function(title,defaultIndex) {
return defaultIndex;
};
// Navigate to a specified tiddler
/*
Handle a tw-navigate event
*/
NavigatorWidget.prototype.handleNavigateEvent = function(event) {
if(this.storyTitle) {
// Update the story tiddler if specified
@ -89,11 +109,10 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) {
}
// Add a new record to the top of the history stack
if(this.historyTitle) {
var historyList = this.renderer.renderTree.wiki.getTiddlerData(this.historyTitle,[]);
var historyList = this.wiki.getTiddlerData(this.historyTitle,[]);
historyList.push({title: event.navigateTo, fromPageRect: event.navigateFromClientRect});
this.renderer.renderTree.wiki.setTiddlerData(this.historyTitle,historyList);
this.wiki.setTiddlerData(this.historyTitle,historyList);
}
event.stopPropagation();
return false;
};
@ -106,7 +125,6 @@ NavigatorWidget.prototype.handleCloseTiddlerEvent = function(event) {
this.storyList.splice(slot,1);
this.saveStoryList();
}
event.stopPropagation();
return false;
};
@ -114,7 +132,6 @@ NavigatorWidget.prototype.handleCloseTiddlerEvent = function(event) {
NavigatorWidget.prototype.handleCloseAllTiddlersEvent = function(event) {
this.storyList = [];
this.saveStoryList();
event.stopPropagation();
return false;
};
@ -122,44 +139,37 @@ NavigatorWidget.prototype.handleCloseAllTiddlersEvent = function(event) {
NavigatorWidget.prototype.handleEditTiddlerEvent = function(event) {
this.getStoryList();
// Replace the specified tiddler with a draft in edit mode
for(var t=0; t<this.storyList.length; t++) {
var draftTiddler = this.getDraftTiddler(event.tiddlerTitle),
gotOne = false;
for(var t=this.storyList.length-1; t>=0; t--) {
// Replace the first story instance of the original tiddler name with the draft title
if(this.storyList[t] === event.tiddlerTitle) {
// Compute the title for the draft
var draftTitle = this.generateDraftTitle(event.tiddlerTitle);
this.storyList[t] = draftTitle;
// Get the current value of the tiddler we're editing
var tiddler = this.renderer.renderTree.wiki.getTiddler(event.tiddlerTitle);
// Save the initial value of the draft tiddler
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(
{
text: "Type the text for the tiddler '" + event.tiddlerTitle + "'"
},
tiddler,
{
title: draftTitle,
"draft.title": event.tiddlerTitle,
"draft.of": event.tiddlerTitle
},
this.renderer.renderTree.wiki.getModificationFields()
));
if(!gotOne) {
this.storyList[t] = draftTiddler.fields.title;
gotOne = true;
} else {
this.storyList.splice(t,1);
}
} else if(this.storyList[t] === draftTiddler.fields.title) {
// Remove any existing references to the draft
this.storyList.splice(t,1);
}
}
this.saveStoryList();
event.stopPropagation();
return false;
};
// Delete a tiddler
NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) {
// Get the tiddler title we're deleting
var tiddler = this.renderer.renderTree.wiki.getTiddler(event.tiddlerTitle);
// Get the tiddler we're deleting
var tiddler = this.wiki.getTiddler(event.tiddlerTitle);
// Check if the tiddler we're deleting is in draft mode
if(tiddler.hasField("draft.title")) {
// Delete the original tiddler
this.renderer.renderTree.wiki.deleteTiddler(tiddler.fields["draft.of"]);
this.wiki.deleteTiddler(tiddler.fields["draft.of"]);
}
// Delete this tiddler
this.renderer.renderTree.wiki.deleteTiddler(event.tiddlerTitle);
this.wiki.deleteTiddler(event.tiddlerTitle);
// Remove the closed tiddler from the story
this.getStoryList();
// Look for tiddler with this title to close
@ -168,10 +178,41 @@ NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) {
this.storyList.splice(slot,1);
this.saveStoryList();
}
event.stopPropagation();
return false;
};
/*
Create/reuse the draft tiddler for a given title
*/
NavigatorWidget.prototype.getDraftTiddler = function(targetTitle) {
// See if there is already a draft tiddler for this tiddler
var drafts = [];
this.wiki.forEachTiddler(function(title,tiddler) {
if(tiddler.fields["draft.title"] && tiddler.fields["draft.of"] === targetTitle) {
drafts.push(tiddler);
}
});
if(drafts.length > 0) {
return drafts[0];
}
// Get the current value of the tiddler we're editing
var tiddler = this.wiki.getTiddler(targetTitle),
draftTitle = this.generateDraftTitle(targetTitle);
// Save the initial value of the draft tiddler
var draftTiddler = new $tw.Tiddler(
{text: "Type the text for the tiddler '" + targetTitle + "'"},
tiddler,
{
title: draftTitle,
"draft.title": targetTitle,
"draft.of": targetTitle
},
this.wiki.getModificationFields()
);
this.wiki.addTiddler(draftTiddler);
return draftTiddler;
};
/*
Generate a title for the draft of a given tiddler
*/
@ -180,7 +221,7 @@ NavigatorWidget.prototype.generateDraftTitle = function(title) {
do {
var draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'";
c++;
} while(this.renderer.renderTree.wiki.tiddlerExists(draftTitle));
} while(this.wiki.tiddlerExists(draftTitle));
return draftTitle;
};
@ -190,28 +231,28 @@ NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) {
var storyTiddlerModified = false; // We have to special case saving the story tiddler itself
for(var t=0; t<this.storyList.length; t++) {
if(this.storyList[t] === event.tiddlerTitle) {
var tiddler = this.renderer.renderTree.wiki.getTiddler(event.tiddlerTitle);
var tiddler = this.wiki.getTiddler(event.tiddlerTitle);
if(tiddler) {
var draftTitle = tiddler.fields["draft.title"],
draftOf = tiddler.fields["draft.of"];
if(draftTitle) {
var isRename = draftOf !== draftTitle,
isConfirmed = true;
if(isRename && this.renderer.renderTree.wiki.tiddlerExists(draftTitle)) {
if(isRename && this.wiki.tiddlerExists(draftTitle)) {
isConfirmed = confirm("Do you wish to overwrite the tiddler '" + draftTitle + "'?");
}
if(isConfirmed) {
// Save the draft tiddler as the real tiddler
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(this.renderer.renderTree.wiki.getCreationFields(),tiddler,{
this.wiki.addTiddler(new $tw.Tiddler(this.wiki.getCreationFields(),tiddler,{
title: draftTitle,
"draft.title": undefined,
"draft.of": undefined
},this.renderer.renderTree.wiki.getModificationFields()));
},this.wiki.getModificationFields()));
// Remove the draft tiddler
this.renderer.renderTree.wiki.deleteTiddler(event.tiddlerTitle);
this.wiki.deleteTiddler(event.tiddlerTitle);
// Remove the original tiddler if we're renaming it
if(isRename) {
this.renderer.renderTree.wiki.deleteTiddler(draftOf);
this.wiki.deleteTiddler(draftOf);
}
// Make the story record point to the newly saved tiddler
this.storyList[t] = draftTitle;
@ -227,7 +268,6 @@ NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) {
if(!storyTiddlerModified) {
this.saveStoryList();
}
event.stopPropagation();
return false;
};
@ -237,10 +277,10 @@ NavigatorWidget.prototype.handleCancelTiddlerEvent = function(event) {
var storyTiddlerModified = false;
for(var t=0; t<this.storyList.length; t++) {
if(this.storyList[t] === event.tiddlerTitle) {
var tiddler = this.renderer.renderTree.wiki.getTiddler(event.tiddlerTitle);
if(tiddler.hasField("draft.title")) {
var tiddler = this.wiki.getTiddler(event.tiddlerTitle);
if(tiddler && tiddler.hasField("draft.title")) {
// Remove the draft tiddler
this.renderer.renderTree.wiki.deleteTiddler(event.tiddlerTitle);
this.wiki.deleteTiddler(event.tiddlerTitle);
// Make the story record point to the original tiddler
this.storyList[t] = tiddler.fields["draft.title"];
// Check if we're modifying the story tiddler itself
@ -253,7 +293,6 @@ NavigatorWidget.prototype.handleCancelTiddlerEvent = function(event) {
if(!storyTiddlerModified) {
this.saveStoryList();
}
event.stopPropagation();
return false;
};
@ -265,15 +304,15 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
var title;
for(var t=0; true; t++) {
title = "New Tiddler" + (t ? " " + t : "");
if(!this.renderer.renderTree.wiki.tiddlerExists(title)) {
if(!this.wiki.tiddlerExists(title)) {
break;
}
}
var tiddler = new $tw.Tiddler(this.renderer.renderTree.wiki.getCreationFields(),{
var tiddler = new $tw.Tiddler(this.wiki.getCreationFields(),{
title: title,
text: "Newly created tiddler"
},this.renderer.renderTree.wiki.getModificationFields());
this.renderer.renderTree.wiki.addTiddler(tiddler);
},this.wiki.getModificationFields());
this.wiki.addTiddler(tiddler);
// Create the draft tiddler
var draftTitle = this.generateDraftTitle(title),
draftTiddler = new $tw.Tiddler({
@ -281,18 +320,53 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
title: draftTitle,
"draft.title": title,
"draft.of": title
},this.renderer.renderTree.wiki.getModificationFields());
this.renderer.renderTree.wiki.addTiddler(draftTiddler);
},this.wiki.getModificationFields());
this.wiki.addTiddler(draftTiddler);
// Update the story to insert the new draft at the top
var slot = this.findTitleInStory(event.navigateFromTitle,-1) + 1;
this.storyList.splice(slot,0,draftTitle);
// Save the updated story
this.saveStoryList();
// Add a new record to the top of the history stack
var history = this.renderer.renderTree.wiki.getTiddlerData(this.historyTitle,[]);
var history = this.wiki.getTiddlerData(this.historyTitle,[]);
history.push({title: draftTitle});
this.renderer.renderTree.wiki.setTiddlerData(this.historyTitle,history);
event.stopPropagation();
this.wiki.setTiddlerData(this.historyTitle,history);
return false;
};
// Import JSON tiddlers
NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
var self = this;
// Get the story and history details
this.getStoryList();
var history = this.wiki.getTiddlerData(this.historyTitle,[]);
// Get the tiddlers
var tiddlers = [];
try {
tiddlers = JSON.parse(event.param);
} catch(e) {
}
// Process each tiddler
$tw.utils.each(tiddlers,function(tiddlerFields) {
// Generate a unique title for the tiddler
var title = self.wiki.generateNewTitle(tiddlerFields.title);
// Add it to the store
self.wiki.addTiddler(new $tw.Tiddler(
self.wiki.getCreationFields(),
tiddlerFields,
self.wiki.getModificationFields(),
{title: title}
));
// Add it to the story
if(self.storyList.indexOf(title) === -1) {
self.storyList.unshift(title);
}
// And to history
history.push({title: title});
});
// Save the updated story and history
this.saveStoryList();
this.wiki.setTiddlerData(this.historyTitle,history);
return false;
};

View File

@ -0,0 +1,92 @@
/*\
title: $:/core/modules/new_widgets/password.js
type: application/javascript
module-type: new_widget
Password widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
var PasswordWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
PasswordWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
PasswordWidget.prototype.render = function(parent,nextSibling) {
// Save the parent dom node
this.parentDomNode = parent;
// Compute our attributes
this.computeAttributes();
// Execute our logic
this.execute();
// Get the current password
var password = $tw.browser ? $tw.utils.getPassword(this.passwordName) : "";
// Create our element
var domNode = this.document.createElement("input");
domNode.setAttribute("type","password");
domNode.setAttribute("value",password);
// Add a click event handler
$tw.utils.addEventListeners(domNode,[
{name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"}
]);
// Insert the label into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
PasswordWidget.prototype.handleChangeEvent = function(event) {
var password = this.domNodes[0].value;
return $tw.utils.savePassword(this.passwordName,password);
};
/*
Compute the internal state of the widget
*/
PasswordWidget.prototype.execute = function() {
// Get the parameters from the attributes
this.passwordName = this.getAttribute("name","");
// Make the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
PasswordWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.name) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
/*
Remove any DOM nodes created by this widget or its children
*/
PasswordWidget.prototype.removeChildDomNodes = function() {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
};
exports.password = PasswordWidget;
})();

View File

@ -0,0 +1,205 @@
/*\
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(this.parseTreeNode.isBlock ? "div" : "span");
var classes = this["class"].split(" ") || [];
classes.push("tw-reveal");
domNode.className = classes.join(" ");
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) {
this.positionPopup(domNode);
}
if(!this.isOpen) {
domNode.setAttribute("hidden","true")
}
this.domNodes.push(domNode);
};
RevealWidget.prototype.positionPopup = function(domNode) {
domNode.style.position = "absolute";
domNode.style.zIndex = "1000";
switch(this.position) {
case "left":
domNode.style.left = (this.popup.left - domNode.offsetWidth) + "px";
domNode.style.top = this.popup.top + "px";
break;
case "above":
domNode.style.left = this.popup.left + "px";
domNode.style.top = (this.popup.top - domNode.offsetHeight) + "px";
break;
case "aboveright":
domNode.style.left = (this.popup.left + this.popup.width) + "px";
domNode.style.top = (this.popup.top + this.popup.height - domNode.offsetHeight) + "px";
break;
case "right":
domNode.style.left = (this.popup.left + this.popup.width) + "px";
domNode.style.top = this.popup.top + "px";
break;
case "belowleft":
domNode.style.left = (this.popup.left + this.popup.width - domNode.offsetWidth) + "px";
domNode.style.top = (this.popup.top + this.popup.height) + "px";
break;
default: // Below
domNode.style.left = this.popup.left + "px";
domNode.style.top = (this.popup.top + this.popup.height) + "px";
break;
}
};
/*
Compute the internal state of the widget
*/
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["class"] = this.getAttribute("class","");
this["default"] = this.getAttribute("default","");
this.animate = this.getAttribute("animate","no");
this.openAnimation = this.animate === "no" ? undefined : "open";
this.closeAnimation = this.animate === "no" ? undefined : "close";
// Compute the title of the state tiddler and read it
this.stateTitle = this.state;
this.readState();
// Construct the child widgets
var childNodes = this.isOpen ? this.parseTreeNode.children : [];
this.hasChildNodes = this.isOpen;
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("currentTiddler"));
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.animate) {
this.refreshSelf();
return true;
} else {
var refreshed = false;
if(changedTiddlers[this.stateTitle]) {
this.updateState();
refreshed = true;
}
return this.refreshChildren(changedTiddlers) || refreshed;
}
};
/*
Called by refresh() to dynamically show or hide the content
*/
RevealWidget.prototype.updateState = function() {
// Read the current state
this.readState();
// Construct the child nodes if needed
var domNode = this.domNodes[0];
if(this.isOpen && !this.hasChildNodes) {
this.hasChildNodes = true;
this.makeChildWidgets(this.parseTreeNode.children);
this.renderChildren(domNode,null);
}
// Animate our DOM node
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) {
this.positionPopup(domNode);
}
if(this.isOpen) {
domNode.removeAttribute("hidden");
$tw.anim.perform(this.openAnimation,domNode);
} else {
$tw.anim.perform(this.closeAnimation,domNode,{callback: function() {
domNode.setAttribute("hidden","true");
}});
}
};
/*
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","currentTiddler");
this.setValue = this.getAttribute("value");
// Set context variable
this.setVariable(this.setName,this.setValue,this.parseTreeNode.params);
// Construct the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
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,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,66 @@
/*\
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);
};
/*
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("tiddler",this.getVariable("currentTiddler"));
// Set context variables
this.setVariable("currentTiddler",this.tiddlerTitle);
this.setVariable("missingTiddlerClass",(this.wiki.tiddlerExists(this.tiddlerTitle) || this.wiki.isShadowTiddler(this.tiddlerTitle)) ? "tw-tiddler-exists" : "tw-tiddler-missing");
this.setVariable("shadowTiddlerClass",this.wiki.isShadowTiddler(this.tiddlerTitle) ? "tw-tiddler-shadow" : "");
this.setVariable("systemTiddlerClass",this.wiki.isSystemTiddler(this.tiddlerTitle) ? "tw-tiddler-system" : "");
// Construct the child widgets
this.makeChildWidgets();
};
/*
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.tiddler) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
exports.tiddler = TiddlerWidget;
})();

View File

@ -0,0 +1,95 @@
/*\
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("tiddler",this.getVariable("currentTiddler"));
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.getVariable("currentTiddler",{defaultValue: ""}));
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.tiddler || changedAttributes.field || changedAttributes.index || changedTiddlers[this.transcludeTitle]) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
exports.transclude = TranscludeWidget;
})();

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

@ -0,0 +1,185 @@
/*\
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("tiddler",this.getVariable("currentTiddler"));
this.viewField = this.getAttribute("field","text");
this.viewIndex = this.getAttribute("index");
this.viewFormat = this.getAttribute("format","text");
this.viewTemplate = this.getAttribute("template","");
switch(this.viewFormat) {
case "htmlwikified":
this.text = this.getValueAsHtmlWikified();
break;
case "htmlencoded":
this.text = this.getValueAsHtmlEncoded();
break;
case "date":
this.text = this.getValueAsDate(this.viewTemplate);
break;
case "relativedate":
this.text = this.getValueAsRelativeDate();
break;
case "stripcomments":
this.text = this.getValueAsStrippedComments();
break;
case "jsencoded":
this.text = this.getValueAsJsEncoded();
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.getValue = function() {
// Get the value to display
var value,
tiddler = this.wiki.getTiddler(this.viewTitle);
if(tiddler) {
if(this.viewField === "text") {
// Calling getTiddlerText() triggers lazy loading of skinny tiddlers
value = this.wiki.getTiddlerText(this.viewTitle);
} else {
if($tw.utils.hop(tiddler.fields,this.viewField)) {
value = tiddler.fields[this.viewField];
} else {
value = "";
}
}
} else {
if(this.viewField === "title") {
value = this.viewTitle;
} else {
value = undefined;
}
}
return value;
};
ViewWidget.prototype.getValueAsText = function() {
// Get the value to display
var text,
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.getFieldString(this.viewField);
}
} else { // Use a special value if the tiddler is missing
if(this.viewField === "title") {
text = this.viewTitle;
} else {
text = "";
}
}
return text;
};
ViewWidget.prototype.getValueAsHtmlWikified = function() {
return this.wiki.new_renderText("text/html","text/vnd.tiddlywiki",this.getValueAsText(),this);
};
ViewWidget.prototype.getValueAsHtmlEncoded = function() {
return $tw.utils.htmlEncode(this.getValueAsText());
};
ViewWidget.prototype.getValueAsDate = function(format) {
return $tw.utils.formatDateString(this.getValue(),format);
};
ViewWidget.prototype.getValueAsRelativeDate = function(format) {
var value = this.getValue();
if(value) {
return $tw.utils.getRelativeDate((new Date()) - (new Date(value))).description;
} else {
return "";
}
};
ViewWidget.prototype.getValueAsStrippedComments = function() {
var lines = this.getValueAsText().split("\n"),
out = [];
for(var line=0; line<lines.length; line++) {
var text = lines[line];
if(!/^\s*\/\/#/.test(text)) {
out.push(text);
}
}
return out.join("\n");
};
ViewWidget.prototype.getValueAsJsEncoded = function() {
return $tw.utils.stringify(this.getValueAsText());
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ViewWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.template || changedAttributes.format || 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,460 @@
/*\
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:
currentTiddler: title of the tiddler providing the context
*/
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
options: see below
Options include
params: array of {name:, value:} for each parameter
defaultValue: default value if the variable is not defined
*/
Widget.prototype.getVariable = function(name,options) {
options = options || {};
var actualParams = options.params || [];
// Search up the widget tree for the variable name
var node = this;
while(node && !$tw.utils.hop(node.variables,name)) {
node = node.parentWidget;
}
// If we get to the root then look for a macro module
if(!node) {
return this.evaluateMacroModule(name,actualParams,options.defaultValue);
}
// Get the value
var value = node.variables[name].value || "";
// Substitute any parameters specified in the definition
value = this.substituteVariableParameters(value,node.variables[name].params,actualParams);
value = this.substituteVariableReferences(value);
return value;
};
Widget.prototype.substituteVariableParameters = function(text,formalParams,actualParams) {
if(formalParams) {
var nextAnonParameter = 0, // Next candidate anonymous parameter in macro call
paramInfo, paramValue;
// Step through each of the parameters in the macro definition
for(var p=0; p<formalParams.length; p++) {
// Check if we've got a macro call parameter with the same name
paramInfo = formalParams[p];
paramValue = undefined;
for(var m=0; m<actualParams.length; m++) {
if(actualParams[m].name === paramInfo.name) {
paramValue = actualParams[m].value;
}
}
// If not, use the next available anonymous macro call parameter
while(nextAnonParameter < actualParams.length && actualParams[nextAnonParameter].name) {
nextAnonParameter++;
}
if(paramValue === undefined && nextAnonParameter < actualParams.length) {
paramValue = actualParams[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
text = text.replace(new RegExp("\\$" + $tw.utils.escapeRegExp(paramInfo.name) + "\\$","mg"),paramValue);
}
}
return text;
};
Widget.prototype.substituteVariableReferences = function(text) {
var self = this;
return text.replace(/\$\(([^\)\$]+)\)\$/g,function(match,p1,offset,string) {
return self.getVariable(p1,{defaultValue: ""});
});
};
Widget.prototype.evaluateMacroModule = function(name,actualParams,defaultValue) {
if($tw.utils.hop($tw.macros,name)) {
var macro = $tw.macros[name],
args = [];
var nextAnonParameter = 0, // Next candidate anonymous parameter in macro call
paramInfo, paramValue;
// Step through each of the parameters in the macro definition
for(var p=0; p<macro.params.length; p++) {
// Check if we've got a macro call parameter with the same name
paramInfo = macro.params[p];
paramValue = undefined;
for(var m=0; m<actualParams.length; m++) {
if(actualParams[m].name === paramInfo.name) {
paramValue = actualParams[m].value;
}
}
// If not, use the next available anonymous macro call parameter
while(nextAnonParameter < actualParams.length && actualParams[nextAnonParameter].name) {
nextAnonParameter++;
}
if(paramValue === undefined && nextAnonParameter < actualParams.length) {
paramValue = actualParams[nextAnonParameter++].value;
}
// If we've still not got a value, use the default, if any
paramValue = paramValue || paramInfo["default"] || "";
// Save the parameter
args.push(paramValue);
}
return macro.run.apply(this,args)
} else {
return defaultValue;
}
};
/*
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("currentTiddler"));
} else if(attribute.type === "macro") {
var text = self.getVariable(attribute.value.name,{params: attribute.value.params});
value = self.wiki.new_renderText("text/plain","text/vnd.tiddlywiki",text);
} 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 && 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
});
};
/*
Get the next sibling of this widget
*/
Widget.prototype.nextSibling = function() {
if(this.parentWidget) {
var index = this.parentWidget.children.indexOf(this);
if(index !== -1 && index < this.parentWidget.children.length-1) {
return this.parentWidget.children[index+1];
}
}
return null;
};
/*
Get the previous sibling of this widget
*/
Widget.prototype.previousSibling = function() {
if(this.parentWidget) {
var index = this.parentWidget.children.indexOf(this);
if(index !== -1 && index > 0) {
return this.parentWidget.children[index-1];
}
}
return null;
};
/*
Render the children of this widget into the DOM
*/
Widget.prototype.renderChildren = function(parent,nextSibling) {
$tw.utils.each(this.children,function(childWidget) {
childWidget.render(parent,nextSibling);
});
};
/*
Add a list of event listeners from an array [{type:,handler:},...]
*/
Widget.prototype.addEventListeners = function(listeners) {
var self = this;
$tw.utils.each(listeners,function(listenerInfo) {
self.addEventListener(listenerInfo.type,listenerInfo.handler);
});
};
/*
Add an event listener
*/
Widget.prototype.addEventListener = function(type,handler) {
var self = this;
if(typeof handler === "string") { // The handler is a method name on this widget
this.eventListeners[type] = function(event) {
return self[handler].call(self,event);
};
} else { // The handler is a function
this.eventListeners[type] = function(event) {
return handler.call(self,event);
}
}
};
/*
Dispatch an event to a widget. If the widget doesn't handle the event then it is also dispatched to the parent widget
*/
Widget.prototype.dispatchEvent = function(event) {
// Dispatch the event if this widget handles it
var listener = this.eventListeners[event.type];
if(listener) {
// Don't propagate the event if the listener returned false
if(!listener(event)) {
return false;
}
}
// Dispatch the event to the parent widget
if(this.parentWidget) {
return this.parentWidget.dispatchEvent(event);
}
return true;
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
Widget.prototype.refresh = function(changedTiddlers) {
return this.refreshChildren(changedTiddlers);
};
/*
Rebuild a previously rendered widget
*/
Widget.prototype.refreshSelf = function() {
var nextSibling = this.findNextSiblingDomNode();
this.removeChildDomNodes();
this.render(this.parentDomNode,nextSibling);
};
/*
Refresh all the children of a widget
*/
Widget.prototype.refreshChildren = function(changedTiddlers) {
var self = this,
refreshed = false;
$tw.utils.each(this.children,function(childWidget) {
refreshed = childWidget.refresh(changedTiddlers) || refreshed;
});
return refreshed;
};
/*
Find the next sibling in the DOM to this widget. This is done by scanning the widget tree through all next siblings and their descendents that share the same parent DOM node
*/
Widget.prototype.findNextSiblingDomNode = function(startIndex) {
// Refer to this widget by its index within its parents children
var parent = this.parentWidget,
index = startIndex !== undefined ? startIndex : parent.children.indexOf(this);
if(index === -1) {
throw "node not found in parents children";
}
// Look for a DOM node in the later siblings
while(++index < parent.children.length) {
var domNode = parent.children[index].findFirstDomNode();
if(domNode) {
return domNode;
}
}
// Go back and look for later siblings of our parent if it has the same parent dom node
var grandParent = parent.parentWidget;
if(grandParent && parent.parentDomNode === this.parentDomNode) {
index = grandParent.children.indexOf(parent);
return parent.findNextSiblingDomNode(index);
}
return null;
};
/*
Find the first DOM node generated by a widget or its children
*/
Widget.prototype.findFirstDomNode = function() {
// Return the first dom node of this widget, if we've got one
if(this.domNodes.length > 0) {
return this.domNodes[0];
}
// Otherwise, recursively call our children
for(var t=0; t<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;
})();

View File

@ -38,9 +38,10 @@ exports.parse = function() {
} else {
return [{
type: "element",
tag: "$link",
tag: "a",
attributes: {
to: {type: "string", value: this.match[0]}
href: {type: "string", value: this.match[0]},
target: {type: "string", value: "_blank"}
},
children: [{
type: "text", text: this.match[0]

View File

@ -27,22 +27,41 @@ exports.init = function(parser) {
this.matchRegExp = /\[\[(.*?)(?:\|(.*?))?\]\]/mg;
};
var isLinkExternal = function(to) {
var externalRegExp = /(?:file|http|https|mailto|ftp|irc|news|data|skype):[^\s'"]+(?:\/|\b)/i;
return externalRegExp.test(to);
};
exports.parse = function() {
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;
// Process the link
var text = this.match[1],
link = this.match[2] || text;
return [{
type: "element",
tag: "$link",
attributes: {
to: {type: "string", value: link}
},
children: [{
type: "text", text: text
}]
}];
if(isLinkExternal(link)) {
return [{
type: "element",
tag: "a",
attributes: {
href: {type: "string", value: link},
target: {type: "string", value: "_blank"}
},
children: [{
type: "text", text: text
}]
}];
} else {
return [{
type: "element",
tag: "$link",
attributes: {
to: {type: "string", value: link}
},
children: [{
type: "text", text: text
}]
}];
}
};
})();

View File

@ -47,7 +47,7 @@ exports.parse = function() {
type: "element",
tag: "$transclude",
attributes: {
title: {type: "string", value: template || targetTitle}
tiddler: {type: "string", value: template || targetTitle}
},
isBlock: true
};
@ -55,7 +55,7 @@ exports.parse = function() {
type: "element",
tag: "$tiddler",
attributes: {
title: {type: "string", value: targetTitle}
tiddler: {type: "string", value: targetTitle}
},
isBlock: true,
children: [transcludeNode]

View File

@ -47,14 +47,14 @@ exports.parse = function() {
type: "element",
tag: "$transclude",
attributes: {
title: {type: "string", value: template || targetTitle}
tiddler: {type: "string", value: template || targetTitle}
}
};
var tiddlerNode = {
type: "element",
tag: "$tiddler",
attributes: {
title: {type: "string", value: targetTitle}
tiddler: {type: "string", value: targetTitle}
},
children: [transcludeNode]
};

View File

@ -28,6 +28,8 @@ $$$
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/new_widgets/widget.js");
exports.name = "typedblock";
exports.types = {block: true};
@ -57,16 +59,15 @@ exports.parse = function() {
this.parser.pos = this.parser.sourceLength;
}
// Parse the block according to the specified type
var parser = this.parser.wiki.parseText(parseType,text,{defaultType: "text/plain"});
var parser = this.parser.wiki.new_parseText(parseType,text,{defaultType: "text/plain"});
// If there's no render type, just return the parse tree
if(!renderType) {
return parser.tree;
} else {
// Otherwise, render to the rendertype and return in a <PRE> tag
var renderTree = new $tw.WikiRenderTree(parser,{wiki: $tw.wiki, document: $tw.document});
renderTree.execute();
var container = $tw.document.createElement("div");
renderTree.renderInDom(container);
var widgetNode = this.parser.wiki.makeWidget(parser),
container = $tw.document.createElement("div");
widgetNode.render(container,null);
var text = renderType === "text/html" ? container.innerHTML : container.textContent;
return [{
type: "element",

View File

@ -1,212 +0,0 @@
/*\
title: $:/core/modules/rendertree/renderers/element.js
type: application/javascript
module-type: wikirenderer
Element renderer
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Element widget. A degenerate widget that renders ordinary HTML elements
*/
var ElementWidget = function(renderer) {
this.renderer = renderer;
this.tag = this.renderer.parseTreeNode.tag;
this.attributes = this.renderer.attributes;
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
this.events = this.renderer.parseTreeNode.events;
};
ElementWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attribute dependencies have changed
if($tw.utils.count(changedAttributes) > 0) {
// Update our attributes
this.renderer.assignAttributes();
}
// Refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
};
/*
Element renderer
*/
var ElementRenderer = function(renderTree,parentRenderer,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.parentRenderer = parentRenderer;
this.parseTreeNode = parseTreeNode;
// Initialise widget classes
if(!this.widgetClasses) {
ElementRenderer.prototype.widgetClasses = $tw.modules.applyMethods("widget");
}
// Select the namespace for the tag
var tagNameSpaces = {
svg: "http://www.w3.org/2000/svg",
math: "http://www.w3.org/1998/Math/MathML"
};
this.namespace = tagNameSpaces[this.parseTreeNode.tag];
if(this.namespace) {
this.context = this.context || {};
this.context.namespace = this.namespace;
} else {
this.namespace = this.renderTree.getContextVariable(this.parentRenderer,"namespace","http://www.w3.org/1999/xhtml");
}
// Get the context tiddler title
this.tiddlerTitle = this.renderTree.getContextVariable(this.parentRenderer,"tiddlerTitle");
// Compute our dependencies
this.dependencies = {};
var self = this;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(attribute.type === "indirect") {
var tr = $tw.utils.parseTextReference(attribute.textReference);
self.dependencies[tr.title ? tr.title : self.tiddlerTitle] = true;
}
});
// Compute our attributes
this.attributes = {};
this.computeAttributes();
// Create the parasite widget object if required
if(this.parseTreeNode.tag.charAt(0) === "$") {
// Choose the class
var WidgetClass = this.widgetClasses[this.parseTreeNode.tag.substr(1)];
// Instantiate the widget
if(WidgetClass) {
this.widget = new WidgetClass(this);
} else {
WidgetClass = this.widgetClasses.error;
if(WidgetClass) {
this.widget = new WidgetClass(this,"Unknown widget '<" + this.parseTreeNode.tag + ">'");
}
}
}
// If we haven't got a widget, use the generic HTML element widget
if(!this.widget) {
this.widget = new ElementWidget(this);
}
};
ElementRenderer.prototype.computeAttributes = function() {
var changedAttributes = {},
self = this,
value;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(attribute.type === "indirect") {
value = self.renderTree.wiki.getTextReference(attribute.textReference,"",self.tiddlerTitle);
} else if(attribute.type === "macro") {
// Get the macro definition
var macro = self.renderTree.findMacroDefinition(self.parentRenderer,attribute.value.name);
if(!macro) {
value = "";
} else {
// Substitute the macro parameters
value = self.renderTree.substituteParameters(macro,attribute.value);
// Parse the text and render it as text
value = self.renderTree.wiki.renderText("text/plain","text/vnd.tiddlywiki",value,self.context);
}
} else { // String attribute
value = attribute.value;
}
// Check whether the attribute has changed
if(self.attributes[name] !== value) {
self.attributes[name] = value;
changedAttributes[name] = true;
}
});
return changedAttributes;
};
ElementRenderer.prototype.hasAttribute = function(name) {
return $tw.utils.hop(this.attributes,name);
};
ElementRenderer.prototype.getAttribute = function(name,defaultValue) {
if($tw.utils.hop(this.attributes,name)) {
return this.attributes[name];
} else {
return defaultValue;
}
};
ElementRenderer.prototype.renderInDom = function() {
// Check if our widget is providing an element
if(this.widget.tag) {
// Create the element
this.domNode = this.renderTree.document.createElementNS(this.namespace,this.widget.tag);
// Assign any specified event handlers
$tw.utils.addEventListeners(this.domNode,this.widget.events);
// Assign the attributes
this.assignAttributes();
// Render any child nodes
var self = this;
$tw.utils.each(this.widget.children,function(node) {
if(node.renderInDom) {
self.domNode.appendChild(node.renderInDom());
}
});
// Call postRenderInDom if the widget provides it and we're in the browser
if($tw.browser && this.widget.postRenderInDom) {
this.widget.postRenderInDom();
}
// Return the dom node
return this.domNode;
} else {
// If we're not generating an element, just render our first child
return this.widget.children[0].renderInDom();
}
};
ElementRenderer.prototype.assignAttributes = function() {
var self = this;
$tw.utils.each(this.widget.attributes,function(v,a) {
if(v !== undefined) {
if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className?
self.domNode.className = v.join(" ");
} else if (typeof v === "object") { // ...or objects other than style?
for(var p in v) {
self.domNode.style[$tw.utils.unHyphenateCss(p)] = v[p];
}
} else {
// Setting certain attributes can cause a DOM error (eg xmlns on the svg element)
try {
self.domNode.setAttributeNS(null,a,v);
} catch(e) {
}
}
}
});
};
ElementRenderer.prototype.refreshInDom = function(changedTiddlers) {
// Update our attributes if required
var changedAttributes = {};
if($tw.utils.checkDependencies(this.dependencies,changedTiddlers)) {
changedAttributes = this.computeAttributes();
}
// Check if the widget has a refreshInDom method
if(this.widget.refreshInDom) {
// Let the widget refresh itself
this.widget.refreshInDom(changedAttributes,changedTiddlers);
} else {
// If not, assign the attributes and refresh any child nodes
this.assignAttributes();
$tw.utils.each(this.widget.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.element = ElementRenderer
})();

View File

@ -1,31 +0,0 @@
/*\
title: $:/core/modules/rendertree/renderers/entity.js
type: application/javascript
module-type: wikirenderer
Entity renderer
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Entity renderer
*/
var EntityRenderer = function(renderTree,parentRenderer,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.parentRenderer = parentRenderer;
this.parseTreeNode = parseTreeNode;
};
EntityRenderer.prototype.renderInDom = function() {
return this.renderTree.document.createTextNode($tw.utils.entityDecode(this.parseTreeNode.entity));
};
exports.entity = EntityRenderer
})();

View File

@ -1,65 +0,0 @@
/*\
title: $:/core/modules/rendertree/renderers/macrocall.js
type: application/javascript
module-type: wikirenderer
Macro call renderer
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Macro call renderer
*/
var MacroCallRenderer = function(renderTree,parentRenderer,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.parentRenderer = parentRenderer;
this.parseTreeNode = parseTreeNode;
// Find the macro definition
var macro = this.renderTree.findMacroDefinition(this.parentRenderer,this.parseTreeNode.name);
// Insert an error message if we couldn't find the macro
var childTree;
if(!macro) {
childTree = [{type: "text", text: "<<Undefined macro: " + this.parseTreeNode.name + ">>"}];
} else {
// Substitute the macro parameters
var text = this.renderTree.substituteParameters(macro,this.parseTreeNode);
// Parse the text
childTree = this.renderTree.wiki.parseText("text/vnd.tiddlywiki",text,{parseAsInline: !this.parseTreeNode.isBlock}).tree;
}
// Create the renderers for the child nodes
this.children = this.renderTree.createRenderers(this,childTree);
};
MacroCallRenderer.prototype.renderInDom = function() {
// Create the element
this.domNode = this.renderTree.document.createElement(this.parseTreeNode.isBlock ? "div" : "span");
this.domNode.setAttribute("data-macro-name",this.parseTreeNode.name);
// Render any child nodes
var self = this;
$tw.utils.each(this.children,function(node,index) {
if(node.renderInDom) {
self.domNode.appendChild(node.renderInDom());
}
});
// Return the dom node
return this.domNode;
};
MacroCallRenderer.prototype.refreshInDom = function(changedTiddlers) {
// Refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
};
exports.macrocall = MacroCallRenderer
})();

View File

@ -1,30 +0,0 @@
/*\
title: $:/core/modules/rendertree/renderers/macrodef.js
type: application/javascript
module-type: wikirenderer
Macro definition renderer
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Macro definition renderer
*/
var MacroDefRenderer = function(renderTree,parentRenderer,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.parentRenderer = parentRenderer;
this.parseTreeNode = parseTreeNode;
// Save the macro definition into the context of the rendertree
this.renderTree.context.macroDefinitions = this.renderTree.context.macroDefinitions || {};
this.renderTree.context.macroDefinitions[this.parseTreeNode.name] = this.parseTreeNode;
};
exports.macrodef = MacroDefRenderer
})();

View File

@ -1,33 +0,0 @@
/*\
title: $:/core/modules/rendertree/renderers/raw.js
type: application/javascript
module-type: wikirenderer
Raw HTML renderer
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Raw HTML renderer
*/
var RawRenderer = function(renderTree,parentRenderer,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.parentRenderer = parentRenderer;
this.parseTreeNode = parseTreeNode;
};
RawRenderer.prototype.renderInDom = function() {
var domNode = this.renderTree.document.createElement("div");
domNode.innerHTML = this.parseTreeNode.html;
return domNode;
};
exports.raw = RawRenderer
})();

View File

@ -1,31 +0,0 @@
/*\
title: $:/core/modules/rendertree/renderers/text.js
type: application/javascript
module-type: wikirenderer
Text renderer
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Text renderer
*/
var TextRenderer = function(renderTree,parentRenderer,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.parentRenderer = parentRenderer;
this.parseTreeNode = parseTreeNode;
};
TextRenderer.prototype.renderInDom = function() {
return this.renderTree.document.createTextNode(this.parseTreeNode.text);
};
exports.text = TextRenderer
})();

View File

@ -1,198 +0,0 @@
/*\
title: $:/core/modules/rendertree/wikirendertree.js
type: application/javascript
module-type: global
Wiki text render tree
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Create a render tree object for a parse tree
parser: reference to the parse tree to be rendered
options: see below
Options include:
wiki: mandatory reference to wiki associated with this render tree
context: optional hashmap of context variables (see below)
parentRenderer: optional reference to a parent renderer node for the context chain
document: optional document object to use instead of global document
Context variables include:
tiddlerTitle: title of the tiddler providing the context
templateTitle: title of the tiddler providing the current template
macroDefinitions: hashmap of macro definitions
*/
var WikiRenderTree = function(parser,options) {
this.parser = parser;
this.wiki = options.wiki;
this.context = options.context || {};
this.parentRenderer = options.parentRenderer;
this.document = options.document;
// Hashmap of the renderer classes
if(!this.rendererClasses) {
WikiRenderTree.prototype.rendererClasses = $tw.modules.applyMethods("wikirenderer");
}
};
/*
Generate the full render tree for this parse tree
*/
WikiRenderTree.prototype.execute = function() {
this.rendererTree = this.createRenderers(this,this.parser.tree);
};
/*
Create an array of renderers for an array of parse tree nodes
*/
WikiRenderTree.prototype.createRenderers = function(parentRenderer,parseTreeNodes) {
var rendererNodes = [];
if(parseTreeNodes) {
for(var t=0; t<parseTreeNodes.length; t++) {
rendererNodes.push(this.createRenderer(parentRenderer,parseTreeNodes[t]));
}
}
return rendererNodes;
};
/*
Create a renderer node for a parse tree node
*/
WikiRenderTree.prototype.createRenderer = function(parentRenderer,parseTreeNode) {
var RenderNodeClass = this.rendererClasses[parseTreeNode.type];
return new RenderNodeClass(this,parentRenderer,parseTreeNode);
};
/*
Render to the DOM
*/
WikiRenderTree.prototype.renderInDom = function(container) {
this.container = container;
$tw.utils.each(this.rendererTree,function(node) {
if(node.renderInDom) {
container.appendChild(node.renderInDom());
}
});
};
/*
Update the DOM rendering in the light of a set of changes
*/
WikiRenderTree.prototype.refreshInDom = function(changes) {
$tw.utils.each(this.rendererTree,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changes);
}
});
};
/*
Find the value of a given context variable for a particular renderer node
*/
WikiRenderTree.prototype.getContextVariable = function(renderer,name,defaultValue) {
while(renderer) {
if($tw.utils.hop(renderer.context,name)) {
return renderer.context[name];
}
renderer = renderer.parentRenderer;
};
return defaultValue;
};
/*
Check for render context recursion from a particular renderer node by returning true if the members of a proposed new render context are already present in the render context chain
*/
WikiRenderTree.prototype.checkContextRecursion = function(renderer,newContext) {
while(renderer) {
var context = renderer.context;
if(context) {
var match = true;
for(var member in newContext) {
if($tw.utils.hop(context,member)) {
if(newContext[member] !== context[member]) {
match = false;
}
} else {
match = false;
}
}
if(match) {
return true;
}
}
renderer = renderer.parentRenderer;
}
return false;
};
WikiRenderTree.prototype.getContextScopeId = function(renderer) {
var guidBits = [],
scopeComponents = ["tiddlerTitle","templateTitle"],
processContext = function(field,name) {
if(scopeComponents.indexOf(name) !== -1) {
guidBits.push(name + ":" + field + ";");
}
};
while(renderer) {
if(renderer.context) {
$tw.utils.each(renderer.context,processContext);
guidBits.push("-");
}
renderer = renderer.parentRenderer;
}
return guidBits.join("");
};
/*
Find a named macro definition
*/
WikiRenderTree.prototype.findMacroDefinition = function(renderer,name) {
while(renderer) {
if(renderer.context && renderer.context.macroDefinitions && renderer.context.macroDefinitions[name]) {
return renderer.context.macroDefinitions[name];
}
renderer = renderer.parentRenderer;
}
return undefined;
};
/*
Expand the parameters of a macro
*/
WikiRenderTree.prototype.substituteParameters = function(macroDefinition,macroCallParseTreeNode) {
var text = macroDefinition.text,
nextAnonParameter = 0; // Next candidate anonymous parameter in macro call
// Step through each of the parameters in the macro definition
for(var p=0; p<macroDefinition.params.length; p++) {
// Check if we've got a macro call parameter with the same name
var paramInfo = macroDefinition.params[p],
paramValue = undefined;
for(var m=0; m<macroCallParseTreeNode.params.length; m++) {
if(macroCallParseTreeNode.params[m].name === paramInfo.name) {
paramValue = macroCallParseTreeNode.params[m].value;
}
}
// If not, use the next available anonymous macro call parameter
if(!paramValue && nextAnonParameter < macroCallParseTreeNode.params.length) {
while(macroCallParseTreeNode.params[nextAnonParameter].name && nextAnonParameter < macroCallParseTreeNode.params.length-1) {
nextAnonParameter++;
}
if(!macroCallParseTreeNode.params[nextAnonParameter].name) {
paramValue = macroCallParseTreeNode.params[nextAnonParameter].value;
nextAnonParameter++;
}
}
// If we've still not got a value, use the default, if any
paramValue = paramValue || paramInfo["default"] || "";
// Replace any instances of this parameter
text = text.replace(new RegExp("\\$" + $tw.utils.escapeRegExp(paramInfo.name) + "\\$","mg"),paramValue);
}
return text;
};
exports.WikiRenderTree = WikiRenderTree;
})();

61
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
@ -26,6 +28,7 @@ exports.startup = function() {
$tw.modules.applyMethods("tiddlermethod",$tw.Tiddler.prototype);
$tw.modules.applyMethods("wikimethod",$tw.Wiki.prototype);
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
$tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro");
// Set up the parsers
$tw.wiki.initParsers();
// Set up the syncer object
@ -50,39 +53,44 @@ exports.startup = function() {
$tw.wiki.addTiddler({title: storyTitle, text: "", list: story},$tw.wiki.getModificationFields());
// Host-specific startup
if($tw.browser) {
// Call browser startup modules
$tw.modules.forEachModuleOfType("browser-startup",function(title,module) {
if(module.startup) {
module.startup();
}
});
// Install the popup manager
$tw.popup = new $tw.utils.Popup({
rootElement: document.body
});
// Install the animator
$tw.anim = new $tw.utils.Animator();
// Kick off the stylesheet manager
$tw.stylesheetManager = new $tw.utils.StylesheetManager($tw.wiki);
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
$tw.rootWidget = new widget.widget({type: "widget", children: []},{
wiki: $tw.wiki,
document: document
});
// Install the modal message mechanism
$tw.modal = new $tw.utils.Modal($tw.wiki);
document.addEventListener("tw-modal",function(event) {
$tw.rootWidget.addEventListener("tw-modal",function(event) {
$tw.modal.display(event.param);
},false);
});
// Install the notification mechanism
$tw.notifier = new $tw.utils.Notifier($tw.wiki);
document.addEventListener("tw-notify",function(event) {
$tw.rootWidget.addEventListener("tw-notify",function(event) {
$tw.notifier.display(event.param);
},false);
});
// Install the scroller
$tw.pageScroller = new $tw.utils.PageScroller();
document.addEventListener("tw-scroll",$tw.pageScroller,false);
$tw.rootWidget.addEventListener("tw-scroll",function(event) {
$tw.pageScroller.handleEvent(event);
});
// Install the save action handler
$tw.wiki.initSavers();
document.addEventListener("tw-save-wiki",function(event) {
$tw.rootWidget.addEventListener("tw-save-wiki",function(event) {
$tw.wiki.saveWiki({
template: event.param,
downloadType: "text/plain"
});
},false);
});
// Install the crypto event handlers
document.addEventListener("tw-set-password",function(event) {
$tw.rootWidget.addEventListener("tw-set-password",function(event) {
$tw.passwordPrompt.createPrompt({
serviceName: "Set a new password for this TiddlyWiki",
noUserName: true,
@ -93,24 +101,19 @@ exports.startup = function() {
}
});
});
document.addEventListener("tw-clear-password",function(event) {
$tw.rootWidget.addEventListener("tw-clear-password",function(event) {
$tw.crypto.setPassword(null);
});
// Install the animator
$tw.anim = new $tw.utils.Animator();
// Kick off the stylesheet manager
$tw.stylesheetManager = new $tw.utils.StylesheetManager($tw.wiki);
// Display the PageTemplate
var templateTitle = "$:/core/ui/PageTemplate",
parser = $tw.wiki.parseTiddler(templateTitle),
renderTree = new $tw.WikiRenderTree(parser,{wiki: $tw.wiki, context: {tiddlerTitle: templateTitle}, document: document});
renderTree.execute();
// Display the PageMacros, which includes the PageTemplate
var templateTitle = "$:/core/ui/PageMacros",
parser = $tw.wiki.new_parseTiddler(templateTitle);
$tw.pageWidgetNode = $tw.wiki.makeWidget(parser,{document: document, parentWidget: $tw.rootWidget});
$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.pageWidgetNode.render($tw.pageContainer,null);
$tw.wiki.addEventListener("change",function(changes) {
renderTree.refreshInDom(changes);
$tw.pageWidgetNode.refresh(changes,$tw.pageContainer,null);
});
// If we're being viewed on a data: URI then give instructions for how to save
if(document.location.protocol === "data:") {
@ -118,6 +121,12 @@ exports.startup = function() {
param: "$:/messages/SaveInstructions"
});
}
// Call browser startup modules
$tw.modules.forEachModuleOfType("browser-startup",function(title,module) {
if(module.startup) {
module.startup();
}
});
} else {
// On the server, start a commander with the command line arguments
commander = new $tw.Commander(

View File

@ -1,9 +1,9 @@
/*\
title: $:/core/modules/widgets/list/listviews/classic.js
title: $:/core/modules/storyviews/classic.js
type: application/javascript
module-type: listview
module-type: storyview
Views the list as a linear sequence
Views the story as a linear sequence
\*/
(function(){
@ -12,30 +12,28 @@ Views the list as a linear sequence
/*global $tw: false */
"use strict";
var ClassicListView = function(listWidget) {
var ClassicStoryView = function(listWidget) {
this.listWidget = listWidget;
}
ClassicListView.prototype.navigateTo = function(historyInfo) {
var listElementIndex = this.listWidget.findListElementByTitle(0,historyInfo.title);
ClassicStoryView.prototype.navigateTo = function(historyInfo) {
var listElementIndex = this.listWidget.findListItem(0,historyInfo.title);
if(listElementIndex === undefined) {
return;
}
var listElementNode = this.listWidget.children[listElementIndex],
targetElement = listElementNode.domNode;
var listItemWidget = this.listWidget.children[listElementIndex],
targetElement = listItemWidget.findFirstDomNode();
// Scroll the node into view
var scrollEvent = this.listWidget.renderer.renderTree.document.createEvent("Event");
scrollEvent.initEvent("tw-scroll",true,true);
targetElement.dispatchEvent(scrollEvent);
this.listWidget.dispatchEvent({type: "tw-scroll", target: targetElement});
};
ClassicListView.prototype.insert = function(index) {
var listElementNode = this.listWidget.children[index],
targetElement = listElementNode.domNode,
ClassicStoryView.prototype.insert = function(widget) {
var targetElement = widget.findFirstDomNode(),
duration = $tw.utils.getAnimationDuration();
// Get the current height of the tiddler
var currMarginBottom = parseInt(window.getComputedStyle(targetElement).marginBottom,10),
currMarginTop = parseInt(window.getComputedStyle(targetElement).marginTop,10),
var computedStyle = window.getComputedStyle(targetElement),
currMarginBottom = parseInt(computedStyle.marginBottom,10),
currMarginTop = parseInt(computedStyle.marginTop,10),
currHeight = targetElement.offsetHeight + currMarginTop;
// Reset the margin once the transition is over
setTimeout(function() {
@ -60,20 +58,18 @@ ClassicListView.prototype.insert = function(index) {
]);
};
ClassicListView.prototype.remove = function(index) {
var listElementNode = this.listWidget.children[index],
targetElement = listElementNode.domNode,
ClassicStoryView.prototype.remove = function(widget) {
var targetElement = widget.findFirstDomNode(),
duration = $tw.utils.getAnimationDuration();
// Get the current height of the tiddler
var currWidth = targetElement.offsetWidth,
currMarginBottom = parseInt(window.getComputedStyle(targetElement).marginBottom,10),
currMarginTop = parseInt(window.getComputedStyle(targetElement).marginTop,10),
computedStyle = window.getComputedStyle(targetElement),
currMarginBottom = parseInt(computedStyle.marginBottom,10),
currMarginTop = parseInt(computedStyle.marginTop,10),
currHeight = targetElement.offsetHeight + currMarginTop;
// Remove the element at the end of the transition
// Remove the dom nodes of the widget at the end of the transition
setTimeout(function() {
if(targetElement.parentNode) {
targetElement.parentNode.removeChild(targetElement);
}
widget.removeChildDomNodes();
},duration);
// Animate the closure
$tw.utils.setStyle(targetElement,[
@ -91,10 +87,8 @@ ClassicListView.prototype.remove = function(index) {
{marginBottom: (-currHeight) + "px"},
{opacity: "0.0"}
]);
// Returning true causes the DOM node not to be deleted
return true;
};
exports.classic = ClassicListView;
exports.classic = ClassicStoryView;
})();
})();

View File

@ -1,7 +1,7 @@
/*\
title: $:/core/modules/widgets/list/listviews/pop.js
title: $:/core/modules/storyviews/pop.js
type: application/javascript
module-type: listview
module-type: storyview
Animates list insertions and removals
@ -12,13 +12,23 @@ Animates list insertions and removals
/*global $tw: false */
"use strict";
var PopListView = function(listWidget) {
var PopStoryView = function(listWidget) {
this.listWidget = listWidget;
}
PopListView.prototype.insert = function(index) {
var listElementNode = this.listWidget.children[index],
targetElement = listElementNode.domNode,
PopStoryView.prototype.navigateTo = function(historyInfo) {
var listElementIndex = this.listWidget.findListItem(0,historyInfo.title);
if(listElementIndex === undefined) {
return;
}
var listItemWidget = this.listWidget.children[listElementIndex],
targetElement = listItemWidget.findFirstDomNode();
// Scroll the node into view
this.listWidget.dispatchEvent({type: "tw-scroll", target: targetElement});
};
PopStoryView.prototype.insert = function(widget) {
var targetElement = widget.findFirstDomNode(),
duration = $tw.utils.getAnimationDuration();
// Reset once the transition is over
setTimeout(function() {
@ -43,14 +53,13 @@ PopListView.prototype.insert = function(index) {
]);
};
PopListView.prototype.remove = function(index) {
var listElementNode = this.listWidget.children[index],
targetElement = listElementNode.domNode,
PopStoryView.prototype.remove = function(widget) {
var targetElement = widget.findFirstDomNode(),
duration = $tw.utils.getAnimationDuration();
// Remove the element at the end of the transition
setTimeout(function() {
if(targetElement.parentNode) {
targetElement.parentNode.removeChild(targetElement);
widget.removeChildDomNodes();
}
},duration);
// Animate the closure
@ -66,10 +75,8 @@ PopListView.prototype.remove = function(index) {
{transform: "scale(0.1)"},
{opacity: "0.0"}
]);
// Returning true causes the DOM node not to be deleted
return true;
};
exports.pop = PopListView;
exports.pop = PopStoryView;
})();

View File

@ -1,7 +1,7 @@
/*\
title: $:/core/modules/widgets/list/listviews/zoomin.js
title: $:/core/modules/storyviews/zoomin.js
type: application/javascript
module-type: listview
module-type: storyview
Zooms between individual tiddlers
@ -13,131 +13,32 @@ Zooms between individual tiddlers
"use strict";
var ZoominListView = function(listWidget) {
var self = this;
this.listWidget = listWidget;
this.storyNode = this.listWidget.renderer.domNode;
// Set the current tiddler
this.currentTiddler = this.listWidget.children[0].domNode;
// Make all the tiddlers position absolute, and hide all but the first one
this.storyNode.style.position = "relative";
for(var t=0; t<this.storyNode.children.length; t++) {
if(t) {
this.storyNode.children[t].style.display = "none";
$tw.utils.each(this.listWidget.children,function(itemWidget,index) {
var domNode = itemWidget.findFirstDomNode();
if(index) {
domNode.style.display = "none";
} else {
self.currentTiddlerDomNode = domNode;
}
this.storyNode.children[t].style.position = "absolute";
}
};
domNode.style.position = "absolute";
});
}
/*
Find the first descendent node that is a <$view field="title"> widget
*/
function findTitleNode(node) {
var t,r;
// Return true if this node is a view widget with the field attribute set to "title"
if(node instanceof $tw.WikiRenderTree.prototype.rendererClasses.element) {
if(node.widget instanceof $tw.WikiRenderTree.prototype.rendererClasses.element.prototype.widgetClasses.view && node.attributes.field === "title") {
return node;
}
if(node.widget.children) {
for(t=0; t<node.widget.children.length; t++) {
var r = findTitleNode(node.widget.children[t]);
if(r) {
return r;
}
}
}
} else {
if(node.children) {
for(t=0; t<node.children.length; t++) {
var r = findTitleNode(node.children[t]);
if(r) {
return r;
}
}
}
}
function findTitleWidget() {
return null;
}
ZoominListView.prototype.insert = function(index) {
var listElementNode = this.listWidget.children[index],
targetElement = listElementNode.domNode;
// Make the newly inserted node position absolute and hidden
$tw.utils.setStyle(targetElement,[
{display: "none"},
{position: "absolute"}
]);
};
/*
Visualise navigating back to the previous tiddler
storyElement: story element being closed
*/
ZoominListView.prototype.remove = function(index) {
var listElementNode = this.listWidget.children[index],
targetElement = listElementNode.domNode,
duration = $tw.utils.getAnimationDuration();
// Set up the tiddler that is being closed
$tw.utils.setStyle(targetElement,[
{position: "absolute"},
{display: "block"},
{transformOrigin: "50% 50%"},
{transform: "translateX(0px) translateY(0px) scale(1)"},
{transition: "none"},
{zIndex: "0"}
]);
// We'll move back to the previous or next element in the story
var toElement = this.storyNode.children[index - 1];
if(!toElement) {
toElement = this.storyNode.children[index + 1];
}
// Set up the tiddler we're moving back in
if(toElement) {
$tw.utils.setStyle(toElement,[
{position: "absolute"},
{display: "block"},
{transformOrigin: "50% 50%"},
{transform: "translateX(0px) translateY(0px) scale(10)"},
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in, opacity " + duration + "ms ease-in"},
{opacity: "0"},
{zIndex: "500"}
]);
this.currentTiddler = toElement;
}
// Animate them both
// Force layout
$tw.utils.forceLayout(this.storyNode);
// First, the tiddler we're closing
$tw.utils.setStyle(targetElement,[
{transformOrigin: "50% 50%"},
{transform: "translateX(0px) translateY(0px) scale(0.1)"},
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in, opacity " + duration + "ms ease-in"},
{opacity: "0"},
{zIndex: "0"}
]);
setTimeout(function() {
// Delete the DOM node when the transition is over
if(targetElement.parentNode) {
targetElement.parentNode.removeChild(targetElement);
}
},duration);
// Now the tiddler we're going back to
if(toElement) {
$tw.utils.setStyle(toElement,[
{transform: "translateX(0px) translateY(0px) scale(1)"},
{opacity: "1"}
]);
}
return true; // Indicate that we'll delete the DOM node
};
ZoominListView.prototype.navigateTo = function(historyInfo) {
var listElementIndex = this.listWidget.findListElementByTitle(0,historyInfo.title),
duration = $tw.utils.getAnimationDuration();
var duration = $tw.utils.getAnimationDuration(),
listElementIndex = this.listWidget.findListItem(0,historyInfo.title);
if(listElementIndex === undefined) {
return;
}
var listElementNode = this.listWidget.children[listElementIndex],
targetElement = listElementNode.domNode;
var listItemWidget = this.listWidget.children[listElementIndex],
targetElement = listItemWidget.findFirstDomNode();
// Make the new tiddler be position absolute and visible so that we can measure it
$tw.utils.setStyle(targetElement,[
{position: "absolute"},
@ -155,8 +56,8 @@ ZoominListView.prototype.navigateTo = function(historyInfo) {
height: window.innerHeight/8
};
// Try to find the title node in the target tiddler
var titleNode = findTitleNode(listElementNode) || listElementNode,
zoomBounds = titleNode.domNode.getBoundingClientRect();
var titleWidget = findTitleWidget(listItemWidget) || listItemWidget,
zoomBounds = titleWidget.findFirstDomNode().getBoundingClientRect();
// Compute the transform for the target tiddler to make the title lie over the source rectange
var targetBounds = targetElement.getBoundingClientRect(),
scale = sourceBounds.width / zoomBounds.width,
@ -170,8 +71,8 @@ ZoominListView.prototype.navigateTo = function(historyInfo) {
$tw.utils.forceLayout(targetElement);
// Apply the ending transitions with a timeout to ensure that the previously applied transformations are applied first
var self = this,
prevCurrentTiddler = this.currentTiddler;
this.currentTiddler = targetElement;
prevCurrentTiddler = this.currentTiddlerDomNode;
this.currentTiddlerDomNode = targetElement;
// Transform the target tiddler to its natural size
$tw.utils.setStyle(targetElement,[
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in, opacity " + duration + "ms ease-in"},
@ -193,7 +94,7 @@ ZoominListView.prototype.navigateTo = function(historyInfo) {
]);
// Hide the tiddler when the transition has finished
setTimeout(function() {
if(self.currentTiddler !== prevCurrentTiddler) {
if(self.currentTiddlerDomNode !== prevCurrentTiddler) {
prevCurrentTiddler.style.display = "none";
}
},duration);
@ -202,6 +103,71 @@ ZoominListView.prototype.navigateTo = function(historyInfo) {
// $tw.pageScroller.scrollIntoView(targetElement);
};
ZoominListView.prototype.insert = function(widget) {
var targetElement = widget.findFirstDomNode();
// Make the newly inserted node position absolute and hidden
$tw.utils.setStyle(targetElement,[
{display: "none"},
{position: "absolute"}
]);
};
ZoominListView.prototype.remove = function(widget) {
var targetElement = widget.findFirstDomNode(),
duration = $tw.utils.getAnimationDuration();
// Set up the tiddler that is being closed
$tw.utils.setStyle(targetElement,[
{position: "absolute"},
{display: "block"},
{transformOrigin: "50% 50%"},
{transform: "translateX(0px) translateY(0px) scale(1)"},
{transition: "none"},
{zIndex: "0"}
]);
// We'll move back to the previous or next element in the story
var toWidget = widget.previousSibling();
if(!toWidget) {
toWidget = widget.nextSibling();
}
var toWidgetDomNode = toWidget && toWidget.findFirstDomNode();
// Set up the tiddler we're moving back in
if(toWidgetDomNode) {
$tw.utils.setStyle(toWidgetDomNode,[
{position: "absolute"},
{display: "block"},
{transformOrigin: "50% 50%"},
{transform: "translateX(0px) translateY(0px) scale(10)"},
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in, opacity " + duration + "ms ease-in"},
{opacity: "0"},
{zIndex: "500"}
]);
this.currentTiddlerDomNode = toWidgetDomNode;
}
// Animate them both
// Force layout
$tw.utils.forceLayout(this.listWidget.parentDomNode);
// First, the tiddler we're closing
$tw.utils.setStyle(targetElement,[
{transformOrigin: "50% 50%"},
{transform: "translateX(0px) translateY(0px) scale(0.1)"},
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in, opacity " + duration + "ms ease-in"},
{opacity: "0"},
{zIndex: "0"}
]);
setTimeout(function() {
// Delete the DOM node when the transition is over
widget.removeChildDomNodes();
},duration);
// Now the tiddler we're going back to
if(toWidgetDomNode) {
$tw.utils.setStyle(toWidgetDomNode,[
{transform: "translateX(0px) translateY(0px) scale(1)"},
{opacity: "1"}
]);
}
return true; // Indicate that we'll delete the DOM node
};
exports.zoomin = ZoominListView;
})();
})();

View File

@ -13,7 +13,8 @@ A simple slide animation that varies the height of the element
"use strict";
function slideOpen(domNode,options) {
var duration = $tw.utils.getAnimationDuration();
options = options || {};
var duration = options.duration || $tw.utils.getAnimationDuration();
// Get the current height of the domNode
var computedStyle = window.getComputedStyle(domNode),
currMarginBottom = parseInt(computedStyle.marginBottom,10),
@ -65,7 +66,8 @@ function slideOpen(domNode,options) {
}
function slideClosed(domNode,options) {
var duration = $tw.utils.getAnimationDuration(),
options = options || {};
var duration = options.duration || $tw.utils.getAnimationDuration(),
currHeight = domNode.offsetHeight;
// Clear the properties we've set when the animation is over
setTimeout(function() {

View File

@ -27,10 +27,15 @@ Animator.prototype.perform = function(type,domNode,options) {
chosenAnimation = animation[type];
}
});
// Call the animation
if(chosenAnimation) {
chosenAnimation(domNode,options);
if(!chosenAnimation) {
chosenAnimation = function(domNode,options) {
if(options.callback) {
options.callback();
}
};
}
// Call the animation
chosenAnimation(domNode,options);
};
exports.Animator = Animator;

View File

@ -117,14 +117,16 @@ exports.getBoundingPageRect = function(element) {
Saves a named password in the browser
*/
exports.savePassword = function(name,password) {
localStorage.setItem("tw5-password-" + name,password);
if(window.localStorage) {
localStorage.setItem("tw5-password-" + name,password);
}
};
/*
Retrieve a named password from the browser
*/
exports.getPassword = function(name) {
return localStorage.getItem("tw5-password-" + name);
return window.localStorage ? localStorage.getItem("tw5-password-" + name) : "";
};
/*

View File

@ -12,6 +12,8 @@ Modal message mechanism
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/new_widgets/widget.js");
var Modal = function(wiki) {
this.wiki = wiki;
this.modalCount = 0;
@ -71,20 +73,18 @@ Modal.prototype.display = function(title,options) {
} else {
titleText = title;
}
var headerParser = this.wiki.parseText("text/vnd.tiddlywiki",titleText,{parseAsInline: true}),
headerRenderTree = new $tw.WikiRenderTree(headerParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document});
headerRenderTree.execute();
headerRenderTree.renderInDom(headerTitle);
var headerParser = this.wiki.new_parseText("text/vnd.tiddlywiki",titleText,{parseAsInline: true}),
headerWidgetNode = this.wiki.makeWidget(headerParser,{parentWidget: $tw.rootWidget, document: document});
headerWidgetNode.render(headerTitle,null);
this.wiki.addEventListener("change",function(changes) {
headerRenderTree.refreshInDom(changes);
headerWidgetNode.refresh(changes,modalHeader,null);
});
// Render the body of the message
var bodyParser = this.wiki.parseTiddler(title),
bodyRenderTree = new $tw.WikiRenderTree(bodyParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document});
bodyRenderTree.execute();
bodyRenderTree.renderInDom(modalBody);
var bodyParser = this.wiki.new_parseTiddler(title),
bodyWidgetNode = this.wiki.makeWidget(bodyParser,{parentWidget: $tw.rootWidget, document: document});
bodyWidgetNode.render(modalBody,null);
this.wiki.addEventListener("change",function(changes) {
bodyRenderTree.refreshInDom(changes);
bodyWidgetNode.refresh(changes,modalBody,null);
});
// Setup the link if present
if(options.downloadLink) {
@ -107,15 +107,14 @@ Modal.prototype.display = function(title,options) {
} else {
footerText = '<$button message="tw-close-tiddler" class="btn btn-primary">Close</$button>';
}
var footerParser = this.wiki.parseText("text/vnd.tiddlywiki",footerText,{parseAsInline: true}),
footerRenderTree = new $tw.WikiRenderTree(footerParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document});
footerRenderTree.execute();
footerRenderTree.renderInDom(modalFooterButtons);
var footerParser = this.wiki.new_parseText("text/vnd.tiddlywiki",footerText,{parseAsInline: true}),
footerWidgetNode = this.wiki.makeWidget(footerParser,{parentWidget: $tw.rootWidget, document: document});
footerWidgetNode.render(modalFooterButtons,null);
this.wiki.addEventListener("change",function(changes) {
footerRenderTree.refreshInDom(changes);
footerWidgetNode.refresh(changes,modalFooterButtons,null);
});
// Add the close event handler
wrapper.addEventListener("tw-close-tiddler",function(event) {
var closeHandler = function(event) {
// Decrease the modal count and adjust the body class
self.modalCount--;
self.adjustPageClass();
@ -136,9 +135,11 @@ Modal.prototype.display = function(title,options) {
}
},duration);
// Don't let anyone else handle the tw-close-tiddler message
event.stopPropagation();
return false;
},false);
};
headerWidgetNode.addEventListener("tw-close-tiddler",closeHandler,false);
bodyWidgetNode.addEventListener("tw-close-tiddler",closeHandler,false);
footerWidgetNode.addEventListener("tw-close-tiddler",closeHandler,false);
// Set the initial styles for the message
$tw.utils.setStyle(modalBackdrop,[
{opacity: "0"}

View File

@ -12,6 +12,8 @@ Notifier mechanism
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/new_widgets/widget.js");
var Notifier = function(wiki) {
this.wiki = wiki;
};
@ -35,12 +37,11 @@ Notifier.prototype.display = function(title,options) {
// Add classes
$tw.utils.addClass(notification,"tw-notification");
// Render the body of the notification
var bodyParser = this.wiki.parseTiddler(title),
bodyRenderTree = new $tw.WikiRenderTree(bodyParser,{wiki: $tw.wiki, context: {tiddlerTitle: title}, document: document});
bodyRenderTree.execute();
bodyRenderTree.renderInDom(notification);
var parser = this.wiki.new_parseTiddler(title),
widgetNode = this.wiki.makeWidget(parser,{parentWidget: $tw.rootWidget, document: document});
widgetNode.render(notification,null);
this.wiki.addEventListener("change",function(changes) {
bodyRenderTree.refreshInDom(changes);
widgetNode.refresh(changes,notification,null);
});
// Set the initial styles for the notification
$tw.utils.setStyle(notification,[

View File

@ -30,7 +30,7 @@ Popup.prototype.show = function(options) {
};
Popup.prototype.handleEvent = function(event) {
if(event.type === "click" && !$tw.utils.domContains(this.anchorDomNode,event.target)) {
if(event.type === "click" && this.anchorDomNode !== event.target && !$tw.utils.domContains(this.anchorDomNode,event.target)) {
this.cancel();
}
};
@ -54,8 +54,7 @@ Popup.prototype.triggerPopup = function(options) {
// Get the current popup state tiddler
var value = options.wiki.getTextReference(options.title,"");
// Check if the popup is open by checking whether it matches "(<x>,<y>)"
var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/,
state = !popupLocationRegExp.test(value);
var state = !this.readPopupState(options.title,value);
if("force" in options) {
state = options.force;
}
@ -71,6 +70,18 @@ Popup.prototype.triggerPopup = function(options) {
}
};
/*
Returns true if the specified title and text identifies an active popup
*/
Popup.prototype.readPopupState = function(title,text) {
var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/,
result = false;
if(this.title === title) {
result = popupLocationRegExp.test(text);
}
return result;
};
exports.Popup = Popup;
})();

View File

@ -34,12 +34,7 @@ StylesheetManager.prototype.addStylesheet = function(title) {
// Record the stylesheet in the hashmap
this.stylesheets[title] = true;
// Parse the tiddler and render as plain text
var parser = this.wiki.parseTiddler(title),
renderTree = new $tw.WikiRenderTree(parser,{wiki: this.wiki, context: {tiddlerTitle: title}, document: $tw.document});
renderTree.execute();
var container = $tw.document.createElement("div");
renderTree.renderInDom(container);
var text = container.textContent;
var text = this.wiki.new_renderTiddler("text/plain",title);
// Create a style element and put it in the document
var styleNode = document.createElement("style");
styleNode.setAttribute("type","text/css");

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

@ -12,16 +12,29 @@ 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_Element = function(tag) {
var TW_TextNode = function(text) {
bumpSequenceNumber(this);
this.textContent = text;
};
var TW_Element = function(tag,namespace) {
bumpSequenceNumber(this);
this.isTiddlyWikiFakeDom = true;
this.tag = tag;
this.attributes = {};
this.isRaw = false;
this.children = [];
}
this.namespaceURI = namespace || "http://www.w3.org/1999/xhtml";
};
TW_Element.prototype.setAttribute = function(name,value) {
if(this.isRaw) {
@ -34,11 +47,33 @@ TW_Element.prototype.setAttributeNS = function(namespace,name,value) {
this.setAttribute(name,value);
};
TW_Element.prototype.removeAttribute = function(name) {
if(this.isRaw) {
throw "Cannot removeAttribute on a raw TW_Element";
}
if($tw.utils.hop(this.attributes,name)) {
delete this.attributes[name];
}
};
TW_Element.prototype.appendChild = function(node) {
this.children.push(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 +95,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;
@ -77,7 +121,7 @@ Object.defineProperty(TW_Element.prototype, "outerHTML", {
}
}
}
output.push(">\n");
output.push(">");
if($tw.config.htmlVoidElements.indexOf(this.tag) === -1) {
output.push(this.innerHTML);
output.push("</",this.tag,">");
@ -123,8 +167,11 @@ Object.defineProperty(TW_Element.prototype, "textContent", {
});
var document = {
setSequenceNumber: function(value) {
sequenceNumber = value;
},
createElementNS: function(namespace,tag) {
return new TW_Element(tag);
return new TW_Element(tag,namespace);
},
createElement: function(tag) {
return new TW_Element(tag);

View File

@ -351,7 +351,7 @@ Returns an object with the following fields, all optional:
*/
exports.parseTextReference = function(textRef) {
// Separate out the title, field name and/or JSON indices
var reTextRef = /^\s*([^\s!#]+)?(?:(?:!!([^\s]+))|(?:##([^\s]+)))?\s*/mg,
var reTextRef = /^\s*([^!#]+)?(?:(?:!!([^\s]+))|(?:##([^\s]+)))?\s*/mg,
match = reTextRef.exec(textRef);
if(match && reTextRef.lastIndex === textRef.length) {
// Return the parts

View File

@ -1,150 +0,0 @@
/*\
title: $:/core/modules/widgets/button.js
type: application/javascript
module-type: widget
Implements the button widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ButtonWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
ButtonWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.message = this.renderer.getAttribute("message");
this.param = this.renderer.getAttribute("param");
this.set = this.renderer.getAttribute("set");
this.setTo = this.renderer.getAttribute("setTo");
this.popup = this.renderer.getAttribute("popup");
this.hover = this.renderer.getAttribute("hover");
this.qualifyTiddlerTitles = this.renderer.getAttribute("qualifyTiddlerTitles");
this["class"] = this.renderer.getAttribute("class");
this.selectedClass = this.renderer.getAttribute("selectedClass");
// Compose the button
var classes = ["tw-button"];
if(this["class"]) {
$tw.utils.pushTop(classes,this["class"]);
}
var events = [{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}];
if(this.hover === "yes") {
events.push({name: "mouseover", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
events.push({name: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
}
if(this.set && this.setTo && this.selectedClass) {
if(this.isSelected()) {
classes.push(this.selectedClass);
}
}
// Set the return element
this.tag = "button";
this.attributes ={"class": classes.join(" ")};
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
this.events = events;
};
ButtonWidget.prototype.dispatchMessage = function(event) {
$tw.utils.dispatchCustomEvent(event.target,this.message,{
param: this.param,
tiddlerTitle: this.renderer.tiddlerTitle
});
};
ButtonWidget.prototype.triggerPopup = function(event) {
var title = this.popup;
if(this.qualifyTiddlerTitles) {
title = title + "-" + this.renderer.renderTree.getContextScopeId(this.renderer.parentRenderer);
}
$tw.popup.triggerPopup({
domNode: this.renderer.domNode,
title: title,
wiki: this.renderer.renderTree.wiki
});
};
ButtonWidget.prototype.isSelected = function() {
var title = this.set;
if(this.qualifyTiddlerTitles) {
title = title + "-" + this.renderer.renderTree.getContextScopeId(this.renderer.parentRenderer);
}
var tiddler = this.renderer.renderTree.wiki.getTiddler(title);
return tiddler ? tiddler.fields.text === this.setTo : false;
};
ButtonWidget.prototype.setTiddler = function() {
var title = this.set;
if(this.qualifyTiddlerTitles) {
title = title + "-" + this.renderer.renderTree.getContextScopeId(this.renderer.parentRenderer);
}
var tiddler = this.renderer.renderTree.wiki.getTiddler(title);
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: title, text: this.setTo}));
};
ButtonWidget.prototype.handleClickEvent = function(event) {
var handled = false;
if(this.message) {
this.dispatchMessage(event);
handled = true;
}
if(this.popup) {
this.triggerPopup(event);
handled = true;
}
if(this.set) {
this.setTiddler();
handled = true;
}
event.stopPropagation();
event.preventDefault();
return handled;
};
ButtonWidget.prototype.handleMouseOverOrOutEvent = function(event) {
if(this.popup) {
this.triggerPopup(event);
}
event.preventDefault();
return false;
};
ButtonWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
var setTitle = this.set,
popupTitle = this.popup;
if(this.qualifyTiddlerTitles) {
var scopeId = this.renderer.renderTree.getContextScopeId(this.renderer.parentRenderer);
if(setTitle) {
setTitle = setTitle + "-" + scopeId;
}
if(popupTitle) {
popupTitle = popupTitle + "-" + scopeId;
}
}
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || (setTitle && changedTiddlers[setTitle]) || (popupTitle && changedTiddlers[popupTitle])) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.button = ButtonWidget;
})();

View File

@ -1,111 +0,0 @@
/*\
title: $:/core/modules/widgets/checkbox.js
type: application/javascript
module-type: widget
Implements the checkbox widget.
```
<$checkbox tag="done"/>
<$checkbox tiddler="HelloThere" tag="red"/>
<$checkbox tag="done">
<$view field="title" format="link"/>
</$checkbox>
```
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var CheckboxWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
CheckboxWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
this.tagName = this.renderer.getAttribute("tag");
this["class"] = this.renderer.getAttribute("class");
// Compute classes
var classes = ["tw-checkbox"];
if(this["class"]) {
$tw.utils.pushTop(classes,this["class"]);
}
// Create the checkbox and span elements
var nodeCheckbox = {
type: "element",
tag: "input",
attributes: {
type: {type: "string", value: "checkbox"}
}
},
nodeSpan = {
type: "element",
tag: "span",
children: this.renderer.parseTreeNode.children
};
// Set the state of the checkbox
if(this.getValue()) {
$tw.utils.addAttributeToParseTreeNode(nodeCheckbox,"checked","true");
}
// Set the return element
this.tag = "label";
this.attributes ={"class": classes.join(" ")};
this.children = this.renderer.renderTree.createRenderers(this.renderer,[nodeCheckbox,nodeSpan]);
this.events = [{name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"}];
};
CheckboxWidget.prototype.getValue = function() {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
return tiddler ? tiddler.hasTag(this.tagName) : false;
};
CheckboxWidget.prototype.handleChangeEvent = function(event) {
var checked = this.children[0].domNode.checked,
tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
if(tiddler && tiddler.hasTag(this.tagName) !== checked) {
var newTags = tiddler.fields.tags.slice(0),
pos = newTags.indexOf(this.tagName);
if(pos !== -1) {
newTags.splice(pos,1);
}
if(checked) {
newTags.push(this.tagName);
}
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,{tags: newTags}));
}
};
CheckboxWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.tiddler || changedAttributes.tag || changedAttributes["class"]) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// Update the checkbox if necessary
if(changedTiddlers[this.tiddlerTitle]) {
this.children[0].domNode.checked = this.getValue();
}
// Refresh children
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.checkbox = CheckboxWidget;
})();

View File

@ -1,59 +0,0 @@
/*\
title: $:/core/modules/widgets/count.js
type: application/javascript
module-type: widget
Implements the count widget that displays the number of tiddlers that match a filter
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var CountWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Execute the filter to get the initial count
this.executeFilter();
// Generate child nodes
this.generate();
};
CountWidget.prototype.executeFilter = function() {
// Get attributes
this.filter = this.renderer.getAttribute("filter");
// Execute the filter
if(this.filter) {
this.currentCount = this.renderer.renderTree.wiki.filterTiddlers(this.filter,this.renderer.tiddlerTitle).length;
} else {
this.currentCount = undefined;
}
};
CountWidget.prototype.generate = function() {
// Set the element
this.tag = "span";
this.attributes = {};
this.children = this.renderer.renderTree.createRenderers(this.renderer,[
{type: "text", text: this.currentCount !== undefined ? this.currentCount.toString() : ""}
]);
};
CountWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Re-execute the filter to get the count
var oldCount = this.currentCount;
this.executeFilter();
if(this.currentCount !== oldCount) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
}
};
exports.count = CountWidget;
})();

View File

@ -1,53 +0,0 @@
/*\
title: $:/core/modules/widgets/datauri.js
type: application/javascript
module-type: widget
The datauri widget displays the contents of a tiddler as a data URI.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var DataUriWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
DataUriWidget.prototype.generate = function() {
// Get parameters from our attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
// Compose the data URI
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
uri = "";
if(tiddler) {
var type = tiddler.fields.type || "text/vnd.tiddlywiki",
typeInfo = $tw.config.contentTypeInfo[type],
isBase64 = typeInfo && typeInfo.encoding === "base64",
parts = ["data:"];
parts.push(type);
parts.push(isBase64 ? ";base64" : "");
parts.push(",");
parts.push(isBase64 ? tiddler.fields.text : encodeURIComponent(tiddler.fields.text));
uri = parts.join("");
}
// Set the element
this.tag = "pre";
this.attributes = {
"class": "tw-data-uri"
};
// Create the renderers for the wrapper and the children
this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
type: "text",
text: uri
}]);
};
exports.datauri = DataUriWidget;
})();

View File

@ -1,87 +0,0 @@
/*\
title: $:/core/modules/widgets/edit/edit.js
type: application/javascript
module-type: widget
The edit widget uses editor plugins to edit tiddlers of different types.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var EditWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Initialise the editors if they've not been done already
if(!this.editors) {
EditWidget.prototype.editors = {};
$tw.modules.applyMethods("editor",this.editors);
}
// Generate child nodes
this.generate();
};
EditWidget.prototype.generate = function() {
// Get parameters from our attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
this.fieldName = this.renderer.getAttribute("field");
this.indexName = this.renderer.getAttribute("index");
if(!this.fieldName && !this.indexName) {
this.fieldName = "text";
}
// Choose the editor to use
// TODO: Tiddler field modules should be able to specify a field type from which the editor is derived
this.editorName = this.chooseEditor();
var Editor = this.editors[this.editorName];
// Instantiate the editor
this.editor = new Editor(this,this.tiddlerTitle,this.fieldName,this.indexName);
// Ask the editor to create the widget element
this.editor.render();
};
/*
Return the name of the editor that should handle this tiddler field
*/
EditWidget.prototype.chooseEditor = function() {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
if(this.fieldName === "text" && tiddler && tiddler.fields.type && this.editors[tiddler.fields.type]) {
return tiddler.fields.type;
}
return "text/vnd.tiddlywiki";
};
EditWidget.prototype.postRenderInDom = function() {
if(this.editor && this.editor.postRenderInDom) {
this.editor.postRenderInDom();
}
};
EditWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// We'll completely regenerate ourselves if any of our attributes have changed
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.format || this.chooseEditor() !== this.editorName) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else if(this.tiddlerTitle && changedTiddlers[this.tiddlerTitle]) {
// Refresh the editor if our tiddler has changed
if(this.editor && this.editor.refreshInDom) {
this.editor.refreshInDom(changedTiddlers);
}
} else {
// Otherwise, just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.edit = EditWidget;
})();

View File

@ -1,318 +0,0 @@
/*\
title: $:/core/modules/widgets/edit/editors/bitmapeditor.js
type: application/javascript
module-type: editor
A bitmap editor
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
// Default images sizes
var DEFAULT_IMAGE_WIDTH = 300,
DEFAULT_IMAGE_HEIGHT = 185;
// The elements of the editor UI
var DOM_CANVAS = 0,
DOM_WIDTH = 1,
DOM_HEIGHT = 2;
var BitmapEditor = function(editWidget,tiddlerTitle,fieldName,indexName) {
this.editWidget = editWidget;
this.tiddlerTitle = tiddlerTitle;
this.fieldName = fieldName;
};
BitmapEditor.prototype.render = function() {
// Set the element details
this.editWidget.tag = "div";
this.editWidget.attributes = {
"class": "tw-edit-bitmapeditor-wrapper"
};
var children = [{
type: "element",
tag: "canvas",
attributes: {
"class": {type: "string", value: "tw-edit-bitmapeditor"}
},
events: [{
name: "touchstart",
handlerObject: this,
handlerMethod: "handleTouchStartEvent"
},{
name: "touchmove",
handlerObject: this,
handlerMethod: "handleTouchMoveEvent"
},{
name: "touchend",
handlerObject: this,
handlerMethod: "handleTouchEndEvent"
},{
name: "mousedown",
handlerObject: this,
handlerMethod: "handleMouseDownEvent"
},{
name: "mousemove",
handlerObject: this,
handlerMethod: "handleMouseMoveEvent"
},{
name: "mouseup",
handlerObject: this,
handlerMethod: "handleMouseUpEvent"
}]
},{
type: "element",
tag: "input",
attributes: {
"class": {type: "string", value: "tw-edit-bitmapeditor-width"},
"type": {type: "string", value: "number"},
"value": {type: "string", value: ""}
},
events: [{
name: "change",
handlerObject: this,
handlerMethod: "handleWidthChangeEvent"
}]
},{
type: "element",
tag: "input",
attributes: {
"class": {type: "string", value: "tw-edit-bitmapeditor-height"},
"type": {type: "string", value: "number"},
"value": {type: "string", value: ""}
},
events: [{
name: "change",
handlerObject: this,
handlerMethod: "handleHeightChangeEvent"
}]
}];
this.editWidget.children = this.editWidget.renderer.renderTree.createRenderers(this.editWidget.renderer,children);
};
BitmapEditor.prototype.postRenderInDom = function() {
var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
canvas = this.getDomNode(DOM_CANVAS),
currImage = new Image();
// Set up event handlers for loading the image
var self = this;
currImage.onload = function() {
// Copy the image to the on-screen canvas
self.initCanvas(canvas,currImage.width,currImage.height,currImage);
// And also copy the current bitmap to the off-screen canvas
self.currCanvas = self.editWidget.renderer.renderTree.document.createElement("canvas");
self.initCanvas(self.currCanvas,currImage.width,currImage.height,currImage);
// Set the width and height input boxes
self.updateSize();
};
currImage.onerror = function() {
// Set the on-screen canvas size and clear it
self.initCanvas(canvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
// Set the off-screen canvas size and clear it
self.currCanvas = self.editWidget.renderer.renderTree.document.createElement("canvas");
self.initCanvas(self.currCanvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
// Set the width and height input boxes
self.updateSize();
}
// Get the current bitmap into an image object
currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
};
BitmapEditor.prototype.initCanvas = function(canvas,width,height,image) {
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
if(image) {
ctx.drawImage(image,0,0);
} else {
ctx.fillStyle = "#fff";
ctx.fillRect(0,0,canvas.width,canvas.height);
}
}
BitmapEditor.prototype.getDomNode = function(index) {
return this.editWidget.renderer.domNode.childNodes[index];
};
/*
** Update the input boxes with the actual size of the canvas
*/
BitmapEditor.prototype.updateSize = function() {
this.getDomNode(DOM_WIDTH).value = this.currCanvas.width;
this.getDomNode(DOM_HEIGHT).value = this.currCanvas.height;
};
/*
** Change the size of the canvas, preserving the current image
*/
BitmapEditor.prototype.changeCanvasSize = function(newWidth,newHeight) {
// Create and size a new canvas
var newCanvas = this.editWidget.renderer.renderTree.document.createElement("canvas");
this.initCanvas(newCanvas,newWidth,newHeight);
// Copy the old image
var ctx = newCanvas.getContext("2d");
ctx.drawImage(this.currCanvas,0,0);
// Set the new canvas as the current one
this.currCanvas = newCanvas;
// Set the size of the onscreen canvas
var canvas = this.getDomNode(DOM_CANVAS);
canvas.width = newWidth;
canvas.height = newHeight;
// Paint the onscreen canvas with the offscreen canvas
ctx = canvas.getContext("2d");
ctx.drawImage(this.currCanvas,0,0);
};
BitmapEditor.prototype.handleWidthChangeEvent = function(event) {
// Get the new width
var newWidth = parseInt(this.getDomNode(DOM_WIDTH).value,10);
// Update if necessary
if(newWidth > 0 && newWidth !== this.currCanvas.width) {
this.changeCanvasSize(newWidth,this.currCanvas.height);
}
// Update the input controls
this.updateSize();
};
BitmapEditor.prototype.handleHeightChangeEvent = function(event) {
// Get the new width
var newHeight = parseInt(this.getDomNode(DOM_HEIGHT).value,10);
// Update if necessary
if(newHeight > 0 && newHeight !== this.currCanvas.height) {
this.changeCanvasSize(this.currCanvas.width,newHeight);
}
// Update the input controls
this.updateSize();
};
BitmapEditor.prototype.handleTouchStartEvent = function(event) {
this.brushDown = true;
this.strokeStart(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
event.stopPropagation();
return false;
};
BitmapEditor.prototype.handleTouchMoveEvent = function(event) {
if(this.brushDown) {
this.strokeMove(event.touches[0].clientX,event.touches[0].clientY);
}
event.preventDefault();
event.stopPropagation();
return false;
};
BitmapEditor.prototype.handleTouchEndEvent = function(event) {
if(this.brushDown) {
this.brushDown = false;
this.strokeEnd();
}
event.preventDefault();
event.stopPropagation();
return false;
};
BitmapEditor.prototype.handleMouseDownEvent = function(event) {
this.strokeStart(event.clientX,event.clientY);
this.brushDown = true;
event.preventDefault();
event.stopPropagation();
return false;
};
BitmapEditor.prototype.handleMouseMoveEvent = function(event) {
if(this.brushDown) {
this.strokeMove(event.clientX,event.clientY);
event.preventDefault();
event.stopPropagation();
return false;
}
return true;
};
BitmapEditor.prototype.handleMouseUpEvent = function(event) {
if(this.brushDown) {
this.brushDown = false;
this.strokeEnd();
event.preventDefault();
event.stopPropagation();
return false;
}
return true;
};
BitmapEditor.prototype.adjustCoordinates = function(x,y) {
var canvas = this.getDomNode(DOM_CANVAS),
canvasRect = canvas.getBoundingClientRect(),
scale = canvas.width/canvasRect.width;
return {x: (x - canvasRect.left) * scale, y: (y - canvasRect.top) * scale};
};
BitmapEditor.prototype.strokeStart = function(x,y) {
// Start off a new stroke
this.stroke = [this.adjustCoordinates(x,y)];
};
BitmapEditor.prototype.strokeMove = function(x,y) {
var canvas = this.getDomNode(DOM_CANVAS),
ctx = canvas.getContext("2d"),
t;
// Add the new position to the end of the stroke
this.stroke.push(this.adjustCoordinates(x,y));
// Redraw the previous image
ctx.drawImage(this.currCanvas,0,0);
// Render the stroke
ctx.strokeStyle = "#ff0";
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.moveTo(this.stroke[0].x,this.stroke[0].y);
for(t=1; t<this.stroke.length-1; t++) {
var s1 = this.stroke[t],
s2 = this.stroke[t-1],
tx = (s1.x + s2.x)/2,
ty = (s1.y + s2.y)/2;
ctx.quadraticCurveTo(s2.x,s2.y,tx,ty);
}
ctx.stroke();
};
BitmapEditor.prototype.strokeEnd = function() {
// Copy the bitmap to the off-screen canvas
var canvas = this.getDomNode(DOM_CANVAS),
ctx = this.currCanvas.getContext("2d");
ctx.drawImage(canvas,0,0);
// Save the image into the tiddler
this.saveChanges();
};
BitmapEditor.prototype.saveChanges = function() {
var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
if(tiddler) {
// data URIs look like "data:<type>;base64,<text>"
var dataURL = this.getDomNode(DOM_CANVAS).toDataURL(tiddler.fields.type,1.0),
posColon = dataURL.indexOf(":"),
posSemiColon = dataURL.indexOf(";"),
posComma = dataURL.indexOf(","),
type = dataURL.substring(posColon+1,posSemiColon),
text = dataURL.substring(posComma+1);
var update = {type: type, text: text};
this.editWidget.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
};
/*
Note that the bitmap editor intentionally doesn't have a refreshInDom method to avoid the situation where a bitmap being editted is modified externally
*/
exports["image/jpg"] = BitmapEditor;
exports["image/jpeg"] = BitmapEditor;
exports["image/png"] = BitmapEditor;
exports["image/gif"] = BitmapEditor;
})();

View File

@ -1,219 +0,0 @@
/*\
title: $:/core/modules/widgets/edit/editors/texteditor.js
type: application/javascript
module-type: editor
A plain text editor
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var MIN_TEXT_AREA_HEIGHT = 100;
var TextEditor = function(editWidget,tiddlerTitle,fieldName,indexName) {
this.editWidget = editWidget;
this.tiddlerTitle = tiddlerTitle;
this.fieldName = fieldName;
this.indexName = indexName;
};
/*
Get the tiddler being edited and current value
*/
TextEditor.prototype.getEditInfo = function() {
var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
value;
if(this.fieldName) {
// Get the current tiddler and the field name
if(tiddler) {
// If we've got a tiddler, the value to display is the field string value
value = tiddler.getFieldString(this.fieldName);
} else {
// Otherwise, we need to construct a default value for the editor
switch(this.fieldName) {
case "text":
value = "Type the text for the tiddler '" + this.tiddlerTitle + "'";
break;
case "title":
value = this.tiddlerTitle;
break;
default:
value = "";
break;
}
value = this.editWidget.renderer.getAttribute("default",value);
}
} else {
value = this.editWidget.renderer.renderTree.wiki.extractTiddlerDataItem(this.tiddlerTitle,this.indexName,this.editWidget.renderer.getAttribute("default"));
}
return {tiddler: tiddler, value: value};
};
TextEditor.prototype.render = function() {
// Get the initial value of the editor
var editInfo = this.getEditInfo();
// Create the editor nodes
var node = {
type: "element",
attributes: {}
};
// Get the edit type associated with this field
var type = "input";
if(this.fieldName === "text") {
type = "textarea";
} else {
var fieldModule = $tw.Tiddler.fieldModules[this.fieldName];
if(fieldModule && fieldModule.editType) {
type = fieldModule.editType;
}
}
var type = this.editWidget.renderer.getAttribute("type",type);
switch(type) {
case "textarea":
node.tag = "textarea";
node.children = [{
type: "text",
text: editInfo.value
}];
break;
case "color":
node.tag = "input";
node.attributes.type = {type: "string", value: "color"};
node.attributes.value = {type: "string", value: editInfo.value};
break;
case "search":
node.tag = "input";
node.attributes.type = {type: "string", value: "search"};
node.attributes.value = {type: "string", value: editInfo.value};
break;
default: // "input"
node.tag = "input";
node.attributes.type = {type: "string", value: "text"};
node.attributes.value = {type: "string", value: editInfo.value};
break;
}
node.events = [
{name: "focus", handlerObject: this, handlerMethod: "handleFocusEvent"},
{name: "blur", handlerObject: this, handlerMethod: "handleBlurEvent"},
{name: "input", handlerObject: this, handlerMethod: "handleInputEvent"}
];
// Add a placeholder if specified
if(this.editWidget.renderer.hasAttribute("placeholder")) {
node.attributes.placeholder = {type: "string", value: this.editWidget.renderer.getAttribute("placeholder")};
}
// Set the element details
this.editWidget.tag = this.editWidget.renderer.parseTreeNode.isBlock ? "div" : "span";
this.editWidget.attributes = {
"class": "tw-edit-texteditor"
};
if(this.editWidget.renderer.hasAttribute("class")) {
this.editWidget.attributes["class"] += " " + this.editWidget.renderer.getAttribute("class");
}
if(this.editWidget.renderer.hasAttribute("style")) {
this.editWidget.attributes.style = this.editWidget.attributes.style || "";
this.editWidget.attributes.style += this.editWidget.renderer.getAttribute("style");
}
this.editWidget.children = this.editWidget.renderer.renderTree.createRenderers(this.editWidget.renderer,[node]);
};
TextEditor.prototype.setFocus = function() {
if(this.editWidget.renderer.hasAttribute("focusSet")) {
var title = this.editWidget.renderer.getAttribute("focusSet");
if(this.editWidget.renderer.getAttribute("qualifyTiddlerTitles") === "yes") {
title = title + "-" + this.editWidget.renderer.renderTree.getContextScopeId(this.editWidget.renderer.parentRenderer);
}
$tw.popup.triggerPopup({
domNode: this.editWidget.renderer.domNode,
title: title,
wiki: this.editWidget.renderer.renderTree.wiki,
force: true
});
}
};
TextEditor.prototype.handleFocusEvent = function(event) {
// this.saveChanges();
// this.fixHeight();
this.setFocus();
return true;
};
TextEditor.prototype.handleBlurEvent = function(event) {
// this.saveChanges();
return true;
};
TextEditor.prototype.handleInputEvent = function(event) {
this.saveChanges();
this.fixHeight();
return true;
};
TextEditor.prototype.saveChanges = function() {
var text = this.editWidget.children[0].domNode.value
if(this.fieldName) {
var tiddler = this.editWidget.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
if(!tiddler) {
tiddler = new $tw.Tiddler({title: this.tiddlerTitle});
}
var oldValue = tiddler.getFieldString(this.fieldName);
if(text !== oldValue) {
var update = {};
update[this.fieldName] = text;
this.editWidget.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
} else {
var data = this.editWidget.renderer.renderTree.wiki.getTiddlerData(this.tiddlerTitle,{});
if(data[this.indexName] !== text) {
data[this.indexName] = text;
this.editWidget.renderer.renderTree.wiki.setTiddlerData(this.tiddlerTitle,data);
}
}
};
TextEditor.prototype.fixHeight = function() {
var self = this;
if(this.editWidget.children[0].domNode && this.editWidget.children[0].domNode.type === "textarea") {
$tw.utils.nextTick(function() {
// Resize the textarea to fit its content
var textarea = self.editWidget.children[0].domNode,
scrollPosition = $tw.utils.getScrollPosition(),
scrollTop = scrollPosition.y;
// Set its height to auto so that it snaps to the correct height
textarea.style.height = "auto";
// Calculate the revised height
var newHeight = Math.max(textarea.scrollHeight + textarea.offsetHeight - textarea.clientHeight,MIN_TEXT_AREA_HEIGHT);
// Only try to change the height if it has changed
if(newHeight !== textarea.offsetHeight) {
textarea.style.height = newHeight + "px";
// Make sure that the dimensions of the textarea are recalculated
$tw.utils.forceLayout(textarea);
// Check that the scroll position is still visible before trying to scroll back to it
scrollTop = Math.min(scrollTop,self.editWidget.renderer.renderTree.document.body.scrollHeight - window.innerHeight);
window.scrollTo(scrollPosition.x,scrollTop);
}
});
}
};
TextEditor.prototype.postRenderInDom = function() {
this.fixHeight();
};
TextEditor.prototype.refreshInDom = function() {
if(this.editWidget.renderer.renderTree.document.activeElement !== this.editWidget.children[0].domNode) {
var editInfo = this.getEditInfo();
this.editWidget.children[0].domNode.value = editInfo.value;
}
// Fix the height if needed
this.fixHeight();
};
exports["text/vnd.tiddlywiki"] = TextEditor;
exports["text/plain"] = TextEditor;
})();

View File

@ -1,51 +0,0 @@
/*\
title: $:/core/modules/widgets/encrypt.js
type: application/javascript
module-type: widget
Implements the encrypt widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var EncryptWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
EncryptWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.filter = this.renderer.getAttribute("filter");
// Check whether we've got an encryption password
var isEncrypted = $tw.crypto.hasPassword();
// Encrypt the filtered tiddlers
var tiddlers = this.renderer.renderTree.wiki.filterTiddlers(this.filter),
json = {},
self = this;
$tw.utils.each(tiddlers,function(title) {
var tiddler = self.renderer.renderTree.wiki.getTiddler(title),
jsonTiddler = {};
for(var f in tiddler.fields) {
jsonTiddler[f] = tiddler.getFieldString(f);
}
json[title] = jsonTiddler;
});
var encryptedText = $tw.utils.htmlEncode($tw.crypto.encrypt(JSON.stringify(json)));
// Set the return element
this.tag = "pre";
this.attributes ={"class": "tw-encrypt"};
this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
type: "text",
text: encryptedText
}]);
};
exports.encrypt = EncryptWidget;
})();

View File

@ -1,37 +0,0 @@
/*\
title: $:/core/modules/widgets/error.js
type: application/javascript
module-type: widget
The error widget displays an error message.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ErrorWidget = function(renderer,errorMessage) {
// Save state
this.renderer = renderer;
this.errorMessage = errorMessage;
// Generate child nodes
this.generate();
};
ErrorWidget.prototype.generate = function() {
// Set the element details
this.tag = "span";
this.attributes = {
"class": "tw-error-widget"
};
this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
type: "text",
text: this.errorMessage
}]);
};
exports.error = ErrorWidget;
})();

View File

@ -1,87 +0,0 @@
/*\
title: $:/core/modules/widgets/fieldmangler.js
type: application/javascript
module-type: widget
The fieldmangler widget modifies the fields of the current tiddler in response to messages.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var FieldManglerWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
FieldManglerWidget.prototype.generate = function() {
var self = this;
// Get parameters from our attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
// Set the element
this.tag = "div";
this.attributes = {
"class": "tw-fieldmangler"
};
// Set event handlers
this.events = [
{name: "tw-remove-field", handlerObject: this, handlerMethod: "handleRemoveFieldEvent"},
{name: "tw-add-field", handlerObject: this, handlerMethod: "handleAddFieldEvent"},
{name: "tw-remove-tag", handlerObject: this, handlerMethod: "handleRemoveTagEvent"},
{name: "tw-add-tag", handlerObject: this, handlerMethod: "handleAddTagEvent"}
];
// Render the children
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
};
FieldManglerWidget.prototype.handleRemoveFieldEvent = function(event) {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
deletion = {};
deletion[event.param] = undefined;
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,deletion));
return true;
};
FieldManglerWidget.prototype.handleAddFieldEvent = function(event) {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
if(tiddler && typeof event.param === "string" && event.param !== "" && !$tw.utils.hop(tiddler.fields,event.param)) {
var addition = {};
addition[event.param] = "";
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,addition));
}
return true;
};
FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
if(tiddler && tiddler.fields.tags) {
var p = tiddler.fields.tags.indexOf(event.param);
if(p !== -1) {
var modification = {};
modification.tags = (tiddler.fields.tags || []).slice(0);
modification.tags.splice(p,1);
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
}
}
return true;
};
FieldManglerWidget.prototype.handleAddTagEvent = function(event) {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
if(tiddler && typeof event.param === "string" && event.param !== "") {
var modification = {};
modification.tags = (tiddler.fields.tags || []).slice(0);
$tw.utils.pushTop(modification.tags,event.param);
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
}
return true;
};
exports.fieldmangler = FieldManglerWidget;
})();

View File

@ -1,94 +0,0 @@
/*\
title: $:/core/modules/widgets/fields.js
type: application/javascript
module-type: widget
The fields widget displays the fields of a tiddler through a text substitution template.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var FieldsWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
FieldsWidget.prototype.generate = function() {
// Get parameters from our attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
this.template = this.renderer.getAttribute("template");
this.exclude = this.renderer.getAttribute("exclude");
this.stripTitlePrefix = this.renderer.getAttribute("stripTitlePrefix","no") === "yes";
// Get the tiddler we're displaying
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle);
// Get the exclusion list
var exclude;
if(this.exclude) {
exclude = this.exclude.split(" ");
} else {
exclude = ["text"];
}
// Compose the template
var text = [];
if(this.template && tiddler) {
var fields = [];
for(var fieldName in tiddler.fields) {
if(exclude.indexOf(fieldName) === -1) {
fields.push(fieldName);
}
}
fields.sort();
for(var f=0; f<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)
}
}
}
// Set the element
this.tag = "pre";
this.attributes = {
"class": "tw-fields"
};
// Set up the attributes for the wrapper element
var classes = [];
if(this.renderer.hasAttribute("class")) {
$tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" "));
}
if(classes.length > 0) {
this.attributes["class"] = classes.join(" ");
}
if(this.renderer.hasAttribute("style")) {
this.attributes.style = this.renderer.getAttribute("style");
}
if(this.renderer.hasAttribute("tooltip")) {
this.attributes.title = this.renderer.getAttribute("tooltip");
}
// Create the renderers for the wrapper and the children
this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
type: "text",
text: text.join("")
}]);
};
exports.fields = FieldsWidget;
})();

View File

@ -1,109 +0,0 @@
/*\
title: $:/core/modules/widgets/grid.js
type: application/javascript
module-type: widget
The grid widget.
This example renders a table made up of tiddlers titled `MySheet_A_1`, `MySheet_A_2`, `MySheet_A_3`, ... , `MySheet_B_1`, `MySheet_B_2`, `MySheet_B_3` etc.
```
<$grid prefix="MySheet" rows=20 cols=20/>
```
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var GridWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate widget elements
this.generate();
};
GridWidget.prototype.generate = function() {
// Get our attributes
this.prefix = this.renderer.getAttribute("prefix","Grid");
this.rows = parseInt(this.renderer.getAttribute("rows","10"),10);
this.cols = parseInt(this.renderer.getAttribute("cols","10"),10);
this["class"] = this.renderer.getAttribute("class");
// Set up the classes
var classes = ["tw-grid-frame"];
if(this["class"]) {
$tw.utils.pushTop(classes,this["class"]);
}
// Create the grid table element
this.tag = "div";
this.attributes = {
"class": classes.join(" ")
};
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.generateTable());
};
GridWidget.prototype.generateTable = function() {
var rows = [];
for(var row=0; row<this.rows; row++) {
var tr = {
type: "element",
tag: "tr",
children: []
};
rows.push(tr);
for(var col=0; col<this.cols; col++) {
var td = {
type: "element",
tag: "td",
children: [{
type: "element",
tag: "$transclude",
attributes: {
title: {type: "string", value: this.getTableCellTitle(col,row)}
}
}]
};
tr.children.push(td);
}
}
return [{
type: "element",
tag: "table",
children: [{
type: "element",
tag: "tbody",
children: rows
}]
}];
};
GridWidget.prototype.getTableCellTitle = function(col,row) {
var c = String.fromCharCode(col % 26 + "A".charCodeAt(0));
col = Math.floor(col/26);
while(col>0) {
c = String.fromCharCode(col % 26 + "A".charCodeAt(0) - 1) + c;
col = Math.floor(col/26);
}
return this.prefix + "_" + c + "_" + (row + 1);
};
GridWidget.prototype.postRenderInDom = function() {
};
GridWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Reexecute the widget if any of our attributes have changed
if(true) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
}
};
exports.grid = GridWidget;
})();

View File

@ -1,256 +0,0 @@
/*\
title: $:/core/modules/widgets/import.js
type: application/javascript
module-type: widget
Implements the import widget.
```
<$import>
Import using the "browse..." button or drag files onto this text
</$import>
```
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ImportWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
ImportWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.browse = this.renderer.getAttribute("browse","yes");
this["class"] = this.renderer.getAttribute("class");
// Compute classes
var classes = ["tw-import"];
if(this["class"]) {
$tw.utils.pushTop(classes,this["class"]);
}
// Create the file input and container elements
var fileInput = {
type: "element",
tag: "input",
attributes: {
type: {type: "string", value: "file"},
style: {type: "string", value: this.browse === "no" ? "display: none;" : "display: block;"}
},
events: [{name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"}]
},
container = {
type: "element",
tag: "div",
children: this.renderer.parseTreeNode.children,
events: [
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"},
{name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"}]
};
// Set the return element
this.tag = "div";
this.attributes = {
"class": classes.join(" ")
};
this.children = this.renderer.renderTree.createRenderers(this.renderer,[fileInput,container]);
};
ImportWidget.prototype.handleChangeEvent = function(event) {
event.stopPropagation();
this.importFiles(event.target.files);
};
ImportWidget.prototype.handleDragEnterEvent = function(event) {
// We count enter/leave events
this.dragEnterCount = (this.dragEnterCount || 0) + 1;
// If we're entering for the first time we need to apply highlighting
if(this.dragEnterCount === 1) {
$tw.utils.addClass(this.renderer.domNode,"tw-dragover");
}
// Tell the browser that we're ready to handle the drop
event.preventDefault();
// Tell the browser not to ripple the drag up to any parent drop handlers
event.stopPropagation();
};
ImportWidget.prototype.handleDragOverEvent = function(event) {
// Tell the browser that we're still interested in the drop
event.preventDefault();
event.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy
};
ImportWidget.prototype.handleDragLeaveEvent = function(event) {
// Reduce the enter count
this.dragEnterCount = (this.dragEnterCount || 0) - 1;
// Remove highlighting if we're leaving externally
if(this.dragEnterCount <= 0) {
$tw.utils.removeClass(this.renderer.domNode,"tw-dragover");
}
};
ImportWidget.prototype.handleDropEvent = function(event) {
var dataTransfer = event.dataTransfer;
// Reset the enter count
this.dragEnterCount = 0;
// Remove highlighting
$tw.utils.removeClass(this.renderer.domNode,"tw-dragover");
// Try to import the various data types we understand
this.importData(dataTransfer);
// Import any files in the drop
this.importFiles(dataTransfer.files);
// Tell the browser that we handled the drop
event.preventDefault();
// Stop the drop ripple up to any parent handlers
event.stopPropagation();
};
ImportWidget.prototype.handlePasteEvent = function(event) {
// Let the browser handle it if we're in a textarea or input box
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1) {
var self = this,
items = event.clipboardData.items;
// Enumerate the clipboard items
for(var t = 0; t<items.length; t++) {
var item = items[t];
if(item.kind === "file") {
// Import any files
var file = item.getAsFile();
this.importFiles([file]);
} else if(item.kind === "string") {
// Create tiddlers from string items
item.getAsString(function(str) {
var fields = {
title: self.generateTitle("Untitled"),
text: str
};
self.storeTiddler(fields);
self.openTiddler(fields.title);
});
}
}
// Tell the browser that we've handled the paste
event.stopPropagation();
event.preventDefault();
}
};
ImportWidget.prototype.openTiddler = function(title) {
$tw.utils.dispatchCustomEvent(this.renderer.domNode,"tw-navigate",{
navigateTo: title,
navigateFromNode: this.renderer.domNode,
navigateFromClientRect: this.renderer.domNode.getBoundingClientRect()
});
};
ImportWidget.prototype.importData = function(dataTransfer) {
for(var t=0; t<this.importDataTypes.length; t++) {
var dataType = this.importDataTypes[t];
var data = dataTransfer.getData(dataType.type);
if(data !== "") {
var fields = dataType.handler(data);
if(!fields.title) {
fields.title = this.generateTitle("Untitled");
}
this.storeTiddler(fields);
this.openTiddler(fields.title);
return;
}
};
};
ImportWidget.prototype.importDataTypes = [
{type: "text/vnd.tiddler", handler: function(data) {
return JSON.parse(data);
}},
{type: "text/plain", handler: function(data) {
return {
text: data
};
}},
{type: "text/uri-list", handler: function(data) {
return {
text: data
};
}}
];
ImportWidget.prototype.importFiles = function(files) {
var self = this,
importFile = function(file) {
// Get the type, falling back to the filename extension
var type = file.type;
if(type === "" || !type) {
var dotPos = file.name.lastIndexOf(".");
if(dotPos !== -1) {
var fileExtensionInfo = $tw.config.fileExtensionInfo[file.name.substr(dotPos)];
if(fileExtensionInfo) {
type = fileExtensionInfo.type;
}
}
}
// Figure out if we're reading a binary file
var contentTypeInfo = $tw.config.contentTypeInfo[type],
isBinary = contentTypeInfo ? contentTypeInfo.encoding === "base64" : false;
// Create the FileReader
var reader = new FileReader();
reader.onload = function(event) {
// Deserialise the file contents
var fields = {
title: file.name || "Untitled",
type: type};
// Are we binary?
if(isBinary) {
var commaPos = event.target.result.indexOf(",");
if(commaPos !== -1) {
fields.text = event.target.result.substr(commaPos+1);
self.storeTiddler(fields);
self.openTiddler(fields.title);
}
} else {
var tiddlers = self.renderer.renderTree.wiki.deserializeTiddlers(type,event.target.result,fields);
if(!tiddlers) {
console.log("No tiddlers found in file ",file.name);
} else {
$tw.utils.each(tiddlers,function(tiddlerFields) {
tiddlerFields.title = self.generateTitle(tiddlerFields.title);
self.storeTiddler(tiddlerFields);
self.openTiddler(tiddlerFields.title);
});
}
}
};
if(isBinary) {
reader.readAsDataURL(file);
} else {
reader.readAsText(file);
}
};
for(var f=0; f<files.length; f++) {
importFile(files[f]);
};
};
ImportWidget.prototype.storeTiddler = function(fields) {
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(fields,this.renderer.renderTree.wiki.getModificationFields()));
};
ImportWidget.prototype.generateTitle = function(baseTitle) {
var c = 0;
do {
var title = baseTitle + (c ? " " + (c + 1) : "");
c++;
} while(this.renderer.renderTree.wiki.tiddlerExists(title));
return title;
};
exports.import = ImportWidget;
})();

View File

@ -1,115 +0,0 @@
/*\
title: $:/core/modules/widgets/info.js
type: application/javascript
module-type: widget
Implements the info widget that displays various information about a specified tiddler.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var InfoWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
InfoWidget.types = {};
InfoWidget.types.changecount = function(options) {
var text = options.wiki.getChangeCount(options.widget.renderer.tiddlerTitle);
return [{type: "text", text: text}];
};
InfoWidget.types.currentfield = function(options) {
var fieldName = options.widget.renderer.renderTree.getContextVariable(options.widget.renderer,"field","text");
return [{type: "text", text: fieldName}];
};
var FIELD_DESCRIPTION_PREFIX = "$:/docs/fields/";
InfoWidget.types.currentfielddescription = function(options) {
var fieldName = options.widget.renderer.renderTree.getContextVariable(options.widget.renderer,"field","text"),
descriptionTitle = FIELD_DESCRIPTION_PREFIX + fieldName;
return [{
type: "element",
tag: "$transclude",
isBlock: false,
attributes: {
title: {type: "string", value: descriptionTitle}
}
}];
};
var MODULE_TYPE_DESCRIPTION_PREFIX = "$:/docs/moduletypes/";
/*
Return a list of all the currently loaded modules grouped by type
*/
InfoWidget.types.modules = function(options) {
var output = [],
types = [];
// Collect and sort the module types
$tw.utils.each($tw.modules.types,function(moduleInfo,type) {
types.push(type);
});
types.sort();
// Output the module types
$tw.utils.each(types,function(moduleType) {
// Heading
output.push({type: "element", tag: "h2", children: [
{type: "text", text: moduleType}
]})
// Description
output.push({
type: "element",
tag: "$transclude",
isBlock: false,
attributes: {
title: {type: "string", value: MODULE_TYPE_DESCRIPTION_PREFIX + moduleType}
}
});
// List each module
var list = {type: "element", tag: "ul", children: []},
modules = [];
$tw.utils.each($tw.modules.types[moduleType],function(moduleInfo,moduleName) {
var listItem = {type: "element", tag: "li", children: [
{type: "element", tag: "$link", attributes: {
to: {type: "string", value: moduleName}
}, children: [
{type: "text", text: moduleName}
]}
]}
list.children.push(listItem);
});
output.push(list);
});
return output;
};
InfoWidget.prototype.generate = function() {
// Get attributes
this.type = this.renderer.getAttribute("type","changecount").toLowerCase();
// Get the appropriate value for the current tiddler
var value = [],
fn = InfoWidget.types[this.type];
if(fn) {
value = fn({
wiki: this.renderer.renderTree.wiki,
widget: this
});
}
// Set the element
this.tag = "span";
this.attributes = {};
this.children = this.renderer.renderTree.createRenderers(this.renderer,value);
};
exports.info = InfoWidget;
})();

View File

@ -1,203 +0,0 @@
/*\
title: $:/core/modules/widgets/link.js
type: application/javascript
module-type: widget
Implements the link widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var isLinkExternal = function(to) {
var externalRegExp = /(?:file|http|https|mailto|ftp|irc|news|data|skype):[^\s'"]+(?:\/|\b)/i;
return externalRegExp.test(to);
};
var LinkWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
LinkWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.to = this.renderer.getAttribute("to",this.renderer.tiddlerTitle);
this.hover = this.renderer.getAttribute("hover");
this.qualifyHoverTitles = this.renderer.getAttribute("qualifyHoverTitles");
// Qualify the hover tiddler title if needed
if(this.qualifyHoverTitles) {
this.hover = this.hover + "-" + this.renderer.renderTree.getContextScopeId(this.renderer.parentRenderer);
}
// Determine the default link characteristics
this.isExternal = isLinkExternal(this.to);
if(!this.isExternal) {
this.isMissing = !this.renderer.renderTree.wiki.tiddlerExists(this.to);
this.isShadow = this.renderer.renderTree.wiki.isShadowTiddler(this.to);
}
// Compose the link
var classes = ["tw-tiddlylink"]
if(this.isExternal) {
$tw.utils.pushTop(classes,"tw-tiddlylink-external");
} else {
$tw.utils.pushTop(classes,"tw-tiddlylink-internal");
if(this.isShadow) {
$tw.utils.pushTop(classes,"tw-tiddlylink-shadow");
}
if(this.isMissing && !this.isShadow) {
$tw.utils.pushTop(classes,"tw-tiddlylink-missing");
} else {
if(!this.isMissing) {
$tw.utils.pushTop(classes,"tw-tiddlylink-resolves");
}
}
}
var events = [
{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"},
{name: "dragstart", handlerObject: this, handlerMethod: "handleDragStartEvent"},
{name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"}
];
if(this.hover) {
events.push({name: "mouseover", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
events.push({name: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
}
// Get the value of the tw-wikilinks configuration macro
var wikiLinksMacro = this.renderer.renderTree.findMacroDefinition(this.renderer.parentRenderer,"tw-wikilinks"),
useWikiLinks = wikiLinksMacro ? !(wikiLinksMacro.text.trim() === "no") : true;
// Set up the element
if(useWikiLinks) {
this.tag = "a";
this.attributes = {
"class": classes.join(" ")
};
if(this.isExternal) {
this.attributes.href = this.to;
} else {
var wikiLinkTemplateMacro = this.renderer.renderTree.findMacroDefinition(this.renderer.parentRenderer,"tw-wikilink-template"),
wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.text.trim() : "#$uri_encoded$";
this.wikiLinkText = wikiLinkTemplate.replace("$uri_encoded$",encodeURIComponent(this.to));
this.wikiLinkText = this.wikiLinkText.replace("$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
this.attributes.href = this.wikiLinkText;
}
this.events = events;
} else {
this.tag = "span";
}
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
};
LinkWidget.prototype.handleClickEvent = function(event) {
if(isLinkExternal(this.to)) {
event.target.setAttribute("target","_blank");
return true;
} else {
var bounds = this.renderer.domNode.getBoundingClientRect();
$tw.utils.dispatchCustomEvent(event.target,"tw-navigate",{
navigateTo: this.to,
navigateFromNode: this,
navigateFromClientRect: {
top: bounds.top,
left: bounds.left,
width: bounds.width,
right: bounds.right,
bottom: bounds.bottom,
height: bounds.height
}
});
event.preventDefault();
event.stopPropagation();
return false;
}
};
LinkWidget.prototype.handleMouseOverOrOutEvent = function(event) {
if(this.hover) {
$tw.popup.triggerPopup({
title: this.hover,
domNode: this.renderer.domNode,
wiki: this.renderer.renderTree.wiki
});
}
event.preventDefault();
return false;
};
LinkWidget.prototype.handleDragStartEvent = function(event) {
if(this.to) {
// Set the dragging class on the element being dragged
$tw.utils.addClass(event.target,"tw-tiddlylink-dragging");
// Create the drag image elements
this.dragImage = this.renderer.renderTree.document.createElement("div");
this.dragImage.className = "tw-tiddler-dragger";
var inner = this.renderer.renderTree.document.createElement("div");
inner.className = "tw-tiddler-dragger-inner";
inner.appendChild(this.renderer.renderTree.document.createTextNode(this.to));
this.dragImage.appendChild(inner);
this.renderer.renderTree.document.body.appendChild(this.dragImage);
// Astoundingly, we need to cover the dragger up: http://www.kryogenix.org/code/browser/custom-drag-image.html
var bounds = this.dragImage.firstChild.getBoundingClientRect(),
cover = this.renderer.renderTree.document.createElement("div");
cover.className = "tw-tiddler-dragger-cover";
cover.style.left = (bounds.left - 8) + "px";
cover.style.top = (bounds.top - 8) + "px";
cover.style.width = (bounds.width + 16) + "px";
cover.style.height = (bounds.height + 16) + "px";
this.dragImage.appendChild(cover);
// Set the data transfer properties
var dataTransfer = event.dataTransfer;
dataTransfer.effectAllowed = "copy";
dataTransfer.setDragImage(this.dragImage.firstChild,-16,-16);
dataTransfer.clearData();
dataTransfer.setData("text/vnd.tiddler",this.renderer.renderTree.wiki.getTiddlerAsJson(this.to));
dataTransfer.setData("text/plain",this.renderer.renderTree.wiki.getTiddlerText(this.to,""));
event.stopPropagation();
} else {
event.preventDefault();
}
};
LinkWidget.prototype.handleDragEndEvent = function(event) {
// Remove the dragging class on the element being dragged
$tw.utils.removeClass(event.target,"tw-tiddlylink-dragging");
// Delete the drag image element
if(this.dragImage) {
this.dragImage.parentNode.removeChild(this.dragImage);
}
};
LinkWidget.prototype.postRenderInDom = function() {
// Add the draggable attribute to links (we don't include it in the static HTML representation)
if(this.renderer.domNode.tagName === "A") {
this.renderer.domNode.setAttribute("draggable",true);
}
};
LinkWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Set the class for missing tiddlers
if(this.targetTitle && changedTiddlers[this.targetTitle]) {
$tw.utils.toggleClass(this.renderer.domNode,"tw-tiddler-missing",!this.renderer.renderTree.wiki.tiddlerExists(this.targetTitle));
}
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.to || changedAttributes.hover || (this.to && changedTiddlers[this.to]) || (this.hover && changedTiddlers[this.hover])) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.link = LinkWidget;
})();

View File

@ -1,71 +0,0 @@
/*\
title: $:/core/modules/widgets/linkcatcher.js
type: application/javascript
module-type: widget
Implements the linkcatcher widget. It intercepts navigation events from its children, preventing normal navigation, and instead stores the name of the target tiddler in the text reference specified in the `to` attribute.
Using the linkcatcher widget allows the linking mechanism to be used for tasks like selecting the current theme tiddler from a list.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var LinkCatcherWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
LinkCatcherWidget.prototype.generate = function() {
// Get our attributes
this.to = this.renderer.getAttribute("to");
this.message = this.renderer.getAttribute("message");
this.set = this.renderer.getAttribute("set");
this.setTo = this.renderer.getAttribute("setTo");
// Set the element
this.tag = "div";
this.attributes = {
"class": "tw-linkcatcher"
};
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
this.events = [
{name: "tw-navigate", handlerObject: this, handlerMethod: "handleNavigateEvent"}
];
};
LinkCatcherWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
};
// Navigate to a specified tiddler
LinkCatcherWidget.prototype.handleNavigateEvent = function(event) {
if(this.to) {
this.renderer.renderTree.wiki.setTextReference(this.to,event.navigateTo,this.renderer.tiddlerTitle);
}
if(this.message) {
$tw.utils.dispatchCustomEvent(this.renderer.domNode,this.message,{
param: event.navigateTo,
tiddlerTitle: this.renderer.tiddlerTitle
});
}
if(this.set) {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.set);
this.renderer.renderTree.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: this.set, text: this.setTo}));
}
event.stopPropagation();
return false;
};
exports.linkcatcher = LinkCatcherWidget;
})();

View File

@ -1,408 +0,0 @@
/*\
title: $:/core/modules/widgets/list/list.js
type: application/javascript
module-type: widget
The list widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ListWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Initialise the listviews if they've not been done already
if(!this.listViews) {
ListWidget.prototype.listViews = {};
$tw.modules.applyMethods("listview",this.listViews);
}
// Generate widget elements
this.generate();
};
var typeInfoByType = {
plain: {
frame: {
block: "div", inline: "span"
},
member: {
block: "div", inline: "span"
}
},
ul: {
frame: {
block: "ul", inline: "ul"
},
member: {
block: "li", inline: "li"
}
},
ol: {
frame: {
block: "ol", inline: "ol"
},
member: {
block: "li", inline: "li"
}
}
};
ListWidget.prototype.generate = function() {
// Get our attributes
this.macro = this.renderer.getAttribute("macro");
this.type = this.renderer.getAttribute("type","plain");
this.itemClass = this.renderer.getAttribute("itemClass");
this.template = this.renderer.getAttribute("template");
this.editTemplate = this.renderer.getAttribute("editTemplate");
this.emptyMessage = this.renderer.getAttribute("emptyMessage");
this["class"] = this.renderer.getAttribute("class");
// Get our type information
this.typeInfo = typeInfoByType[this.type] || typeInfoByType.plain;
// Set up the classes
var classes = ["tw-list-frame"];
if(this["class"]) {
$tw.utils.pushTop(classes,this["class"]);
}
// Get the list of tiddlers object
this.getTiddlerList();
// Create the list
var listMembers = [];
if(this.list.length === 0) {
// Check for an empty list
listMembers = [this.getEmptyMessage()];
} else {
// Create the list
for(var t=0; t<this.list.length; t++) {
listMembers.push(this.createListElement(this.list[t]));
}
}
// Create the list frame element
this.tag = this.renderer.parseTreeNode.isBlock ? this.typeInfo.frame.block : this.typeInfo.frame.inline;
this.attributes = {
"class": classes.join(" ")
};
this.children = this.renderer.renderTree.createRenderers(this.renderer,listMembers);
};
ListWidget.prototype.getTiddlerList = function() {
var filter;
if(this.renderer.hasAttribute("filter")) {
filter = this.renderer.getAttribute("filter");
}
if(!filter) {
filter = "[!is[system]]";
}
this.list = this.renderer.renderTree.wiki.filterTiddlers(filter,this.renderer.tiddlerTitle);
};
/*
Create and execute the nodes representing the empty message
*/
ListWidget.prototype.getEmptyMessage = function() {
return {
type: "element",
tag: "span",
children: this.renderer.renderTree.wiki.parseText("text/vnd.tiddlywiki",this.emptyMessage,{parseAsInline: true}).tree
};
};
/*
Create a list element representing a given tiddler
*/
ListWidget.prototype.createListElement = function(title) {
// Define an event handler that adds navigation information to the event
var handleEvent = function(event) {
event.navigateFromTitle = title;
return true;
},
classes = ["tw-list-element"];
// Add any specified classes
if(this.itemClass) {
$tw.utils.pushTop(classes,this.itemClass);
}
// Return the list element
return {
type: "element",
tag: this.renderer.parseTreeNode.isBlock ? this.typeInfo.member.block : this.typeInfo.member.inline,
attributes: {
"class": {type: "string", value: classes.join(" ")}
},
children: [this.createListElementParseTree(title)],
events: [
{name: "tw-navigate", handlerFunction: handleEvent},
{name: "tw-edit-tiddler", handlerFunction: handleEvent},
{name: "tw-save-tiddler", handlerFunction: handleEvent},
{name: "tw-close-tiddler", handlerFunction: handleEvent},
{name: "tw-new-tiddler", handlerFunction: handleEvent}
]
};
};
/*
Create the parse tree nodes needed to represent a given list element
*/
ListWidget.prototype.createListElementParseTree = function(title) {
if(this.macro) {
return this.createListElementMacro(title);
} else {
return this.createListElementTransclusion(title);
}
};
/*
Create a macro call to represent a list element
*/
ListWidget.prototype.createListElementMacro = function(title) {
// Create the macrocall rendertree node
return {
type: "macrocall",
name: this.macro,
params: [
{name: "title", value: title}
]
};
};
/*
Create a transclusion to represent a list element
*/
ListWidget.prototype.createListElementTransclusion = function(title) {
// Check if the tiddler is a draft
var tiddler = this.renderer.renderTree.wiki.getTiddler(title),
isDraft = tiddler ? tiddler.hasField("draft.of") : false;
// Figure out the template to use
var template = this.template,
templateTree = undefined;
if(isDraft && this.editTemplate) {
template = this.editTemplate;
}
// Check for not having a template
if(!template) {
if(this.renderer.parseTreeNode.children && this.renderer.parseTreeNode.children.length > 0) {
// Use our content as the template
templateTree = this.renderer.parseTreeNode.children;
} else {
// Use default content
templateTree = [{
type: "element",
tag: "$view",
attributes: {
field: {type: "string", value: "title"},
format: {type: "string", value: "link"}
}
}];
}
}
// Create the element widgets
if(this.renderer.hasAttribute("hackTemplate")) {
return {
type: "element",
tag: "$transclude",
isBlock: this.renderer.parseTreeNode.isBlock,
attributes: {
title: {type: "string", value: title}
}
};
} else {
if(!templateTree) {
templateTree = [{
type: "element",
tag: "$transclude",
attributes: {
title: {type: "string", value: template}
},
children: templateTree
}];
}
return {
type: "element",
tag: "$tiddler",
isBlock: this.renderer.parseTreeNode.isBlock,
attributes: {
title: {type: "string", value: title}
},
children: templateTree
};
}
};
/*
Remove a list element from the list, along with the attendant DOM nodes
*/
ListWidget.prototype.removeListElement = function(index) {
// Get the list element
var listElement = this.children[index];
// Invoke the listview to animate the removal
if(this.listview && this.listview.remove) {
if(!this.listview.remove(index)) {
// Only delete the DOM element if the listview.remove() returned false
listElement.domNode.parentNode.removeChild(listElement.domNode);
}
} else {
// Always remove the DOM node if we didn't invoke the listview
listElement.domNode.parentNode.removeChild(listElement.domNode);
}
// Then delete the actual renderer node
this.children.splice(index,1);
};
/*
Return the index of the list element that corresponds to a particular title
startIndex: index to start search (use zero to search from the top)
title: tiddler title to seach for
*/
ListWidget.prototype.findListElementByTitle = function(startIndex,title) {
var testNode = this.macro ? function(node) {
// We're looking for a macro list element
return node.widget.children[0].parseTreeNode.params[0].value === title;
} : (this.renderer.hasAttribute("hackTemplate") ? function(node) {
// We're looking for a transclusion list element
return node.widget.children[0].attributes.title === title;
} : function(node) {
// We're looking for a transclusion list element
return node.widget.children[0].attributes.title === title;
});
// Search for the list element
while(startIndex < this.children.length) {
if(testNode(this.children[startIndex])) {
return startIndex;
}
startIndex++;
}
return undefined;
};
ListWidget.prototype.postRenderInDom = function() {
this.listview = this.chooseListView();
this.history = [];
};
/*
Select the appropriate list viewer
*/
ListWidget.prototype.chooseListView = function() {
// Instantiate the list view
var listviewName = this.renderer.getAttribute("listview");
var ListView = this.listViews[listviewName];
return ListView ? new ListView(this) : null;
};
ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Reexecute the widget if any of our attributes have changed
if(changedAttributes.itemClass || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.type || changedAttributes.filter || changedAttributes.template || changedAttributes.history || changedAttributes.listview) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// Handle any changes to the list, and refresh any nodes we're reusing
this.handleListChanges(changedTiddlers);
// Update the history list
var history = this.renderer.getAttribute("history");
if(history && changedTiddlers[history]) {
this.handleHistoryChanges();
}
}
};
ListWidget.prototype.handleListChanges = function(changedTiddlers) {
var t,
prevListLength = this.list.length,
self = this;
// Get the list of tiddlers, having saved the previous length
this.getTiddlerList();
// Check if the list is empty
if(this.list.length === 0) {
// Check if it was empty before
if(prevListLength === 0) {
// If so, just refresh the empty message
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
return;
} else {
// If the list wasn't empty before, empty it
for(t=prevListLength-1; t>=0; t--) {
this.removeListElement(t);
}
// Insert the empty message
this.children = this.renderer.renderTree.createRenderers(this.renderer,[this.getEmptyMessage()]);
$tw.utils.each(this.children,function(node) {
if(node.renderInDom) {
self.renderer.domNode.appendChild(node.renderInDom());
}
});
return;
}
} else {
// If it is not empty now, but was empty previously, then remove the empty message
if(prevListLength === 0) {
this.removeListElement(0);
}
}
// Step through the list and adjust our child list elements appropriately
for(t=0; t<this.list.length; t++) {
// Check to see if the list element is already there
var index = this.findListElementByTitle(t,this.list[t]);
if(index === undefined) {
// The list element isn't there, so we need to insert it
this.children.splice(t,0,this.renderer.renderTree.createRenderer(this.renderer,this.createListElement(this.list[t])));
var before = this.renderer.domNode.childNodes[t],
newNode = this.children[t].renderInDom();
if(before) {
this.renderer.domNode.insertBefore(newNode,before);
} else {
this.renderer.domNode.appendChild(newNode);
}
// Ask the listview to animate the insertion
if(this.listview && this.listview.insert) {
this.listview.insert(t);
}
} else {
// Delete any list elements preceding the one we want
for(var n=index-1; n>=t; n--) {
this.removeListElement(n);
}
// Refresh the node we're reusing
this.children[t].refreshInDom(changedTiddlers);
}
}
// Remove any left over elements
for(t=this.children.length-1; t>=this.list.length; t--) {
this.removeListElement(t);
}
};
/*
Handle any changes to the history list
*/
ListWidget.prototype.handleHistoryChanges = function() {
// Get the history data
var historyAtt = this.renderer.getAttribute("history"),
newHistory = this.renderer.renderTree.wiki.getTiddlerData(historyAtt,[]);
// Ignore any entries of the history that match the previous history
var entry = 0;
while(entry < newHistory.length && entry < this.history.length && newHistory[entry].title === this.history[entry].title) {
entry++;
}
// Navigate forwards to each of the new tiddlers
while(entry < newHistory.length) {
if(this.listview && this.listview.navigateTo) {
this.listview.navigateTo(newHistory[entry]);
}
entry++;
}
// Update the history
this.history = newHistory;
};
exports.list = ListWidget;
})();

View File

@ -1,31 +0,0 @@
/*\
title: $:/core/modules/widgets/list/listviews/scroller.js
type: application/javascript
module-type: listview
A list view that scrolls to newly inserted elements
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ScrollerListView = function(listWidget) {
this.listWidget = listWidget;
}
ScrollerListView.prototype.navigateTo = function(historyInfo) {
var listElementIndex = this.listWidget.findListElementByTitle(0,historyInfo.title),
listElementNode = this.listWidget.children[listElementIndex],
targetElement = listElementNode.domNode;
// Scroll the node into view
var scrollEvent = this.listWidget.renderer.renderTree.document.createEvent("Event");
scrollEvent.initEvent("tw-scroll",true,true);
targetElement.dispatchEvent(scrollEvent);
};
exports.scroller = ScrollerListView;
})();

View File

@ -1,45 +0,0 @@
/*\
title: $:/core/modules/widgets/password.js
type: application/javascript
module-type: widget
Implements the password widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var PasswordWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
PasswordWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.name = this.renderer.getAttribute("name");
// Get the current password
var password = $tw.browser ? $tw.utils.getPassword(this.name) : "";
// Generate our element
this.tag = "input";
this.attributes = {
type: "password",
value: password
};
this.events = [
{name: "keyup", handlerObject: this},
{name: "input", handlerObject: this}];
};
PasswordWidget.prototype.handleEvent = function(event) {
var password = this.renderer.domNode.value;
return $tw.utils.savePassword(this.name,password);
};
exports.password = PasswordWidget;
})();

View File

@ -1,216 +0,0 @@
/*\
title: $:/core/modules/widgets/reveal.js
type: application/javascript
module-type: widget
Implements the reveal widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var RevealWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
RevealWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.state = this.renderer.getAttribute("state");
this.type = this.renderer.getAttribute("type");
this.text = this.renderer.getAttribute("text");
this.position = this.renderer.getAttribute("position");
this["default"] = this.renderer.getAttribute("default","");
this.qualifyTiddlerTitles = this.renderer.getAttribute("qualifyTiddlerTitles");
this["class"] = this.renderer.getAttribute("class");
this.animate = this.renderer.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.renderer.renderTree.getContextScopeId(this.renderer.parentRenderer);
}
this.readState();
// Set up the element attributes
var classes = ["tw-reveal"],
styles = [];
if(this["class"]) {
$tw.utils.pushTop(classes,this["class"]);
}
if(this.isOpen) {
$tw.utils.pushTop(classes,"tw-reveal-open");
}
switch(this.type) {
case "popup":
styles.push("position:absolute;");
classes.push("tw-popup");
break;
}
styles.push("display:" + (this.isOpen ? (this.renderer.parseTreeNode.isBlock ? "block" : "inline") : "none") + ";");
// Set the element
this.tag = "div";
this.attributes = {
"class": classes.join(" "),
style: styles.join("")
};
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.isOpen ? this.renderer.parseTreeNode.children : []);
this.events = [{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}];
};
/*
Read the state tiddler
*/
RevealWidget.prototype.readState = function() {
// Read the information from the state tiddler
if(this.stateTitle) {
var state = this.renderer.renderTree.wiki.getTextReference(this.stateTitle,this["default"],this.renderer.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;
}
};
RevealWidget.prototype.handleClickEvent = function(event) {
if(event.type === "click" && this.type === "popup") {
// Cancel the popup if we get a click on it
if(this.stateTitle) {
this.renderer.renderTree.wiki.deleteTextReference(this.stateTitle);
}
event.preventDefault();
return false;
}
return true;
};
RevealWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
var self = this;
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes["default"] || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"]) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
var needChildrenRefresh = true; // Avoid refreshing the children nodes if we don't need to
// Get the open state
var previousState = this.isOpen
this.readState();
// Construct the child nodes if required
if(this.isOpen && this.children.length === 0) {
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
var parentNode = this.renderer.domNode;
$tw.utils.each(this.children,function(child) {
parentNode.appendChild(child.renderInDom());
});
needChildrenRefresh = false;
}
// Refresh any child nodes
if(needChildrenRefresh) {
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
// Animate the opening or closing
if(this.isOpen !== previousState) {
if(this.animate !== "no") {
if(this.isOpen) {
this.renderer.domNode.style.display = this.renderer.parseTreeNode.isBlock ? "block" : "inline";
$tw.anim.perform("open",this.renderer.domNode);
} else {
$tw.anim.perform("close",this.renderer.domNode,{callback: function() {
self.renderer.domNode.style.display = "none";
}});
}
} else {
this.renderer.domNode.style.display = this.isOpen ? (this.renderer.parseTreeNode.isBlock ? "block" : "inline") : "none";
}
}
// Add or remove the tw-reveal-open class
$tw.utils.toggleClass(this.renderer.domNode,"tw-reveal-open",this.isOpen);
}
// Position the content if required
if(this.isOpen) {
this.postRenderInDom();
}
};
RevealWidget.prototype.postRenderInDom = function() {
switch(this.type) {
case "popup":
if(this.isOpen) {
this.renderer.domNode.style.position = "absolute";
this.renderer.domNode.style.zIndex = "1000";
switch(this.position) {
case "left":
this.renderer.domNode.style.left = (this.popup.left - this.renderer.domNode.offsetWidth) + "px";
this.renderer.domNode.style.top = this.popup.top + "px";
break;
case "above":
this.renderer.domNode.style.left = this.popup.left + "px";
this.renderer.domNode.style.top = (this.popup.top - this.renderer.domNode.offsetHeight) + "px";
break;
case "aboveright":
this.renderer.domNode.style.left = (this.popup.left + this.popup.width) + "px";
this.renderer.domNode.style.top = (this.popup.top + this.popup.height - this.renderer.domNode.offsetHeight) + "px";
break;
case "right":
this.renderer.domNode.style.left = (this.popup.left + this.popup.width) + "px";
this.renderer.domNode.style.top = this.popup.top + "px";
break;
case "belowleft":
this.renderer.domNode.style.left = (this.popup.left + this.popup.width - this.renderer.domNode.offsetWidth) + "px";
this.renderer.domNode.style.top = (this.popup.top + this.popup.height) + "px";
break;
default: // Below
this.renderer.domNode.style.left = this.popup.left + "px";
this.renderer.domNode.style.top = (this.popup.top + this.popup.height) + "px";
break;
}
}
break;
}
};
exports.reveal = RevealWidget;
})();

View File

@ -1,58 +0,0 @@
/*\
title: $:/core/modules/widgets/setstyle.js
type: application/javascript
module-type: widget
Implements the setstyle widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var SetStyleWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
SetStyleWidget.prototype.generate = function() {
// Get the parameters from the attributes
this.name = this.renderer.getAttribute("name");
this.value = this.renderer.getAttribute("value");
this["class"] = this.renderer.getAttribute("class");
// Set up the element
this.tag = this.renderer.parseTreeNode.isBlock ? "div" : "span";
this.attributes = {
style: this.name + ":" + this.value
};
if(this["class"]) {
this.attributes["class"] = this["class"];
}
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
};
SetStyleWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.name || changedAttributes.value || changedAttributes["class"]) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.setstyle = SetStyleWidget;
})();

View File

@ -1,82 +0,0 @@
/*\
title: $:/core/modules/widgets/tiddler.js
type: application/javascript
module-type: widget
The tiddler widget sets the current tiddler to a specified title.
Attributes:
title: the title of the current tiddler
class: CSS classes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var TiddlerWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
TiddlerWidget.prototype.generate = function() {
var self = this;
this.tiddlerTitle = this.renderer.getAttribute("title","");
// Set up the attributes for the wrapper element
var classes = ["tw-tiddler"];
if(this.renderer.hasAttribute("class")) {
$tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" "));
}
if(!this.renderer.renderTree.wiki.tiddlerExists(this.tiddlerTitle) && !this.renderer.renderTree.wiki.isShadowTiddler(this.tiddlerTitle)) {
$tw.utils.pushTop(classes,"tw-tiddler-missing");
}
// Save the context for this renderer node
this.renderer.context = {
tiddlerTitle: this.tiddlerTitle
};
// Initialise events
this.events = [];
// Trap and update tag modification events
this.events.push({name: "tw-remove-tag", handlerFunction: function(event) {
event.currentTag = self.tiddlerTitle;
return true;
}});
// Set the element
this.tag = this.renderer.parseTreeNode.isBlock ? "div" : "span";
this.attributes = {};
if(classes.length > 0) {
this.attributes["class"] = classes.join(" ");
}
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
};
TiddlerWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Set the class for missing tiddlers
if(this.tiddlerTitle && changedTiddlers[this.tiddlerTitle]) {
$tw.utils.toggleClass(this.renderer.domNode,"tw-tiddler-missing",!this.renderer.renderTree.wiki.tiddlerExists(this.tiddlerTitle));
}
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.title) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.tiddler = TiddlerWidget;
})();

View File

@ -1,105 +0,0 @@
/*\
title: $:/core/modules/widgets/transclude.js
type: application/javascript
module-type: widget
The transclude widget includes another tiddler into the tiddler being rendered.
Attributes:
title: the title of the tiddler to transclude
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var TranscludeWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
TranscludeWidget.prototype.generate = function() {
var self = this,
templateParseTree;
// Get the render target details
this.transcludeTitle = this.renderer.getAttribute("title",this.renderer.tiddlerTitle);
this.transcludeField = this.renderer.getAttribute("field");
this.transcludeIndex = this.renderer.getAttribute("index");
// Check for recursion
if(this.renderer.renderTree.checkContextRecursion(this.renderer.parentRenderer,{
transcludeTitle: this.transcludeTitle,
transcludeField: this.transcludeField,
transcludeIndex: this.transcludeIndex
})) {
templateParseTree = [{type: "text", text: "Tiddler recursion error in transclude widget"}];
} else {
var parser;
if(this.transcludeField === "text" || (!this.transcludeField && !this.transcludeIndex)) {
parser = this.renderer.renderTree.wiki.parseTiddler(this.transcludeTitle,{parseAsInline: !this.renderer.parseTreeNode.isBlock});
} else {
var tiddler,text;
if(this.transcludeField) {
tiddler = this.renderer.renderTree.wiki.getTiddler(this.transcludeTitle);
text = tiddler ? tiddler.fields[this.transcludeField] : "";
if(text === undefined) {
text = "";
}
parser = this.renderer.renderTree.wiki.parseText("text/vnd.tiddlywiki",text,{parseAsInline: !this.renderer.parseTreeNode.isBlock});
} else if(this.transcludeIndex) {
text = this.renderer.renderTree.wiki.extractTiddlerDataItem(this.transcludeTitle,this.transcludeIndex,"");
parser = this.renderer.renderTree.wiki.parseText("text/vnd.tiddlywiki",text,{parseAsInline: !this.renderer.parseTreeNode.isBlock});
}
}
templateParseTree = parser ? parser.tree : [];
}
// Set up the attributes for the wrapper element
var classes = ["tw-transclude"];
if(this.renderer.hasAttribute("class")) {
$tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" "));
}
// Save the context for this renderer node
this.renderer.context = {
transcludeTitle: this.transcludeTitle,
transcludeField: this.transcludeField,
transcludeIndex: this.transcludeIndex
};
// Set the element
this.tag = this.renderer.parseTreeNode.isBlock ? "div" : "span";
this.attributes = {};
if(classes.length > 0) {
this.attributes["class"] = classes.join(" ");
}
if(this.renderer.hasAttribute("style")) {
this.attributes.style = this.renderer.getAttribute("style");
}
if(this.renderer.hasAttribute("tooltip")) {
this.attributes.title = this.renderer.getAttribute("tooltip");
}
this.children = this.renderer.renderTree.createRenderers(this.renderer,templateParseTree);
};
TranscludeWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.transcludeField || changedAttributes.transcludeIndex || (this.transcludeTitle && changedTiddlers[this.transcludeTitle])) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.transclude = TranscludeWidget;
})();

View File

@ -1,34 +0,0 @@
/*\
title: $:/core/modules/widgets/version.js
type: application/javascript
module-type: widget
Implements the version widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var VersionWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
VersionWidget.prototype.generate = function() {
// Set the element
this.tag = "span";
this.attributes = {};
this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
type: "text",
text: $tw.version
}]);
};
exports.version = VersionWidget;
})();

View File

@ -1,70 +0,0 @@
/*\
title: $:/core/modules/widgets/video.js
type: application/javascript
module-type: widget
Implements the video widget.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var VideoWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Generate child nodes
this.generate();
};
VideoWidget.prototype.generate = function() {
// Get attributes
this.src = this.renderer.getAttribute("src");
this.type = this.renderer.getAttribute("type","vimeo");
this.width = parseInt(this.renderer.getAttribute("width","640"),10);
this.height = parseInt(this.renderer.getAttribute("height","360"),10);
// Return the appropriate element
switch(this.type) {
case "vimeo":
this.tag = "iframe";
this.attributes = {
src: "http://player.vimeo.com/video/" + this.src + "?autoplay=0",
width: this.width,
height: this.height,
frameborder: 0
};
break;
case "youtube":
this.tag = "iframe";
this.attributes = {
src: "http://www.youtube.com/embed/" + this.src,
width: this.width,
height: this.height,
frameborder: 0
};
break;
case "archiveorg":
this.tag = "iframe";
this.attributes = {
src: "http://www.archive.org/embed/" + this.src,
width: this.width,
height: this.height,
frameborder: 0
};
break;
default:
this.tag = "div";
this.attributes = {};
this.children = this.renderer.renderTree.createRenderers(this.renderer,[{
type: "text",
text: "Unknown video type"
}]);
break;
}
};
exports.video = VideoWidget;
})();

View File

@ -1,120 +0,0 @@
/*\
title: $:/core/modules/widgets/view.js
type: application/javascript
module-type: widget
The view widget displays a tiddler field.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Define the "text" viewer here so that it is always available
*/
var TextViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
TextViewer.prototype.render = function() {
// Get the value as a string
if(this.field !== "text" && this.tiddler) {
this.value = this.tiddler.getFieldString(this.field);
}
var value = "";
if(this.value !== undefined && this.value !== null) {
value = this.value;
}
// Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{
type: "text",
text: value
}]);
};
var ViewWidget = function(renderer) {
// Save state
this.renderer = renderer;
// Initialise the field viewers if they've not been done already
if(!this.fieldViewers) {
ViewWidget.prototype.fieldViewers = {text: TextViewer}; // Start with the built-in text viewer
$tw.modules.applyMethods("fieldviewer",this.fieldViewers);
}
// Generate child nodes
this.generate();
};
ViewWidget.prototype.generate = function() {
// Get parameters from our attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.tiddlerTitle);
this.fieldName = this.renderer.getAttribute("field","text");
this.format = this.renderer.getAttribute("format","text");
// Get the value to display
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.tiddlerTitle),
value;
if(tiddler) {
if(this.fieldName === "text") {
// Calling getTiddlerText() triggers lazy loading of skinny tiddlers
value = this.renderer.renderTree.wiki.getTiddlerText(this.tiddlerTitle);
} else {
value = tiddler.fields[this.fieldName];
}
} else { // Use a special value if the tiddler is missing
switch(this.fieldName) {
case "title":
value = this.tiddlerTitle;
break;
case "modified":
case "created":
value = new Date();
break;
default:
value = "";
break;
}
}
// Choose the viewer to use
var Viewer = this.fieldViewers.text;
if($tw.utils.hop(this.fieldViewers,this.format)) {
Viewer = this.fieldViewers[this.format];
}
this.viewer = new Viewer(this,tiddler,this.fieldName,value);
// Ask the viewer to create the widget element
this.viewer.render();
};
ViewWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.format || (this.tiddlerTitle && changedTiddlers[this.tiddlerTitle])) {
// Regenerate and rerender the widget and replace the existing DOM node
this.generate();
var oldDomNode = this.renderer.domNode,
newDomNode = this.renderer.renderInDom();
oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
} else {
// We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
ViewWidget.prototype.postRenderInDom = function() {
if(this.viewer && this.viewer.postRenderInDom) {
this.viewer.postRenderInDom();
}
};
exports.view = ViewWidget;
})();

View File

@ -1,41 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/date.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as a date
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var DateViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
DateViewer.prototype.render = function() {
var template = this.viewWidget.renderer.getAttribute("template","DD MMM YYYY"),
value = "";
if(this.value !== undefined) {
value = $tw.utils.formatDateString(this.value,template);
}
// Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {
"class": "tw-view-date"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[{
type: "text",
text: value
}]);
};
exports.date = DateViewer;
})();

View File

@ -1,44 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/htmlencoded.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as HTML encoded text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var HtmlEncodedViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
HtmlEncodedViewer.prototype.render = function() {
// Get the value as a string
if(this.field !== "text" && this.tiddler) {
this.value = this.tiddler.getFieldString(this.field);
}
var value = "";
if(this.value !== undefined && this.value !== null) {
value = this.value;
}
// Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {
"class": "tw-view-htmlencoded"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[{
type: "text",
text: $tw.utils.htmlEncode(value)
}]);
};
exports.htmlencoded = HtmlEncodedViewer;
})();

View File

@ -1,44 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/htmlwikified.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as a textual HTML representation of the wikified text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var HtmlWikifiedViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
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, document: this.viewWidget.renderer.renderTree.document});
renderTree.execute();
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 = {
"class": "tw-view-htmlwikified"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[{
type: "text",
text: text
}]);
};
exports.htmlwikified = HtmlWikifiedViewer;
})();

View File

@ -1,44 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/jsencoded.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as JavaScript stringified text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var JsEncodedViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
JsEncodedViewer.prototype.render = function() {
// Get the value as a string
if(this.field !== "text" && this.tiddler) {
this.value = this.tiddler.getFieldString(this.field);
}
var value = "";
if(this.value !== undefined && this.value !== null) {
value = this.value;
}
// Set the element details
this.viewWidget.tag = "pre";
this.viewWidget.attributes = {
"class": "tw-view-jsencoded"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[{
type: "text",
text: $tw.utils.stringify(value)
}]);
};
exports.jsencoded = JsEncodedViewer;
})();

View File

@ -1,44 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/link.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as a link
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var LinkViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
LinkViewer.prototype.render = function() {
var text = this.value === undefined ? "" : this.value;
// Indicate that we're not generating an element
this.viewWidget.tag = this.viewWidget.renderer.parseTreeNode.isBlock ? "div" : "span";
this.viewWidget.attributes = {
"class": "tw-view-link"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[{
type: "element",
tag: "$link",
attributes: {
to: {type: "string", value: text}
},
children: [{
type: "text",
text: text
}]
}]);
};
exports.link = LinkViewer;
})();

View File

@ -1,77 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/relativedate.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as a relative date
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var RelativeDateViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
RelativeDateViewer.prototype.render = function() {
var template = this.viewWidget.renderer.getAttribute("template","DD MMM YYYY"),
value = "";
if(this.value !== undefined) {
value = $tw.utils.formatDateString(this.value,template);
}
// Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {
"class": "tw-view-date"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[{
type: "text",
text: value
}]);
};
/*
Trigger the timer when the relative date is put into the DOM
*/
RelativeDateViewer.prototype.postRenderInDom = function() {
this.update();
};
/*
Trigger the timer for the next update of the relative date
*/
RelativeDateViewer.prototype.setTimer = function() {
var self = this;
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(self.viewWidget.renderer.renderTree.document,self.viewWidget.renderer.domNode)) {
self.update.call(self);
}
},this.relativeDate.updatePeriod);
}
};
/*
Update the relative date display, and trigger the timer for the next update
*/
RelativeDateViewer.prototype.update = function() {
this.relativeDate = $tw.utils.getRelativeDate((new Date()) - this.value);
if(this.relativeDate.delta > 0) {
while(this.viewWidget.renderer.domNode.hasChildNodes()) {
this.viewWidget.renderer.domNode.removeChild(this.viewWidget.renderer.domNode.firstChild);
}
this.viewWidget.renderer.domNode.appendChild(this.viewWidget.renderer.renderTree.document.createTextNode(this.relativeDate.description));
this.setTimer();
}
};
exports.relativedate = RelativeDateViewer;
})();

View File

@ -1,44 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/urlencoded.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as url encoded text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var UrlEncodedViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
UrlEncodedViewer.prototype.render = function() {
// Get the value as a string
if(this.field !== "text" && this.tiddler) {
this.value = this.tiddler.getFieldString(this.field);
}
var value = "";
if(this.value !== undefined && this.value !== null) {
value = this.value;
}
// Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {
"class": "tw-view-urlencoded"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[{
type: "text",
text: encodeURIComponent(value)
}]);
};
exports.urlencoded = UrlEncodedViewer;
})();

View File

@ -1,43 +0,0 @@
/*\
title: $:/core/modules/widgets/view/viewers/wikified.js
type: application/javascript
module-type: fieldviewer
A viewer for viewing tiddler fields as wikified text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var WikifiedViewer = function(viewWidget,tiddler,field,value) {
this.viewWidget = viewWidget;
this.tiddler = tiddler;
this.field = field;
this.value = value;
};
WikifiedViewer.prototype.render = function() {
// Set the element details
this.viewWidget.tag = this.viewWidget.renderer.parseTreeNode.isBlock ? "div" : "span";
this.viewWidget.attributes = {};
var node = {
type: "element",
tag: "$transclude",
attributes: {
"class": "tw-view-wikified",
field: {type: "string", value: this.field}
},
isBlock: this.viewWidget.renderer.parseTreeNode.isBlock
};
if(this.tiddler && this.tiddler.fields.title) {
node.attributes.target = {type: "string", value: this.tiddler.fields.title}
}
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer,[node]);
};
exports.wikified = WikifiedViewer;
})();

224
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";
/*
@ -168,6 +170,18 @@ exports.tiddlerExists = function(title) {
return !!this.tiddlers[title];
};
/*
Generate an unused title from the specified base
*/
exports.generateNewTitle = function(baseTitle) {
var c = 0;
do {
var title = baseTitle + (c ? " " + (c + 1) : "");
c++;
} while(this.tiddlerExists(title));
return title;
};
exports.isSystemTiddler = function(title) {
return title.indexOf("$:/") === 0;
};
@ -315,13 +329,13 @@ exports.getTiddlerLinks = function(title) {
// We'll cache the links so they only get computed if the tiddler changes
return this.getCacheForTiddler(title,"links",function() {
// Parse the tiddler
var parser = self.parseTiddler(title);
var parser = self.new_parseTiddler(title);
// Count up the links
var links = [],
checkParseTree = function(parseTree) {
for(var t=0; t<parseTree.length; t++) {
var parseTreeNode = parseTree[t];
if(parseTreeNode.type === "element" && parseTreeNode.tag === "$link" && parseTreeNode.attributes.to.type === "string") {
if(parseTreeNode.type === "link" && parseTreeNode.attributes.to && parseTreeNode.attributes.to.type === "string") {
var value = parseTreeNode.attributes.to.value;
if(links.indexOf(value) === -1) {
links.push(value);
@ -545,6 +559,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]) {
@ -588,7 +606,7 @@ Parse a block of text of a specified MIME type
Options include:
parseAsInline: if true, the text of the tiddler will be parsed as an inline run
*/
exports.parseText = function(type,text,options) {
exports.old_parseText = function(type,text,options) {
options = options || {};
// Select a parser
var Parser = $tw.Wiki.parsers[type];
@ -611,28 +629,139 @@ exports.parseText = function(type,text,options) {
/*
Parse a tiddler according to its MIME type
*/
exports.parseTiddler = function(title,options) {
exports.old_parseTiddler = function(title,options) {
options = options || {};
var cacheType = options.parseAsInline ? "newInlineParseTree" : "newBlockParseTree",
tiddler = this.getTiddler(title),
self = this;
return tiddler ? this.getCacheForTiddler(title,cacheType,function() {
return self.parseText(tiddler.fields.type,tiddler.fields.text,options);
return self.old_parseText(tiddler.fields.type,tiddler.fields.text,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);
delete node.tag;
}
tweakParseTreeNodes(node.children);
};
var tweakParseTreeNodes = function(nodeList) {
$tw.utils.each(nodeList,tweakParseTreeNode);
};
var tweakMacroDefinition = function(nodeList) {
if(nodeList && nodeList[0] && nodeList[0].type === "macrodef") {
nodeList[0].type = "setvariable";
nodeList[0].attributes = {
name: {type: "string", value: nodeList[0].name},
value: {type: "string", value: nodeList[0].text}
};
nodeList[0].children = nodeList.slice(1);
nodeList.splice(1,nodeList.length-1);
tweakMacroDefinition(nodeList[0].children);
}
};
var tweakParser = function(parser) {
// Move any macro definitions to contain the body tree
tweakMacroDefinition(parser.tree);
// Tweak widgets
tweakParseTreeNodes(parser.tree);
};
exports.new_parseText = function(type,text,options) {
var parser = this.old_parseText(type,text,options);
if(parser) {
tweakParser(parser)
};
return parser;
};
exports.new_parseTiddler = function(title,options) {
var parser = this.old_parseTiddler(title,options);
if(parser) {
tweakParser(parser)
};
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);
}
}
};
/*
Make a widget tree for a parse tree
parser: parser object
options: see below
Options include:
document: optional document to use
variables: hashmap of variables to set
parentWidget: optional parent widget for the root node
*/
exports.makeWidget = function(parser,options) {
options = options || {};
var widgetNode = {
type: "widget",
children: []
},
currWidgetNode = widgetNode;
// Create setvariable widgets for each variable
$tw.utils.each(options.variables,function(value,name) {
var setVariableWidget = {
type: "setvariable",
attributes: {
name: {type: "string", value: name},
value: {type: "string", value: value}
},
children: []
};
currWidgetNode.children = [setVariableWidget];
currWidgetNode = setVariableWidget;
});
// Add in the supplied parse tree nodes
currWidgetNode.children = parser ? parser.tree : [];
// Create the widget
return new widget.widget(widgetNode,{
wiki: this,
document: options.document || $tw.document,
parentWidget: options.parentWidget
});
};
/*
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
options: see below
Options include:
variables: hashmap of variables to set
parentWidget: optional parent widget for the root node
*/
exports.renderText = function(outputType,textType,text,context) {
var parser = this.parseText(textType,text),
renderTree = new $tw.WikiRenderTree(parser,{wiki: this, context: context, document: $tw.document});
renderTree.execute();
exports.new_renderText = function(outputType,textType,text,options) {
options = options || {};
var parser = $tw.wiki.new_parseText(textType,text),
widgetNode = this.makeWidget(parser,options);
var container = $tw.document.createElement("div");
renderTree.renderInDom(container)
widgetNode.render(container,null);
return outputType === "text/html" ? container.innerHTML : container.textContent;
};
@ -640,13 +769,17 @@ exports.renderText = function(outputType,textType,text,context) {
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
options: see below
Options include:
variables: hashmap of variables to set
parentWidget: optional parent widget for the root node
*/
exports.renderTiddler = function(outputType,title,context) {
var parser = this.parseTiddler(title),
renderTree = new $tw.WikiRenderTree(parser,{wiki: this, context: context, document: $tw.document});
renderTree.execute();
exports.new_renderTiddler = function(outputType,title,options) {
options = options || {};
var parser = this.new_parseTiddler(title),
widgetNode = this.makeWidget(parser,options);
var container = $tw.document.createElement("div");
renderTree.renderInDom(container)
widgetNode.render(container,null);
return outputType === "text/html" ? container.innerHTML : container.textContent;
};
@ -699,7 +832,7 @@ exports.saveWiki = function(options) {
options = options || {};
var template = options.template || "$:/core/templates/tiddlywiki5.template.html",
downloadType = options.downloadType || "text/plain";
var text = this.renderTiddler(downloadType,template);
var text = this.new_renderTiddler(downloadType,template);
this.callSaver("save",text,function(err) {
$tw.notifier.display("$:/messages/Saved");
});
@ -812,4 +945,63 @@ exports.getTiddlerText = function(title,defaultText) {
}
};
/*
Read an array of browser File objects, invoking callback(tiddlerFields) for each loaded file
*/
exports.readFiles = function(files,callback) {
for(var f=0; f<files.length; f++) {
this.readFile(files[f],callback);
};
};
/*
Read a browser File object, invoking callback(tiddlerFields) with the tiddler fields object
*/
exports.readFile = function(file,callback) {
// Get the type, falling back to the filename extension
var self = this,
type = file.type;
if(type === "" || !type) {
var dotPos = file.name.lastIndexOf(".");
if(dotPos !== -1) {
var fileExtensionInfo = $tw.config.fileExtensionInfo[file.name.substr(dotPos)];
if(fileExtensionInfo) {
type = fileExtensionInfo.type;
}
}
}
// Figure out if we're reading a binary file
var contentTypeInfo = $tw.config.contentTypeInfo[type],
isBinary = contentTypeInfo ? contentTypeInfo.encoding === "base64" : false;
// Create the FileReader
var reader = new FileReader();
// Onload
reader.onload = function(event) {
// Deserialise the file contents
var tiddlerFields = {title: file.name || "Untitled", type: type};
// Are we binary?
if(isBinary) {
// The base64 section starts after the first comma in the data URI
var commaPos = event.target.result.indexOf(",");
if(commaPos !== -1) {
tiddlerFields.text = event.target.result.substr(commaPos+1);
callback(tiddlerFields);
}
} else {
var tiddlers = self.deserializeTiddlers(type,event.target.result,tiddlerFields);
if(tiddlers) {
$tw.utils.each(tiddlers,function(tiddlerFields) {
callback(tiddlerFields);
});
}
}
};
// Kick off the read
if(isBinary) {
reader.readAsDataURL(file);
} else {
reader.readAsText(file);
}
};
})();

View File

@ -18,7 +18,7 @@ type: text/vnd.tiddlywiki-html
{{{ [tag[$:/tags/stylesheet]] [is[shadow]tag[$:/tags/stylesheet]] ||$:/core/templates/wikified-tiddler}}}
</style>
</head>
<body>
<body class="tw-body">
{{$:/StaticBanner||$:/core/templates/html-tiddler}}
{{$:/core/ui/PageTemplate||$:/core/templates/html-tiddler}}
</body>

Some files were not shown because too many files have changed in this diff Show More