text: /*\
  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 MISSING_LINK_CONFIG_TITLE = "$:/config/MissingLinks";
  
  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 tv-wikilinks configuration macro
  	var wikiLinksMacro = this.getVariable("tv-wikilinks"),
  		useWikiLinks = wikiLinksMacro ? (wikiLinksMacro.trim() !== "no") : true,
  		missingLinksEnabled = !(this.hideMissingLinks && this.isMissing && !this.isShadow);
  	// Render the link if required
  	if(useWikiLinks && missingLinksEnabled) {
  		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;
  	// Sanitise the specified tag
  	var tag = this.linkTag;
  	if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) {
  		tag = "a";
  	}
  	// Create our element
  	var domNode = this.document.createElement(tag);
  	// 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("tv-wikilink-template"),
  		wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$",
  		wikiLinkText = $tw.utils.replaceString(wikiLinkTemplate,"$uri_encoded$",encodeURIComponent(this.to));
  	wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
  	wikiLinkText = this.getVariable("tv-get-export-link",{params: [{name: "to",value: this.to}],defaultValue: wikiLinkText});
  	if(tag === "a") {
  		domNode.setAttribute("href",wikiLinkText);
  	}
  	if(this.tabIndex) {
  		domNode.setAttribute("tabindex",this.tabIndex);
  	}
  	// Set the tooltip
  	// HACK: Performance issues with re-parsing the tooltip prevent us defaulting the tooltip to "<$transclude field='tooltip'><$transclude field='title'/></$transclude>"
  	var tooltipWikiText = this.tooltip || this.getVariable("tv-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"},
  	]);
  	if(this.draggable === "yes") {
  		$tw.utils.addEventListeners(domNode,[
  			{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 its 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)
  	});
  	if(this.domNodes[0].hasAttribute("href")) {
  		event.preventDefault();
  	}
  	event.stopPropagation();
  	return false;
  };
  
  LinkWidget.prototype.handleDragStartEvent = function(event) {
  	if(event.target === this.domNodes[0]) {
  		if(this.to) {
  			$tw.dragInProgress = true;
  			// 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,""),
  				title = (new RegExp("^" + $tw.config.textPrimitives.wikiLink + "$","mg")).exec(this.to) ? this.to : "[[" + this.to + "]]";
  			// IE doesn't like these content types
  			if(!$tw.browser.isIE) {
  				dataTransfer.setData("text/vnd.tiddler",jsonData);
  				dataTransfer.setData("text/plain",title);
  				dataTransfer.setData("text/x-moz-url","data:text/vnd.tiddler," + encodeURIComponent(jsonData));
  			}
  			dataTransfer.setData("URL","data:text/vnd.tiddler," + encodeURIComponent(jsonData));
  			dataTransfer.setData("Text",title);
  			event.stopPropagation();
  		} else {
  			event.preventDefault();
  		}
  	}
  };
  
  LinkWidget.prototype.handleDragEndEvent = function(event) {
  	if(event.target === this.domNodes[0]) {
  		$tw.dragInProgress = false;
  		// 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() {
  	// Pick up our attributes
  	this.to = this.getAttribute("to",this.getVariable("currentTiddler"));
  	this.tooltip = this.getAttribute("tooltip");
  	this["aria-label"] = this.getAttribute("aria-label");
  	this.linkClasses = this.getAttribute("class");
  	this.tabIndex = this.getAttribute("tabindex");
  	this.draggable = this.getAttribute("draggable","yes");
  	this.linkTag = this.getAttribute("tag","a");
  	// Determine the link characteristics
  	this.isMissing = !this.wiki.tiddlerExists(this.to);
  	this.isShadow = this.wiki.isShadowTiddler(this.to);
  	this.hideMissingLinks = ($tw.wiki.getTiddlerText(MISSING_LINK_CONFIG_TITLE,"yes") === "no");
  	// 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 || changedTiddlers[MISSING_LINK_CONFIG_TITLE]) {
  		this.refreshSelf();
  		return true;
  	}
  	return this.refreshChildren(changedTiddlers);
  };
  
  exports.link = LinkWidget;
  
  })();
  