From ad4b03506a62d7110cb30aaa3d6f8dbfc712f246 Mon Sep 17 00:00:00 2001 From: Jermolene Date: Thu, 17 Apr 2014 12:52:32 +0100 Subject: [PATCH] Added wikitext image support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’ve added a parser to recognise the `[img[URL or tiddler title]]` format, and an associated image widget. --- .../modules/parsers/wikiparser/rules/image.js | 137 ++++++++++++++++++ core/modules/widgets/image.js | 123 ++++++++++++++++ .../tw5.com/tiddlers/widgets/ImageWidget.tid | 20 +++ .../tiddlers/wikitext/Images in WikiText.tid | 39 ++++- 4 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 core/modules/parsers/wikiparser/rules/image.js create mode 100644 core/modules/widgets/image.js create mode 100644 editions/tw5.com/tiddlers/widgets/ImageWidget.tid diff --git a/core/modules/parsers/wikiparser/rules/image.js b/core/modules/parsers/wikiparser/rules/image.js new file mode 100644 index 000000000..11657ca48 --- /dev/null +++ b/core/modules/parsers/wikiparser/rules/image.js @@ -0,0 +1,137 @@ +/*\ +title: $:/core/modules/parsers/wikiparser/rules/image.js +type: application/javascript +module-type: wikirule + +Wiki text inline rule for embedding images. For example: + +``` +[img[http://tiddlywiki.com/fractalveg.jpg]] +[img width=23 height=24 [http://tiddlywiki.com/fractalveg.jpg]] +[img 23x24 [http://tiddlywiki.com/fractalveg.jpg]] +[img width={{!!width}} height={{!!height}} [http://tiddlywiki.com/fractalveg.jpg]] +[img {{!!width}}x{{!!height}} [http://tiddlywiki.com/fractalveg.jpg]] +[img[Description of image|http://tiddlywiki.com/fractalveg.jpg]] +[img[TiddlerTitle]] +[img[Description of image|TiddlerTitle]] +``` + +Generates the `<$image>` widget. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports.name = "image"; +exports.types = {inline: true}; + +exports.init = function(parser) { + this.parser = parser; +}; + +exports.findNextMatch = function(startPos) { + // Find the next tag + this.nextImage = this.findNextImage(this.parser.source,startPos); + return this.nextImage ? this.nextImage.start : undefined; +}; + +exports.parse = function() { + // Move past the match + this.parser.pos = this.nextImage.end; + var node = { + type: "element", + tag: "$image", + attributes: this.nextImage.attributes + }; + return [node]; +}; + +/* +Find the next image from the current position +*/ +exports.findNextImage = function(source,pos) { + // A regexp for finding candidate HTML tags + var reLookahead = /(\[img)/g; + // Find the next candidate + reLookahead.lastIndex = pos; + var match = reLookahead.exec(source); + while(match) { + // Try to parse the candidate as a tag + var tag = this.parseImage(source,match.index); + // Return success + if(tag) { + return tag; + } + // Look for the next match + reLookahead.lastIndex = match.index + 1; + match = reLookahead.exec(source); + } + // Failed + return null; +}; + +/* +Look for an image at the specified position. Returns null if not found, otherwise returns {type: "element", name: "$image", attributes: [], isSelfClosing:, start:, end:,} +*/ +exports.parseImage = function(source,pos) { + var token, + node = { + type: "element", + name: "$image", + start: pos, + attributes: {} + }; + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Look for the `[img` + token = $tw.utils.parseTokenString(source,pos,"[img"); + if(!token) { + return null; + } + pos = token.end; + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Process attributes + if(source.charAt(pos) !== "[") { + var attribute = $tw.utils.parseAttribute(source,pos); + while(attribute) { + node.attributes[attribute.name] = attribute; + pos = attribute.end; + pos = $tw.utils.skipWhiteSpace(source,pos); + if(source.charAt(pos) !== "[") { + // Get the next attribute + attribute = $tw.utils.parseAttribute(source,pos); + } else { + attribute = null; + } + } + } + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Look for the `[` after the attributes + token = $tw.utils.parseTokenString(source,pos,"["); + if(!token) { + return null; + } + pos = token.end; + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Get the source up to the terminating `]]` + token = $tw.utils.parseTokenRegExp(source,pos,/(?:([^|\]]*?)\|)?([^\]]+?)\]\]/g); + if(!token) { + return null; + } + pos = token.end; + if(token.match[1]) { + node.attributes.tooltip = {type: "string", value: token.match[1].trim()}; + } + node.attributes.source = {type: "string", value: (token.match[2] || "").trim()}; + // Update the end position + node.end = pos; + return node; +}; + +})(); diff --git a/core/modules/widgets/image.js b/core/modules/widgets/image.js new file mode 100644 index 000000000..1ab024ef6 --- /dev/null +++ b/core/modules/widgets/image.js @@ -0,0 +1,123 @@ +/*\ +title: $:/core/modules/widgets/image.js +type: application/javascript +module-type: widget + +The image widget displays an image referenced with an external URI or with a local tiddler title. + +``` +<$image src="TiddlerTitle" width="320" height="400" class="classnames"> +``` + +The image source can be the title of an existing tiddler or the URL of an external image. + +External images always generate an HTML `` tag. + +Tiddlers that have a _canonical_uri field generate an HTML `` tag with the src attribute containing the URI. + +Tiddlers that contain image data generate an HTML `` tag with the src attribute containing a base64 representation of the image. + +Tiddlers that contain wikitext could be rendered to a DIV of the usual size of a tiddler, and then transformed to the size requested. + +The width and height attributes are interpreted as a number of pixels, and do not need to include the "px" suffix. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var ImageWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +ImageWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +ImageWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + // Create element + // Determine what type of image it is + var tag = "img", src = "", + tiddler = this.wiki.getTiddler(this.imageSource); + if(!tiddler) { + // The source isn't the title of a tiddler, so we'll assume it's a URL + src = this.imageSource; + } else { + // Check if it is an image tiddler + if(this.wiki.isImageTiddler(this.imageSource)) { + // Render the appropriate element for the image type + var type = tiddler.fields.type, + text = tiddler.fields.text; + switch(type) { + case "application/pdf": + tag = "embed"; + src = "data:application/pdf;base64," + text; + break; + case "image/svg+xml": + src = "data:image/svg+xml," + encodeURIComponent(text); + break; + default: + src = "data:" + type + ";base64," + text; + break; + } + } + } + // Create the element and assign the attributes + var domNode = this.document.createElement(tag); + domNode.setAttribute("src",src); + if(this.imageClass) { + domNode.setAttribute("class",this.imageClass); + } + if(this.imageWidth) { + domNode.setAttribute("width",parseInt(this.imageWidth,10) + "px"); + } + if(this.imageHeight) { + domNode.setAttribute("height",parseInt(this.imageHeight,10) + "px"); + } + if(this.imageTooltip) { + domNode.setAttribute("title",this.imageTooltip); + } + // Insert element + parent.insertBefore(domNode,nextSibling); + this.domNodes.push(domNode); +}; + +/* +Compute the internal state of the widget +*/ +ImageWidget.prototype.execute = function() { + // Get our parameters + this.imageSource = this.getAttribute("source"); + this.imageWidth = this.getAttribute("width"); + this.imageHeight = this.getAttribute("height"); + this.imageClass = this.getAttribute("class"); + this.imageTooltip = this.getAttribute("tooltip"); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +ImageWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.tooltip || changedTiddlers[this.imageSource]) { + this.refreshSelf(); + return true; + } else { + return false; + } +}; + +exports.image = ImageWidget; + +})(); diff --git a/editions/tw5.com/tiddlers/widgets/ImageWidget.tid b/editions/tw5.com/tiddlers/widgets/ImageWidget.tid new file mode 100644 index 000000000..f616ea97d --- /dev/null +++ b/editions/tw5.com/tiddlers/widgets/ImageWidget.tid @@ -0,0 +1,20 @@ +title: ImageWidget +created: 20140416160234142 +modified: 20140416160234142 +tags: widget + +! Introduction + +The image widget displays images that can be specified as a remote URL or the title of a local tiddler containing the image. + +! Content and Attributes + +Any content of the `<$image>` widget is ignored. + +|!Attribute |!Description | +|source |The URL of the image, or the title of an image tiddler | +|width |The width of the image as a number | +|height |The height of the image | +|tooltip |The tooltip to be displayed over the image | +|class |CSS classes to be assigned to the `` element | + diff --git a/editions/tw5.com/tiddlers/wikitext/Images in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Images in WikiText.tid index 8d14363bc..dc3556c7e 100644 --- a/editions/tw5.com/tiddlers/wikitext/Images in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Images in WikiText.tid @@ -1,10 +1,44 @@ created: 20131205160221762 -modified: 20131205160234142 +modified: 20140416160234142 tags: wikitext title: Images in WikiText type: text/vnd.tiddlywiki -To display an image stored in a tiddler just transclude that tiddler: +! Image Formatting + +Images can be included in WikiText with the following syntax: + +``` +[img[Motovun Jack.jpg]] +[img[http://tiddlywiki.com/favicon.ico]] +``` + +If the image source is the title of an image tiddler then that tiddler is directly displayed. Otherwise it is interpreted as a URL and an HTML `` tag is generated with the `src` attribute containing the URL. + +A tooltip can also be specified: + +``` +[img[An explanatory tooltip|Motovun Jack.jpg]] +``` + +Attributes can be provided to specify CSS classes and the image width and height: + +``` +[img width=32 [Motovun Jack.jpg]] +[img width=32 class="tw-image" [Motovun Jack.jpg]] +``` + +Note that attributes can be specified as transclusions or variable references: + +``` +[img width={{!!mywidth}} class=<> [Motovun Jack.jpg]] +``` + +The image syntax is a shorthand for invoking the ImageWidget. + +! Displaying Images via Transclusion + +You can also display an image stored in a tiddler by transcluding that tiddler. The disadvantage of this approach is that there is no direct way to control the size of the image. ``` {{Motovun Jack.jpg}} @@ -13,3 +47,4 @@ To display an image stored in a tiddler just transclude that tiddler: Renders as: {{Motovun Jack.jpg}} +