/*\ title: $:/core/modules/widgets/link.js type: application/javascript module-type: widget Link widget \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; var Widget = require("$:/core/modules/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 var classes = []; if(this.linkClasses) { classes.push(this.linkClasses); } classes.push("tc-tiddlylink"); if(this.isShadow) { classes.push("tc-tiddlylink-shadow"); } if(this.isMissing && !this.isShadow) { classes.push("tc-tiddlylink-missing"); } else { if(!this.isMissing) { classes.push("tc-tiddlylink-resolves"); } } domNode.setAttribute("class",classes.join(" ")); // 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); // Set the tooltip // HACK: Performance issues with re-parsing the tooltip prevent us defaulting the tooltip to "<$transclude field='tooltip'><$transclude field='title'/>" var tooltipWikiText = this.tooltip || this.getVariable("tw-wikilink-tooltip"); if(tooltipWikiText) { var tooltipText = this.wiki.renderText("text/plain","text/vnd.tiddlywiki",tooltipWikiText,{ parseAsInline: true, variables: { currentTiddler: this.to }, parentWidget: this }); domNode.setAttribute("title",tooltipText); } if(this["aria-label"]) { domNode.setAttribute("aria-label",this["aria-label"]); } // 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: "tm-navigate", navigateTo: this.to, navigateFromTitle: this.getVariable("storyTiddler"), navigateFromNode: this, navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height }, navigateSuppressNavigation: event.metaKey || event.ctrlKey || (event.button === 1) }); 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,"tc-tiddlylink-dragging"); // Create the drag image elements this.dragImage = this.document.createElement("div"); this.dragImage.className = "tc-tiddler-dragger"; var inner = this.document.createElement("div"); inner.className = "tc-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 cover = this.document.createElement("div"); cover.className = "tc-tiddler-dragger-cover"; cover.style.left = (inner.offsetLeft - 16) + "px"; cover.style.top = (inner.offsetTop - 16) + "px"; cover.style.width = (inner.offsetWidth + 32) + "px"; cover.style.height = (inner.offsetHeight + 32) + "px"; this.dragImage.appendChild(cover); // Set the data transfer properties var dataTransfer = event.dataTransfer; // First the image dataTransfer.effectAllowed = "copy"; if(dataTransfer.setDragImage) { dataTransfer.setDragImage(this.dragImage.firstChild,-16,-16); } // Then the data dataTransfer.clearData(); var jsonData = this.wiki.getTiddlerAsJson(this.to), textData = this.wiki.getTiddlerText(this.to,""); // IE doesn't like these content types if(!$tw.browser.isIE) { dataTransfer.setData("text/vnd.tiddler",jsonData); dataTransfer.setData("text/plain",textData); dataTransfer.setData("text/x-moz-url","data:text/vnd.tiddler," + encodeURI(jsonData)); } dataTransfer.setData("URL","data:text/vnd.tiddler," + encodeURI(jsonData)); dataTransfer.setData("Text",textData); event.stopPropagation(); } else { event.preventDefault(); } }; LinkWidget.prototype.handleDragEndEvent = function(event) { // Remove the dragging class on the element being dragged $tw.utils.removeClass(event.target,"tc-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")); // Get the link title and aria label this.tooltip = this.getAttribute("tooltip"); this["aria-label"] = this.getAttribute("aria-label"); // Get the link classes this.linkClasses = this.getAttribute("class"); // 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] || changedAttributes["aria-label"] || changedAttributes.tooltip) { this.refreshSelf(); return true; } return this.refreshChildren(changedTiddlers); }; exports.link = LinkWidget; })();