From c296f14210545374124df5d4ae9ffb402ed73561 Mon Sep 17 00:00:00 2001 From: Jim Lehmer Date: Sat, 3 Jan 2015 09:33:22 -0600 Subject: [PATCH 1/6] Handle case-insensitive file systems. --- plugins/tiddlywiki/filesystem/filesystemadaptor.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/tiddlywiki/filesystem/filesystemadaptor.js b/plugins/tiddlywiki/filesystem/filesystemadaptor.js index b3e4d8af7..68e951c2b 100644 --- a/plugins/tiddlywiki/filesystem/filesystemadaptor.js +++ b/plugins/tiddlywiki/filesystem/filesystemadaptor.js @@ -92,16 +92,17 @@ Given a tiddler title and an array of existing filenames, generate a new legal f */ FileSystemAdaptor.prototype.generateTiddlerFilename = function(title,extension,existingFilenames) { // First remove any of the characters that are illegal in Windows filenames - var baseFilename = title.replace(/<|>|\:|\"|\/|\\|\||\?|\*|\^|\s/g,"_"); + var baseFilename = transliterate(title.replace(/<|>|\:|\"|\/|\\|\||\?|\*|\^|\s/g,"_")); // Truncate the filename if it is too long if(baseFilename.length > 200) { baseFilename = baseFilename.substr(0,200); } // Start with the base filename plus the extension - var filename = transliterate(baseFilename) + extension, + var filename = baseFilename + extension, count = 1; - // Add a discriminator if we're clashing with an existing filename - while(existingFilenames.indexOf(filename) !== -1) { + // Add a discriminator if we're clashing with an existing filename while + // handling case-insensitive filesystems (NTFS, FAT/FAT32, etc.) + while(existingFilenames.some(function(value) {return value.toLocaleLowerCase() === filename.toLocaleLowerCase();})) { filename = baseFilename + " " + (count++) + extension; } return filename; From 4f3cb8b9aebfc4f65f40c96ef99730887d746b41 Mon Sep 17 00:00:00 2001 From: Astrid Elocson Date: Sat, 3 Jan 2015 20:02:27 +0000 Subject: [PATCH 2/6] Introduce railroad plugin --- plugins/tiddlywiki/railroad/components.js | 264 +++++++++ .../railroad/doc/example-source.tid | 9 + plugins/tiddlywiki/railroad/doc/example.tid | 16 + plugins/tiddlywiki/railroad/doc/readme.tid | 17 + .../tiddlywiki/railroad/doc/syntax-string.tid | 5 + plugins/tiddlywiki/railroad/doc/syntax.tid | 67 +++ .../railroad/files/railroad-diagrams.css | 23 + .../railroad/files/railroad-diagrams.js | 508 ++++++++++++++++++ .../railroad/files/tiddlywiki.files | 21 + plugins/tiddlywiki/railroad/parser.js | 334 ++++++++++++ plugins/tiddlywiki/railroad/plugin.info | 7 + plugins/tiddlywiki/railroad/wrapper.js | 70 +++ 12 files changed, 1341 insertions(+) create mode 100644 plugins/tiddlywiki/railroad/components.js create mode 100644 plugins/tiddlywiki/railroad/doc/example-source.tid create mode 100644 plugins/tiddlywiki/railroad/doc/example.tid create mode 100644 plugins/tiddlywiki/railroad/doc/readme.tid create mode 100644 plugins/tiddlywiki/railroad/doc/syntax-string.tid create mode 100644 plugins/tiddlywiki/railroad/doc/syntax.tid create mode 100644 plugins/tiddlywiki/railroad/files/railroad-diagrams.css create mode 100644 plugins/tiddlywiki/railroad/files/railroad-diagrams.js create mode 100644 plugins/tiddlywiki/railroad/files/tiddlywiki.files create mode 100644 plugins/tiddlywiki/railroad/parser.js create mode 100644 plugins/tiddlywiki/railroad/plugin.info create mode 100644 plugins/tiddlywiki/railroad/wrapper.js diff --git a/plugins/tiddlywiki/railroad/components.js b/plugins/tiddlywiki/railroad/components.js new file mode 100644 index 000000000..3001f448c --- /dev/null +++ b/plugins/tiddlywiki/railroad/components.js @@ -0,0 +1,264 @@ +/*\ +title: $:/plugins/tiddlywiki/railroad/components.js +type: application/javascript +module-type: library + +Components of a railroad diagram. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var railroad = require("$:/plugins/tiddlywiki/railroad/railroad-diagrams.js"); + +/////////////////////////// Base component + +var Component = function() { + this.type = "Component"; +}; + +// Set up a leaf component +Component.prototype.initialiseLeaf = function(type,text) { + this.type = type; + this.text = text; +}; + +// Set up a component with a single child +Component.prototype.initialiseWithChild = function(type,content) { + this.type = type; + this.child = toSingleChild(content); +}; + +// Set up a component with an array of children +Component.prototype.initialiseWithChildren = function(type,content) { + this.type = type; + // Force the content to be an array + this.children = $tw.utils.isArray(content) ? content : [content]; +} + +// Return an array of the SVG strings of an array of children +Component.prototype.getSvgOfChildren = function() { + return this.children.map(function(child) { + return child.toSvg(); + }); +} + +Component.prototype.toSvg = function() { + return ""; +} + +Component.prototype.debug = function(output,indent) { + output.push(indent); + output.push(this.type); + // Add the text of a leaf component + if(this.text && this.text !== "") { + output.push(": "); + output.push(this.text); + } + // Flag the normal route + if(this.normal !== undefined) { + if(this.normal === true) { + output.push(" (normal)"); + } else if(this.normal !== false) { + output.push(" (normal: "); + output.push(this.normal); + output.push(")"); + } + } + output.push("\n"); + var contentIndent = indent + " "; + // Add the one child + if(this.child) { + this.child.debug(output,contentIndent); + } + // Add the array of children + if(this.children) { + this.debugArray(this.children,output,contentIndent); + } + // Add the separator if there is one + if(this.separator) { + output.push(indent); + output.push("(separator)\n"); + this.separator.debug(output,contentIndent); + } +}; + +Component.prototype.debugArray = function(array,output,indent) { + for(var i=0; i) +[{("@" name-char | :"--" )}] \ No newline at end of file diff --git a/plugins/tiddlywiki/railroad/doc/example.tid b/plugins/tiddlywiki/railroad/doc/example.tid new file mode 100644 index 000000000..6b4df3071 --- /dev/null +++ b/plugins/tiddlywiki/railroad/doc/example.tid @@ -0,0 +1,16 @@ +created: 20150102165032410 +modified: 20150102172010663 +tags: +title: $:/plugins/tiddlywiki/railroad/example + +<$railroad text={{$:/plugins/tiddlywiki/railroad/example-source}}/> + +``` +<$railroad text=""" +["+"] +({digit} | "#" <'escape sequence'>) +[{("@" name-char | :"--" )}] +"""/> +``` + +<$railroad mode="debug" text={{$:/plugins/tiddlywiki/railroad/example-source}}/> \ No newline at end of file diff --git a/plugins/tiddlywiki/railroad/doc/readme.tid b/plugins/tiddlywiki/railroad/doc/readme.tid new file mode 100644 index 000000000..a7507a305 --- /dev/null +++ b/plugins/tiddlywiki/railroad/doc/readme.tid @@ -0,0 +1,17 @@ +created: 20150102163222184 +modified: 20150102172016663 +title: $:/plugins/tiddlywiki/railroad/readme + +This plugin provides a `<$railroad>` widget for generating railroad syntax diagrams as SVG images. It is based on [[a library by Tab Atkins|https://github.com/tabatkins/railroad-diagrams]]. + +The content of the `<$railroad>` widget is ignored. + +|!Attribute |!Description | +|text |Text in a special syntax that defines the diagram's layout | +|mode |If set to `debug`, the diagram will display its internal tree structure. The default mode is `svg` | + +The `text` can be transcluded from another tiddler: + +``` +<$railroad tiddler={{diagram}}> +``` \ No newline at end of file diff --git a/plugins/tiddlywiki/railroad/doc/syntax-string.tid b/plugins/tiddlywiki/railroad/doc/syntax-string.tid new file mode 100644 index 000000000..aa5007149 --- /dev/null +++ b/plugins/tiddlywiki/railroad/doc/syntax-string.tid @@ -0,0 +1,5 @@ +created: 20150103184022184 +modified: 20150103184022184 +title: $:/plugins/tiddlywiki/railroad/syntax-string + +('"' text '"' | "'" text "'" | '"""' text '"""') \ No newline at end of file diff --git a/plugins/tiddlywiki/railroad/doc/syntax.tid b/plugins/tiddlywiki/railroad/doc/syntax.tid new file mode 100644 index 000000000..a658c734f --- /dev/null +++ b/plugins/tiddlywiki/railroad/doc/syntax.tid @@ -0,0 +1,67 @@ +created: 20150103184022184 +modified: 20150103184022184 +title: $:/plugins/tiddlywiki/railroad/syntax + +The railroad widget constructs a diagram from the components defined below. + +`x` and `y` here stand for any component. + +--- + +; sequence +: <$railroad text=""" ["<-"] {x} ["->"] """/> +* A sequence of components +* The `<-` and `->` delimiters allow you to force a single component to be treated as a sequence. This is occasionally useful for spacing a diagram out + +--- + +; optional +: <$railroad text=""" "[" [":"] x "]" """/> +* A component that can be omitted +* The colon makes `x` appear straight ahead + +--- + +; repeated +: <$railroad text=""" "{" x [:"+" y] "}" """/> +* A list of one or more `x` +* The `+` suffix adds `y` as a separator between each `x` and the next + +--- + +; optional repeated +: <$railroad text=""" "[{" [":"] x [:"+" y] "}]" """/> +* An optional list of `x`, i.e. a list of zero or more `x` + +--- + +; choice +: <$railroad text=""" "(" {[:":"] x +"|"} ")" """/> +* A set of alternatives +* The colon indicates which branch appears straight ahead. By default, it's the first branch + +--- + +; string / terminal +: <$railroad text={{$:/plugins/tiddlywiki/railroad/syntax-string}}/> +* A literal or terminal component +* This follows the normal ~TiddlyWiki rules for quoted strings + +--- + +; nonterminal +: <$railroad text=""" (name | "<" string ">") """/> +* A nonterminal component, i.e. the name of another diagram +* The simple `name` option is available when the text starts with a letter and contains only letters, digits, underscores, dots and hyphens + +--- + +; comment +: <$railroad text=""" "/" string "/" """/> +* A comment + +--- + +; dummy +: <$railroad text=""" "-" """/> +* The absence of a component \ No newline at end of file diff --git a/plugins/tiddlywiki/railroad/files/railroad-diagrams.css b/plugins/tiddlywiki/railroad/files/railroad-diagrams.css new file mode 100644 index 000000000..4f9ba13b3 --- /dev/null +++ b/plugins/tiddlywiki/railroad/files/railroad-diagrams.css @@ -0,0 +1,23 @@ +svg.railroad-diagram { + background-color: hsl(30,20%,95%); +} +svg.railroad-diagram path { + stroke-width: 3; + stroke: black; + fill: rgba(0,0,0,0); +} +svg.railroad-diagram text { + font: bold 14px monospace; + text-anchor: middle; +} +svg.railroad-diagram text.label { + text-anchor: start; +} +svg.railroad-diagram text.comment { + font: italic 12px monospace; +} +svg.railroad-diagram rect { + stroke-width: 3; + stroke: black; + fill: hsl(120,100%,90%); +} \ No newline at end of file diff --git a/plugins/tiddlywiki/railroad/files/railroad-diagrams.js b/plugins/tiddlywiki/railroad/files/railroad-diagrams.js new file mode 100644 index 000000000..bccaa7747 --- /dev/null +++ b/plugins/tiddlywiki/railroad/files/railroad-diagrams.js @@ -0,0 +1,508 @@ +/* TiddlyWiki: modifications to the original library are commented like this */ + +/* +Railroad Diagrams +by Tab Atkins Jr. (and others) +http://xanthir.com +http://twitter.com/tabatkins +http://github.com/tabatkins/railroad-diagrams + +This document and all associated files in the github project are licensed under CC0: http://creativecommons.org/publicdomain/zero/1.0/ +This means you can reuse, remix, or otherwise appropriate this project for your own use WITHOUT RESTRICTION. +(The actual legal meaning can be found at the above link.) +Don't ask me for permission to use any part of this project, JUST USE IT. +I would appreciate attribution, but that is not required by the license. +*/ + +/* +This file uses a module pattern to avoid leaking names into the global scope. +The only accidental leakage is the name "temp". +The exported names can be found at the bottom of this file; +simply change the names in the array of strings to change what they are called in your application. + +As well, several configuration constants are passed into the module function at the bottom of this file. +At runtime, these constants can be found on the Diagram class. +*/ + +var temp = (function(options) { + function subclassOf(baseClass, superClass) { + baseClass.prototype = Object.create(superClass.prototype); + baseClass.prototype.$super = superClass.prototype; + } + + function unnull(/* children */) { + return [].slice.call(arguments).reduce(function(sofar, x) { return sofar !== undefined ? sofar : x; }); + } + + function determineGaps(outer, inner) { + var diff = outer - inner; + switch(Diagram.INTERNAL_ALIGNMENT) { + case 'left': return [0, diff]; break; + case 'right': return [diff, 0]; break; + case 'center': + default: return [diff/2, diff/2]; break; + } + } + + function wrapString(value) { + return ((typeof value) == 'string') ? new Terminal(value) : value; + } + + + function SVG(name, attrs, text) { + attrs = attrs || {}; + text = text || ''; + var el = document.createElementNS("http://www.w3.org/2000/svg",name); + for(var attr in attrs) { + el.setAttribute(attr, attrs[attr]); + } + el.textContent = text; + return el; + } + + function FakeSVG(tagName, attrs, text){ + if(!(this instanceof FakeSVG)) return new FakeSVG(tagName, attrs, text); + if(text) this.children = text; + else this.children = []; + this.tagName = tagName; + this.attrs = unnull(attrs, {}); + return this; + }; + FakeSVG.prototype.format = function(x, y, width) { + // Virtual + }; + FakeSVG.prototype.addTo = function(parent) { + if(parent instanceof FakeSVG) { + parent.children.push(this); + return this; + } else { + var svg = this.toSVG(); + parent.appendChild(svg); + return svg; + } + }; + FakeSVG.prototype.toSVG = function() { + var el = SVG(this.tagName, this.attrs); + if(typeof this.children == 'string') { + el.textContent = this.children; + } else { + this.children.forEach(function(e) { + el.appendChild(e.toSVG()); + }); + } + return el; + }; + FakeSVG.prototype.toString = function() { + var str = '<' + this.tagName; + var group = this.tagName == "g" || this.tagName == "svg"; + for(var attr in this.attrs) { + str += ' ' + attr + '="' + (this.attrs[attr]+'').replace(/&/g, '&').replace(/"/g, '"') + '"'; + } + str += '>'; + if(group) str += "\n"; + if(typeof this.children == 'string') { + str += this.children.replace(/&/g, '&').replace(/\n'; + return str; + } + + function Path(x,y) { + if(!(this instanceof Path)) return new Path(x,y); + FakeSVG.call(this, 'path'); + this.attrs.d = "M"+x+' '+y; + } + subclassOf(Path, FakeSVG); + Path.prototype.m = function(x,y) { + this.attrs.d += 'm'+x+' '+y; + return this; + } + Path.prototype.h = function(val) { + this.attrs.d += 'h'+val; + return this; + } + Path.prototype.right = Path.prototype.h; + Path.prototype.left = function(val) { return this.h(-val); } + Path.prototype.v = function(val) { + this.attrs.d += 'v'+val; + return this; + } + Path.prototype.down = Path.prototype.v; + Path.prototype.up = function(val) { return this.v(-val); } + Path.prototype.arc = function(sweep){ + var x = Diagram.ARC_RADIUS; + var y = Diagram.ARC_RADIUS; + if(sweep[0] == 'e' || sweep[1] == 'w') { + x *= -1; + } + if(sweep[0] == 's' || sweep[1] == 'n') { + y *= -1; + } + if(sweep == 'ne' || sweep == 'es' || sweep == 'sw' || sweep == 'wn') { + var cw = 1; + } else { + var cw = 0; + } + this.attrs.d += "a"+Diagram.ARC_RADIUS+" "+Diagram.ARC_RADIUS+" 0 0 "+cw+' '+x+' '+y; + return this; + } + Path.prototype.format = function() { + // All paths in this library start/end horizontally. + // The extra .5 ensures a minor overlap, so there's no seams in bad rasterizers. + this.attrs.d += 'h.5'; + return this; + } + + function Diagram(items) { + if(!(this instanceof Diagram)) return new Diagram([].slice.call(arguments)); + FakeSVG.call(this, 'svg', {class: Diagram.DIAGRAM_CLASS}); + this.items = items.map(wrapString); + this.items.unshift(new Start); + this.items.push(new End); + this.width = this.items.reduce(function(sofar, el) { return sofar + el.width + (el.needsSpace?20:0)}, 0)+1; + this.up = Math.max.apply(null, this.items.map(function (x) { return x.up; })); + this.down = Math.max.apply(null, this.items.map(function (x) { return x.down; })); + this.formatted = false; + } + subclassOf(Diagram, FakeSVG); + for(var option in options) { + Diagram[option] = options[option]; + } + Diagram.prototype.format = function(paddingt, paddingr, paddingb, paddingl) { + paddingt = unnull(paddingt, 20); + paddingr = unnull(paddingr, paddingt, 20); + paddingb = unnull(paddingb, paddingt, 20); + paddingl = unnull(paddingl, paddingr, 20); + var x = paddingl; + var y = paddingt; + y += this.up; + var g = FakeSVG('g', Diagram.STROKE_ODD_PIXEL_LENGTH ? {transform:'translate(.5 .5)'} : {}); + for(var i = 0; i < this.items.length; i++) { + var item = this.items[i]; + if(item.needsSpace) { + Path(x,y).h(10).addTo(g); + x += 10; + } + item.format(x, y, item.width).addTo(g); + x += item.width; + if(item.needsSpace) { + Path(x,y).h(10).addTo(g); + x += 10; + } + } + this.attrs.width = this.width + paddingl + paddingr; + this.attrs.height = this.up + this.down + paddingt + paddingb; + this.attrs.viewBox = "0 0 " + this.attrs.width + " " + this.attrs.height; + g.addTo(this); + this.formatted = true; + return this; + } + Diagram.prototype.addTo = function(parent) { + var scriptTag = document.getElementsByTagName('script'); + scriptTag = scriptTag[scriptTag.length - 1]; + var parentTag = scriptTag.parentNode; + parent = parent || parentTag; + return this.$super.addTo.call(this, parent); + } + Diagram.prototype.toSVG = function() { + if (!this.formatted) { + this.format(); + } + return this.$super.toSVG.call(this); + } + Diagram.prototype.toString = function() { + if (!this.formatted) { + this.format(); + } + return this.$super.toString.call(this); + } + + function Sequence(items) { + if(!(this instanceof Sequence)) return new Sequence([].slice.call(arguments)); + FakeSVG.call(this, 'g'); + this.items = items.map(wrapString); + this.width = this.items.reduce(function(sofar, el) { return sofar + el.width + (el.needsSpace?20:0)}, 0); + this.up = this.items.reduce(function(sofar,el) { return Math.max(sofar, el.up)}, 0); + this.down = this.items.reduce(function(sofar,el) { return Math.max(sofar, el.down)}, 0); + } + subclassOf(Sequence, FakeSVG); + Sequence.prototype.format = function(x,y,width) { + // Hook up the two sides if this is narrower than its stated width. + var gaps = determineGaps(width, this.width); + Path(x,y).h(gaps[0]).addTo(this); + Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this); + x += gaps[0]; + + for(var i = 0; i < this.items.length; i++) { + var item = this.items[i]; + if(item.needsSpace) { + Path(x,y).h(10).addTo(this); + x += 10; + } + item.format(x, y, item.width).addTo(this); + x += item.width; + if(item.needsSpace) { + Path(x,y).h(10).addTo(this); + x += 10; + } + } + return this; + } + + function Choice(normal, items) { + if(!(this instanceof Choice)) return new Choice(normal, [].slice.call(arguments,1)); + FakeSVG.call(this, 'g'); + if( typeof normal !== "number" || normal !== Math.floor(normal) ) { + throw new TypeError("The first argument of Choice() must be an integer."); + } else if(normal < 0 || normal >= items.length) { + throw new RangeError("The first argument of Choice() must be an index for one of the items."); + } else { + this.normal = normal; + } + this.items = items.map(wrapString); + this.width = this.items.reduce(function(sofar, el){return Math.max(sofar, el.width)},0) + Diagram.ARC_RADIUS*4; + this.up = this.down = 0; + for(var i = 0; i < this.items.length; i++) { + var item = this.items[i]; + if(i < normal) { this.up += Math.max(Diagram.ARC_RADIUS,item.up + item.down + Diagram.VERTICAL_SEPARATION); } + if(i == normal) { this.up += Math.max(Diagram.ARC_RADIUS, item.up); this.down += Math.max(Diagram.ARC_RADIUS, item.down); } + if(i > normal) { this.down += Math.max(Diagram.ARC_RADIUS,Diagram.VERTICAL_SEPARATION + item.up + item.down); } + } + } + subclassOf(Choice, FakeSVG); + Choice.prototype.format = function(x,y,width) { + // Hook up the two sides if this is narrower than its stated width. + var gaps = determineGaps(width, this.width); + Path(x,y).h(gaps[0]).addTo(this); + Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this); + x += gaps[0]; + + var last = this.items.length -1; + var innerWidth = this.width - Diagram.ARC_RADIUS*4; + + // Do the elements that curve above + for(var i = this.normal - 1; i >= 0; i--) { + var item = this.items[i]; + if( i == this.normal - 1 ) { + var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.items[i+1].up + Diagram.VERTICAL_SEPARATION + item.down); + } + Path(x,y).arc('se').up(distanceFromY - Diagram.ARC_RADIUS*2).arc('wn').addTo(this); + item.format(x+Diagram.ARC_RADIUS*2,y - distanceFromY,innerWidth).addTo(this); + Path(x+Diagram.ARC_RADIUS*2+innerWidth, y-distanceFromY).arc('ne').down(distanceFromY - Diagram.ARC_RADIUS*2).arc('ws').addTo(this); + distanceFromY += Math.max(Diagram.ARC_RADIUS, item.up + Diagram.VERTICAL_SEPARATION + (i == 0 ? 0 : this.items[i-1].down)); + } + + // Do the straight-line path. + Path(x,y).right(Diagram.ARC_RADIUS*2).addTo(this); + this.items[this.normal].format(x+Diagram.ARC_RADIUS*2, y, innerWidth).addTo(this); + Path(x+Diagram.ARC_RADIUS*2+innerWidth, y).right(Diagram.ARC_RADIUS*2).addTo(this); + + // Do the elements that curve below + for(var i = this.normal+1; i <= last; i++) { + var item = this.items[i]; + if( i == this.normal + 1 ) { + var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.items[i-1].down + Diagram.VERTICAL_SEPARATION + item.up); + } + Path(x,y).arc('ne').down(distanceFromY - Diagram.ARC_RADIUS*2).arc('ws').addTo(this); + item.format(x+Diagram.ARC_RADIUS*2, y+distanceFromY, innerWidth).addTo(this); + Path(x+Diagram.ARC_RADIUS*2+innerWidth, y+distanceFromY).arc('se').up(distanceFromY - Diagram.ARC_RADIUS*2).arc('wn').addTo(this); + distanceFromY += Math.max(Diagram.ARC_RADIUS, item.down + Diagram.VERTICAL_SEPARATION + (i == last ? 0 : this.items[i+1].up)); + } + + return this; + } + + function Optional(item, skip) { + if( skip === undefined ) + return Choice(1, Skip(), item); + else if ( skip === "skip" ) + return Choice(0, Skip(), item); + else + throw "Unknown value for Optional()'s 'skip' argument."; + } + + function OneOrMore(item, rep) { + if(!(this instanceof OneOrMore)) return new OneOrMore(item, rep); + FakeSVG.call(this, 'g'); + rep = rep || (new Skip); + this.item = wrapString(item); + this.rep = wrapString(rep); + this.width = Math.max(this.item.width, this.rep.width) + Diagram.ARC_RADIUS*2; + this.up = this.item.up; + this.down = Math.max(Diagram.ARC_RADIUS*2, this.item.down + Diagram.VERTICAL_SEPARATION + this.rep.up + this.rep.down); + } + subclassOf(OneOrMore, FakeSVG); + OneOrMore.prototype.needsSpace = true; + OneOrMore.prototype.format = function(x,y,width) { + // Hook up the two sides if this is narrower than its stated width. + var gaps = determineGaps(width, this.width); + Path(x,y).h(gaps[0]).addTo(this); + Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this); + x += gaps[0]; + + // Draw item + Path(x,y).right(Diagram.ARC_RADIUS).addTo(this); + this.item.format(x+Diagram.ARC_RADIUS,y,this.width-Diagram.ARC_RADIUS*2).addTo(this); + Path(x+this.width-Diagram.ARC_RADIUS,y).right(Diagram.ARC_RADIUS).addTo(this); + + // Draw repeat arc + var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.item.down+Diagram.VERTICAL_SEPARATION+this.rep.up); + Path(x+Diagram.ARC_RADIUS,y).arc('nw').down(distanceFromY-Diagram.ARC_RADIUS*2).arc('ws').addTo(this); + this.rep.format(x+Diagram.ARC_RADIUS, y+distanceFromY, this.width - Diagram.ARC_RADIUS*2).addTo(this); + Path(x+this.width-Diagram.ARC_RADIUS, y+distanceFromY).arc('se').up(distanceFromY-Diagram.ARC_RADIUS*2).arc('en').addTo(this); + + return this; + } + + function ZeroOrMore(item, rep, skip) { + return Optional(OneOrMore(item, rep), skip); + } + + function Start() { + if(!(this instanceof Start)) return new Start(); + FakeSVG.call(this, 'path'); + this.width = 20; + this.up = 10; + this.down = 10; + } + subclassOf(Start, FakeSVG); + Start.prototype.format = function(x,y) { + this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 10 -20 v 20 m -10 -10 h 20.5'; + return this; + } + + function End() { + if(!(this instanceof End)) return new End(); + FakeSVG.call(this, 'path'); + this.width = 20; + this.up = 10; + this.down = 10; + } + subclassOf(End, FakeSVG); + End.prototype.format = function(x,y) { + this.attrs.d = 'M '+x+' '+y+' h 20 m -10 -10 v 20 m 10 -20 v 20'; + return this; + } + + function Terminal(text) { + if(!(this instanceof Terminal)) return new Terminal(text); + FakeSVG.call(this, 'g'); + this.text = text; + this.width = text.length * 8 + 20; /* Assume that each char is .5em, and that the em is 16px */ + this.up = 11; + this.down = 11; + } + subclassOf(Terminal, FakeSVG); + Terminal.prototype.needsSpace = true; + Terminal.prototype.format = function(x, y, width) { + // Hook up the two sides if this is narrower than its stated width. + var gaps = determineGaps(width, this.width); + Path(x,y).h(gaps[0]).addTo(this); + Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this); + x += gaps[0]; + + FakeSVG('rect', {x:x, y:y-11, width:this.width, height:this.up+this.down, rx:10, ry:10}).addTo(this); + FakeSVG('text', {x:x+this.width/2, y:y+4}, this.text).addTo(this); + return this; + } + + function NonTerminal(text) { + if(!(this instanceof NonTerminal)) return new NonTerminal(text); + FakeSVG.call(this, 'g'); + this.text = text; + this.width = text.length * 8 + 20; + this.up = 11; + this.down = 11; + } + subclassOf(NonTerminal, FakeSVG); + NonTerminal.prototype.needsSpace = true; + NonTerminal.prototype.format = function(x, y, width) { + // Hook up the two sides if this is narrower than its stated width. + var gaps = determineGaps(width, this.width); + Path(x,y).h(gaps[0]).addTo(this); + Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this); + x += gaps[0]; + + FakeSVG('rect', {x:x, y:y-11, width:this.width, height:this.up+this.down}).addTo(this); + FakeSVG('text', {x:x+this.width/2, y:y+4}, this.text).addTo(this); + return this; + } + + function Comment(text) { + if(!(this instanceof Comment)) return new Comment(text); + FakeSVG.call(this, 'g'); + this.text = text; + this.width = text.length * 7 + 10; + this.up = 11; + this.down = 11; + } + subclassOf(Comment, FakeSVG); + Comment.prototype.needsSpace = true; + Comment.prototype.format = function(x, y, width) { + // Hook up the two sides if this is narrower than its stated width. + var gaps = determineGaps(width, this.width); + Path(x,y).h(gaps[0]).addTo(this); + Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this); + x += gaps[0]; + + FakeSVG('text', {x:x+this.width/2, y:y+5, class:'comment'}, this.text).addTo(this); + return this; + } + + function Skip() { + if(!(this instanceof Skip)) return new Skip(); + FakeSVG.call(this, 'g'); + this.width = 0; + this.up = 0; + this.down = 0; + } + subclassOf(Skip, FakeSVG); + Skip.prototype.format = function(x, y, width) { + Path(x,y).right(width).addTo(this); + return this; + } + +/* TiddlyWiki: added linking ability */ + function Link(target, item) { + if(!(this instanceof Link)) return new Link(target, item); + FakeSVG.call(this, 'a', {'xlink:href': target}); + this.item = item; + this.width = item.width; + this.up = item.up; + this.down = item.down; + } + subclassOf(Link, FakeSVG); + Link.prototype.format = function(x, y, width) { + this.item.format(x,y,width).addTo(this); + return this; + } + +/* TiddlyWiki: this block replaces the export mechanism in the original library */ + if (exports) { + exports.Diagram = Diagram; + exports.Sequence = Sequence; + exports.Choice = Choice; + exports.Optional = Optional; + exports.OneOrMore = OneOrMore; + exports.ZeroOrMore = ZeroOrMore; + exports.Terminal = Terminal; + exports.NonTerminal = NonTerminal; + exports.Comment = Comment; + exports.Skip = Skip; + exports.Link = Link; + }; +})( + { + VERTICAL_SEPARATION: 8, + ARC_RADIUS: 10, + DIAGRAM_CLASS: 'railroad-diagram', + STROKE_ODD_PIXEL_LENGTH: true, + INTERNAL_ALIGNMENT: 'center', + } +); + +/* TiddlyWiki: removed assignments to properties of the window object */ diff --git a/plugins/tiddlywiki/railroad/files/tiddlywiki.files b/plugins/tiddlywiki/railroad/files/tiddlywiki.files new file mode 100644 index 000000000..f479f394d --- /dev/null +++ b/plugins/tiddlywiki/railroad/files/tiddlywiki.files @@ -0,0 +1,21 @@ +{ + "tiddlers": [ + { + "file": "railroad-diagrams.css", + "fields": { + "type": "text/css", + "title": "$:/plugins/tiddlywiki/railroad/railroad-diagrams.css", + "tags": "$:/tags/Stylesheet" + } + },{ + "file": "railroad-diagrams.js", + "fields": { + "type": "application/javascript", + "title": "$:/plugins/tiddlywiki/railroad/railroad-diagrams.js", + "module-type": "library" + }, + "prefix": "(function(document) {\n", + "suffix": "\n})($tw.node ? $tw.fakeDocument : window.document)\n" + } + ] +} diff --git a/plugins/tiddlywiki/railroad/parser.js b/plugins/tiddlywiki/railroad/parser.js new file mode 100644 index 000000000..1c8116e02 --- /dev/null +++ b/plugins/tiddlywiki/railroad/parser.js @@ -0,0 +1,334 @@ +/*\ +title: $:/plugins/tiddlywiki/railroad/parser.js +type: application/javascript +module-type: library + +Parser for the source of a railroad diagram. + +[:x] optional, normally included +[x] optional, normally omitted +{x} one or more +{x +","} one or more, comma-separated +[{:x}] zero or more, normally included +[{:x +","}] zero or more, comma-separated, normally included +[{x}] zero or more, normally omitted +[{x +","}] zero or more, comma-separated, normally omitted +x y z sequence +<-x y z-> explicit sequence +(x|y|z) alternatives +(x|:y|z) alternatives, normally y +"x" terminal +<"x"> nonterminal +/"blah"/ comment +- dummy + +"x" can also be written 'x' or """x""" + +Future extensions: +[[x|tiddler]] link +{{tiddler}} transclusion + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var components = require("$:/plugins/tiddlywiki/railroad/components.js").components; + +var Parser = function(source) { + this.source = source; + this.tokens = this.tokenise(source); + this.tokenPos = 0; + this.advance(); + this.root = new components.Root(this.parseContent()); + this.checkFinished(); +}; + +/////////////////////////// Parser dispatch + +Parser.prototype.parseContent = function() { + var content = []; + // Parse zero or more components + while(true) { + var component = this.parseComponent(); + if(!component) { + break; + } + content.push(component); + } + return content; +}; + +Parser.prototype.parseComponent = function() { + var component = null; + if(this.token) { + if(this.at("string")) { + component = this.parseTerminal(); + } else if(this.at("identifier")) { + component = this.parseIdentifier(); + } else { + switch(this.token.value) { + case "[": + component = this.parseOptional(); + break; + case "{": + component = this.parseRepeated(); + break; + case "<": + component = this.parseNonterminal(); + break; + case "(": + component = this.parseChoice(); + break; + case "/": + component = this.parseComment(); + break; + case "<-": + component = this.parseSequence(); + break; + case "-": + component = this.parseDummy(); + break; + } + } + } + return component; +}; + +/////////////////////////// Specific components + +Parser.prototype.parseChoice = function() { + // Consume the ( + this.advance(); + var content = [], + colon = -1; + do { + // Allow at most one branch to be prefixed with a colon + if(colon === -1 && this.eat(":")) { + colon = content.length; + } + // Parse the next branch + content.push(this.parseContent()); + } while(this.eat("|")); + // Create a component + var component = new components.Choice(content,colon === -1 ? 0 : colon); + // Consume the closing bracket + this.close(")"); + return component; +}; + +Parser.prototype.parseComment = function() { + // Consume the / + this.advance(); + // The comment's content should be in a string literal + this.expectStringLiteral("/"); + // Create a component + var component = new components.Comment(this.token.value); + // Consume the string literal + this.advance(); + // Consume the closing / + this.close("/"); + return component; +}; + +Parser.prototype.parseDummy = function() { + // Consume the - + this.advance(); + // Create a component + return new components.Dummy(); +}; + +Parser.prototype.parseIdentifier = function() { + // Create a component + var component = new components.Nonterminal(this.token.value); + // Consume the identifier + this.advance(); + return component; +}; + + +Parser.prototype.parseNonterminal = function() { + // Consume the < + this.advance(); + // The nonterminal's name should be in a string literal + this.expectStringLiteral("<"); + // Create a component + var component = new components.Nonterminal(this.token.value); + // Consume the string literal + this.advance(); + // Consume the closing bracket + this.close(">"); + return component; +}; + +Parser.prototype.parseOptional = function() { + // Consume the [ + this.advance(); + // Consume the { if there is one + var repeated = this.eat("{"); + // Note whether omission is the normal route + var normal = this.eat(":"); + // Parse the content + var content = this.parseContent(), + separator = null; + // Parse the separator if there is one + if(repeated && this.eat("+")) { + separator = this.parseContent(); + } + // Create a component + var component = repeated ? new components.OptionalRepeated(content,separator,normal) : new components.Optional(content,normal); + // Consume the closing brackets + if(repeated) { + this.close("}"); + } + this.close("]"); + return component; +}; + +Parser.prototype.parseRepeated = function() { + // Consume the { + this.advance(); + // Parse the content + var content = this.parseContent(), + separator = null; + // Parse the separator if there is one + if(this.eat("+")) { + separator = this.parseContent(); + } + // Create a component + var component = new components.Repeated(content,separator); + // Consume the closing bracket + this.close("}"); + return component; +}; + +Parser.prototype.parseSequence = function() { + // Consume the ~ + this.advance(); + // Parse the content + var content = this.parseContent(); + // Create a component + var component = new components.Sequence(content); + // Consume the closing ~ + this.close("->"); + return component; +}; + +Parser.prototype.parseTerminal = function() { + var component = new components.Terminal(this.token.value); + // Consume the string literal + this.advance(); + return component; +}; + +/////////////////////////// Token manipulation + +Parser.prototype.advance = function() { + if(this.tokenPos >= this.tokens.length) { + this.token = null; + } + this.token = this.tokens[this.tokenPos++]; +}; + +Parser.prototype.at = function(token) { + return this.token && (this.token.type === token || this.token.type === "token" && this.token.value === token); +}; + +Parser.prototype.eat = function(token) { + var at = this.at(token); + if(at) { + this.advance(); + } + return at; +}; + +Parser.prototype.expectStringLiteral = function(preamble) { + if(!this.at("string")) { + throw "String expected after " + preamble; + } +}; + +Parser.prototype.close = function(token) { + if(!this.eat(token)) { + throw "Closing " + token + " expected"; + } +}; + +Parser.prototype.checkFinished = function() { + if(this.token) { + throw "Syntax error at " + this.token.value; + } +}; + +/////////////////////////// Tokenisation + +Parser.prototype.tokenise = function(source) { + var tokens = [], + pos = 0, + c, s, token; + while(pos < source.length) { + // Initialise this iteration + s = token = null; + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Avoid falling off the end of the string + if (pos >= source.length) { + break; + } + // Examine the next character + c = source.charAt(pos); + if("\"'".indexOf(c) !== -1) { + // String literal + token = $tw.utils.parseStringLiteral(source,pos); + if(!token) { + throw "Unterminated string literal"; + } + } else if("[]{}".indexOf(c) !== -1) { + // Single or double character + s = source.charAt(pos+1) === c ? c + c : c; + } else if(c === "<") { + // < or <- + s = source.charAt(pos+1) === "-" ? "<-" : "<"; + } else if(c === "-") { + // - or -> + s = source.charAt(pos+1) === ">" ? "->" : "-"; + } else if("()>+|/:".indexOf(c) !== -1) { + // Single character + s = c; + } else if(c.match(/[a-zA-Z]/)) { + // Identifier + token = this.readIdentifier(source,pos); + } else { + throw "Syntax error at " + c; + } + // Add our findings to the return array + if(token) { + tokens.push(token); + } else { + token = $tw.utils.parseTokenString(source,pos,s); + tokens.push(token); + } + // Prepare for the next character + pos = token.end; + } + return tokens; +}; + +Parser.prototype.readIdentifier = function(source,pos) { + var re = /([a-zA-Z0-9_.-]+)/g; + re.lastIndex = pos; + var match = re.exec(source); + if(match && match.index === pos) { + return {type: "identifier", value: match[1], start: pos, end: pos + match[1].length}; + } else { + throw "Invalid identifier"; + } +}; + +/////////////////////////// Exports + +exports.parser = Parser; + +})(); \ No newline at end of file diff --git a/plugins/tiddlywiki/railroad/plugin.info b/plugins/tiddlywiki/railroad/plugin.info new file mode 100644 index 000000000..6ed870514 --- /dev/null +++ b/plugins/tiddlywiki/railroad/plugin.info @@ -0,0 +1,7 @@ +{ + "title": "$:/plugins/tiddlywiki/railroad", + "description": "Plugin for generating SVG railroad diagrams", + "author": "Astrid Elocson", + "plugin-type": "plugin", + "list": "readme syntax example" +} diff --git a/plugins/tiddlywiki/railroad/wrapper.js b/plugins/tiddlywiki/railroad/wrapper.js new file mode 100644 index 000000000..fec71ba76 --- /dev/null +++ b/plugins/tiddlywiki/railroad/wrapper.js @@ -0,0 +1,70 @@ +/*\ +title: $:/plugins/tiddlywiki/railroad/wrapper.js +type: application/javascript +module-type: widget + +Wrapper for `railroad-diagrams.js` that provides a `<$railroad>` widget. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Parser = require("$:/plugins/tiddlywiki/railroad/parser.js").parser, + Widget = require("$:/core/modules/widgets/widget.js").widget; + +var RailroadWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +RailroadWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +RailroadWidget.prototype.render = function(parent,nextSibling) { + // Housekeeping + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + // Get the source text + var source = this.getAttribute("text",this.parseTreeNode.text || ""); + // Create a div to contain the SVG or error message + var div = this.document.createElement("div"); + try { + // Parse the source + var parser = new Parser(source); + if(this.getAttribute("mode","svg") === "debug") { + var output = ["
"];
+			parser.root.debug(output, "");
+			output.push("
"); + div.innerHTML = output.join(""); + } else { + div.innerHTML = parser.root.toSvg(); + } + } catch(ex) { + div.className = "tc-error"; + div.textContent = ex; + } + // Insert it into the DOM + parent.insertBefore(div,nextSibling); + this.domNodes.push(div); +}; + +RailroadWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.text) { + this.refreshSelf(); + return true; + } + return false; +}; + +exports.railroad = RailroadWidget; + +})(); \ No newline at end of file From 388464190d46b6f9186b2f6208cf27bea7b6fc68 Mon Sep 17 00:00:00 2001 From: Astrid Elocson Date: Sat, 3 Jan 2015 20:16:46 +0000 Subject: [PATCH 3/6] Use railroad diagrams to explain filter syntax --- .../tw5.com/tiddlers/concepts/Filters.tid | 2 +- .../tw5.com/tiddlers/filters/FilterSyntax.tid | 80 +++++++++++++++++++ .../nodejs/TiddlerFilter Formal Grammar.tid | 61 -------------- editions/tw5.com/tiddlywiki.info | 3 +- 4 files changed, 83 insertions(+), 63 deletions(-) create mode 100644 editions/tw5.com/tiddlers/filters/FilterSyntax.tid delete mode 100644 editions/tw5.com/tiddlers/nodejs/TiddlerFilter Formal Grammar.tid diff --git a/editions/tw5.com/tiddlers/concepts/Filters.tid b/editions/tw5.com/tiddlers/concepts/Filters.tid index e09868c13..ac0d8c1ec 100644 --- a/editions/tw5.com/tiddlers/concepts/Filters.tid +++ b/editions/tw5.com/tiddlers/concepts/Filters.tid @@ -7,7 +7,7 @@ type: text/vnd.tiddlywiki Filters are used in TiddlyWiki to choose tiddlers by specifying simple match criteria. They are used by widgets like the ListWidget and the CountWidget to perform operations on multiple tiddlers at once. * [[Introduction to Filters]] is a step-by-step introduction to how filters are used -* [[Filter Formal Grammar]] is a technical explanation of the filter syntax. +* [[Filter Syntax]] explains the exact rules for writing filters The most common filter operators are: diff --git a/editions/tw5.com/tiddlers/filters/FilterSyntax.tid b/editions/tw5.com/tiddlers/filters/FilterSyntax.tid new file mode 100644 index 000000000..4b78bd217 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/FilterSyntax.tid @@ -0,0 +1,80 @@ +created: 20140210141217955 +modified: 20150102174633890 +tags: Filters +title: Filter Syntax +type: text/vnd.tiddlywiki + +[[Filter expressions|Filters]] follow a grammar that is presented here, using [[RailroadDiagrams]], for those who find formal syntax descriptions helpful. However, you can write your own filter expressions without needing to understand this tiddler. + +--- + +;filter +: <$railroad text=""" +{ [:whitespace] ("+" | :- | "-") run } +"""/> + +A sequence of runs is evaluated from left to right, as follows: + +|!Sequence |!Interpretation | +|`run1 run2` |union of the sets, i.e. the tiddlers in //either// run1 //or// run2 | +|`run1 -run2` |difference of the sets, i.e. run1 but excluding any tiddlers in run2 | +|`run1 +run2` |run2 takes run1 as its input | + +The first run takes `[all[tiddlers]]` as its input, i.e. the set of all non-missing tiddlers. + +--- + +;run +: <$railroad text=""" +( "[" {step} "]" + | + '"' [{/'any character except "'/}] '"' + | + "'" [{/"any character except '"/}] '"' + | + [{/"any character except [ ] or whitespace"/}] +) +"""/> +* The last three options are short for `[title[text]]` +* A run evaluates each of its steps and returns the intersection of the results + +--- + +;step +: <$railroad text=""" +[:"!"] operator parameter +"""/> +* A step returns a set of tiddlers, in the form of a TitleList + +--- + +;operator +: <$railroad text=""" +( keyword [:":" fieldname] | fieldname ) +"""/> +* Keywords are reserved names ([[is|FilterOperator: is]], [[has|FilterOperator: has]], [[tag|FilterOperator: tag]], etc) of specific filtering functions +* A fieldname on its own implies the keyword `field` +* An entirely omitted operator defaults to `title` + +--- + +;parameter +: <$railroad text=""" +( "[" [{/"any character except ]"/}] "]" + | + "{" [{/"any character except }"/}] "}" + | + "<" [{/"any character except >"/}] ">" +) +"""/> +* `[`...`]` encloses a literal parameter +* `{`...`}` encloses a TextReference parameter +* `<`...`>` encloses a [[variable|Variables]] parameter + +--- + +;whitespace +: <$railroad text=""" +{( "space" | "tab" | "linefeed" | "return" | "vertical tab" | "formfeed" )} +"""/> +* A match for the JavaScript regular expression `\s+` diff --git a/editions/tw5.com/tiddlers/nodejs/TiddlerFilter Formal Grammar.tid b/editions/tw5.com/tiddlers/nodejs/TiddlerFilter Formal Grammar.tid deleted file mode 100644 index 24b7501f5..000000000 --- a/editions/tw5.com/tiddlers/nodejs/TiddlerFilter Formal Grammar.tid +++ /dev/null @@ -1,61 +0,0 @@ -created: 20140210141217955 -modified: 20140912145655663 -tags: Filters -title: Filter Formal Grammar -type: text/vnd.tiddlywiki - -[[Filter expressions|Filters]] follow a grammar that is presented here for those who find formal syntax descriptions helpful. However, you can write your own filter expressions without needing to understand this tiddler. - -* [//x//] denotes an optional //x// -* (//x//)... denotes 1 or more instances of //x// -* Literal characters are `monospaced` -* Top-level bullets indicate alternative possibilities -* Second-level bullets are comments and clarifications - -;filter -* ( [//whitespace//] [`+`|`-`] //run// )... - -;run -* `[` (//operation//)... `]` -* `"` //text// `"` -** The text can contain anything but `"` -* `'` //text// `'` -** The text can contain anything but `'` -* //text// -** The text can contain anything but whitespace and `[` and `]` -** These last three alternatives are short for `[title[text]]` - -;operation -* [`!`] //operator// //operand// - -;operator -* [//keyword//] [`:` //fieldname//] -** Keywords (`is`, `has`, `tag`, etc) are reserved names that identify filter functions -** A fieldname on its own implies the keyword `field` -** An entirely omitted operator defaults to `title` - -;operand -* `[` //text// `]` -** literal -- the text can contain anything but `]` -* `{` //text// `}` -** text reference -- the text can contain anything but `}` -* `<` //text// `>` -** variable -- the text can contain anything but `>` - -;whitespace -* One or more spaces, tabs or linefeeds, i.e. a match for the JavaScript regular expression `\s+` - -!Evaluation - -Each operation returns a set of tiddlers, in the form of a TitleList. - -A run evaluates each of the operations it contains, and returns the intersection of the resulting sets. - -A sequence of runs is evaluated from left to right, as follows: - -|!Sequence |!Interpretation | -|run1 run2 |union of the sets, i.e. the tiddlers in //either// run1 //or// run2 | -|run1 -run2 |difference of the sets, i.e. run1 but excluding any tiddlers in run2 | -|run1 +run2 |run2 takes run1 as its input | - -The first run of a sequence takes `[all[tiddlers]]` as its input, i.e. the set of all non-missing tiddlers. diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index 4f35332d1..54c0e3948 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -4,7 +4,8 @@ "tiddlywiki/googleanalytics", "tiddlywiki/nodewebkitsaver", "tiddlywiki/github-fork-ribbon", - "tiddlywiki/browser-sniff" + "tiddlywiki/browser-sniff", + "tiddlywiki/railroad" ], "themes": [ "tiddlywiki/vanilla", From e319e6557479d3336d0179aa60ac6a9a22cf0b67 Mon Sep 17 00:00:00 2001 From: twMat Date: Sun, 4 Jan 2015 11:44:11 +0100 Subject: [PATCH 4/6] Update TiddlyWiki Hangouts.tid Delinkify word "YouTube" to not mistake it for a link. --- editions/tw5.com/tiddlers/community/TiddlyWiki Hangouts.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editions/tw5.com/tiddlers/community/TiddlyWiki Hangouts.tid b/editions/tw5.com/tiddlers/community/TiddlyWiki Hangouts.tid index fb9fda85b..e91a5becb 100644 --- a/editions/tw5.com/tiddlers/community/TiddlyWiki Hangouts.tid +++ b/editions/tw5.com/tiddlers/community/TiddlyWiki Hangouts.tid @@ -6,6 +6,6 @@ type: text/vnd.tiddlywiki The TiddlyWiki community holds regular Google Hangouts, usually every Tuesday from 4pm to 6pm (UK time). They are announced in the [[TiddlyWiki Google group|https://groups.google.com/d/forum/tiddlywiki]] and on the [[TiddlyWiki Twitter account|https://twitter.com/TiddlyWiki]]. -Past Hangouts are archived in this YouTube playlist: +Past Hangouts are archived in this ~YouTube playlist: From 95897103ef1f7e8b039226ba19b30c41b2f135c0 Mon Sep 17 00:00:00 2001 From: Jermolene Date: Sun, 4 Jan 2015 14:10:13 +0000 Subject: [PATCH 5/6] Update edit-text widget docs --- editions/tw5.com/tiddlers/widgets/EditTextWidget.tid | 1 + 1 file changed, 1 insertion(+) diff --git a/editions/tw5.com/tiddlers/widgets/EditTextWidget.tid b/editions/tw5.com/tiddlers/widgets/EditTextWidget.tid index 563182bb9..4b1393814 100644 --- a/editions/tw5.com/tiddlers/widgets/EditTextWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/EditTextWidget.tid @@ -22,6 +22,7 @@ The content of the `<$edit-text>` widget is ignored. |class |A CSS class to be assigned to the generated HTML editing element | |placeholder |Placeholder text to be displayed when the edit field is empty | |focusPopup |Title of a state tiddler for a popup that is displayed when the editing element has focus | +|focus |Set to "true" to automatically focus the editor after creation | |tag |Overrides the generated HTML editing element tag. Use `textarea` for a multi-line editor | |type |Overrides the generated HTML editing element `type` attribute | |size |The size of the input field (in characters) | From ea6e60e66983ee1184f09c5796ef6c8bceae703a Mon Sep 17 00:00:00 2001 From: Jermolene Date: Sun, 4 Jan 2015 15:53:48 +0000 Subject: [PATCH 6/6] Focus search box on startup --- core/ui/SideBarLists.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/SideBarLists.tid b/core/ui/SideBarLists.tid index bf1e53637..100d6fec8 100644 --- a/core/ui/SideBarLists.tid +++ b/core/ui/SideBarLists.tid @@ -3,7 +3,7 @@ title: $:/core/ui/SideBarLists