2013-10-12 16:05:13 +00:00
/ * \
2013-11-08 08:47:00 +00:00
title : $ : / c o r e / m o d u l e s / w i d g e t s / l i n k . j s
2013-10-12 16:05:13 +00:00
type : application / javascript
2013-11-08 08:47:00 +00:00
module - type : widget
2013-10-12 16:05:13 +00:00
Link widget
\ * /
( function ( ) {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict" ;
2013-11-08 08:47:00 +00:00
var Widget = require ( "$:/core/modules/widgets/widget.js" ) . widget ;
2016-04-04 11:43:40 +00:00
var MISSING _LINK _CONFIG _TITLE = "$:/config/MissingLinks" ;
2013-10-12 16:05:13 +00:00
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 ( ) ;
2014-08-28 21:28:02 +00:00
// Get the value of the tv-wikilinks configuration macro
var wikiLinksMacro = this . getVariable ( "tv-wikilinks" ) ,
2016-04-04 11:43:40 +00:00
useWikiLinks = wikiLinksMacro ? ( wikiLinksMacro . trim ( ) !== "no" ) : true ,
2016-04-20 10:57:38 +00:00
missingLinksEnabled = ! ( this . hideMissingLinks && this . isMissing && ! this . isShadow ) ;
2013-10-21 17:31:04 +00:00
// Render the link if required
2016-04-04 11:43:40 +00:00
if ( useWikiLinks && missingLinksEnabled ) {
2013-10-21 17:31:04 +00:00
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 ) {
2013-10-21 19:20:32 +00:00
var self = this ;
2015-02-23 10:16:44 +00:00
// Sanitise the specified tag
var tag = this . linkTag ;
if ( $tw . config . htmlUnsafeElements . indexOf ( tag ) !== - 1 ) {
tag = "a" ;
}
2013-10-12 16:05:13 +00:00
// Create our element
2015-02-23 10:16:44 +00:00
var domNode = this . document . createElement ( tag ) ;
2013-10-12 16:05:13 +00:00
// Assign classes
2014-08-08 16:19:48 +00:00
var classes = [ ] ;
if ( this . linkClasses ) {
classes . push ( this . linkClasses ) ;
}
2014-08-28 17:13:46 +00:00
classes . push ( "tc-tiddlylink" ) ;
2013-10-12 16:05:13 +00:00
if ( this . isShadow ) {
2014-08-28 17:13:46 +00:00
classes . push ( "tc-tiddlylink-shadow" ) ;
2013-10-12 16:05:13 +00:00
}
if ( this . isMissing && ! this . isShadow ) {
2014-08-28 17:13:46 +00:00
classes . push ( "tc-tiddlylink-missing" ) ;
2013-10-12 16:05:13 +00:00
} else {
if ( ! this . isMissing ) {
2014-08-28 17:13:46 +00:00
classes . push ( "tc-tiddlylink-resolves" ) ;
2013-10-12 16:05:13 +00:00
}
}
2014-04-05 16:37:58 +00:00
domNode . setAttribute ( "class" , classes . join ( " " ) ) ;
2013-10-12 16:05:13 +00:00
// Set an href
2014-08-28 21:28:02 +00:00
var wikiLinkTemplateMacro = this . getVariable ( "tv-wikilink-template" ) ,
2013-10-21 17:31:04 +00:00
wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro . trim ( ) : "#$uri_encoded$" ,
wikiLinkText = wikiLinkTemplate . replace ( "$uri_encoded$" , encodeURIComponent ( this . to ) ) ;
2015-02-20 18:39:13 +00:00
wikiLinkText = wikiLinkText . replace ( "$uri_doubleencoded$" , encodeURIComponent ( encodeURIComponent ( this . to ) ) ) ;
2015-02-20 21:45:40 +00:00
wikiLinkText = this . getVariable ( "tv-get-export-link" , { params : [ { name : "to" , value : this . to } ] , defaultValue : wikiLinkText } ) ;
2015-02-23 10:16:44 +00:00
if ( tag === "a" ) {
2015-03-25 11:11:07 +00:00
domNode . setAttribute ( "href" , wikiLinkText ) ;
}
if ( this . tabIndex ) {
domNode . setAttribute ( "tabindex" , this . tabIndex ) ;
2015-02-23 10:16:44 +00:00
}
2014-03-08 16:06:57 +00:00
// Set the tooltip
2014-04-05 16:37:58 +00:00
// HACK: Performance issues with re-parsing the tooltip prevent us defaulting the tooltip to "<$transclude field='tooltip'><$transclude field='title'/></$transclude>"
2014-08-28 21:28:02 +00:00
var tooltipWikiText = this . tooltip || this . getVariable ( "tv-wikilink-tooltip" ) ;
2014-03-08 16:06:57 +00:00
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 ) ;
2014-03-06 12:38:47 +00:00
}
2014-06-17 06:54:10 +00:00
if ( this [ "aria-label" ] ) {
domNode . setAttribute ( "aria-label" , this [ "aria-label" ] ) ;
}
2013-10-12 16:05:13 +00:00
// Add a click event handler
2013-10-25 22:22:46 +00:00
$tw . utils . addEventListeners ( domNode , [
2013-10-26 07:36:43 +00:00
{ name : "click" , handlerObject : this , handlerMethod : "handleClickEvent" } ,
2013-10-25 22:22:46 +00:00
] ) ;
2015-02-23 10:16:44 +00:00
if ( this . draggable === "yes" ) {
$tw . utils . addEventListeners ( domNode , [
{ name : "dragstart" , handlerObject : this , handlerMethod : "handleDragStartEvent" } ,
{ name : "dragend" , handlerObject : this , handlerMethod : "handleDragEndEvent" }
] ) ;
}
2013-10-12 16:05:13 +00:00
// Insert the link into the DOM and render any children
parent . insertBefore ( domNode , nextSibling ) ;
this . renderChildren ( domNode , null ) ;
this . domNodes . push ( domNode ) ;
} ;
2015-01-06 01:39:24 +00:00
LinkWidget . prototype . handleClickEvent = function ( event ) {
// Send the click on its way as a navigate event
2013-10-25 22:22:46 +00:00
var bounds = this . domNodes [ 0 ] . getBoundingClientRect ( ) ;
this . dispatchEvent ( {
2014-08-28 20:43:44 +00:00
type : "tm-navigate" ,
2013-10-25 22:22:46 +00:00
navigateTo : this . to ,
2013-12-17 15:42:53 +00:00
navigateFromTitle : this . getVariable ( "storyTiddler" ) ,
2013-10-25 22:22:46 +00:00
navigateFromNode : this ,
navigateFromClientRect : { top : bounds . top , left : bounds . left , width : bounds . width , right : bounds . right , bottom : bounds . bottom , height : bounds . height
2014-02-12 22:00:12 +00:00
} ,
2014-03-03 09:09:13 +00:00
navigateSuppressNavigation : event . metaKey || event . ctrlKey || ( event . button === 1 )
2013-10-25 22:22:46 +00:00
} ) ;
2015-02-23 10:16:44 +00:00
if ( this . domNodes [ 0 ] . hasAttribute ( "href" ) ) {
event . preventDefault ( ) ;
}
2015-08-30 18:12:12 +00:00
event . stopPropagation ( ) ;
return false ;
2013-10-25 22:22:46 +00:00
} ;
2013-10-26 07:36:43 +00:00
LinkWidget . prototype . handleDragStartEvent = function ( event ) {
2014-09-14 09:50:53 +00:00
if ( event . target === this . domNodes [ 0 ] ) {
if ( this . to ) {
2015-02-10 23:17:38 +00:00
$tw . dragInProgress = true ;
2014-09-14 09:50:53 +00:00
// 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 ( ) ;
2015-02-10 22:33:19 +00:00
var jsonData = this . wiki . getTiddlerAsJson ( this . to ) ,
2014-11-03 23:00:11 +00:00
textData = this . wiki . getTiddlerText ( this . to , "" ) ,
2014-11-26 11:13:05 +00:00
title = ( new RegExp ( "^" + $tw . config . textPrimitives . wikiLink + "$" , "mg" ) ) . exec ( this . to ) ? this . to : "[[" + this . to + "]]" ;
2014-09-14 09:50:53 +00:00
// IE doesn't like these content types
if ( ! $tw . browser . isIE ) {
dataTransfer . setData ( "text/vnd.tiddler" , jsonData ) ;
2014-11-03 23:00:11 +00:00
dataTransfer . setData ( "text/plain" , title ) ;
2015-02-11 10:07:10 +00:00
dataTransfer . setData ( "text/x-moz-url" , "data:text/vnd.tiddler," + encodeURIComponent ( jsonData ) ) ;
2014-09-14 09:50:53 +00:00
}
2015-02-11 10:07:10 +00:00
dataTransfer . setData ( "URL" , "data:text/vnd.tiddler," + encodeURIComponent ( jsonData ) ) ;
2014-11-03 23:00:11 +00:00
dataTransfer . setData ( "Text" , title ) ;
2014-09-14 09:50:53 +00:00
event . stopPropagation ( ) ;
} else {
event . preventDefault ( ) ;
2013-12-13 01:32:06 +00:00
}
2013-10-26 07:36:43 +00:00
}
} ;
LinkWidget . prototype . handleDragEndEvent = function ( event ) {
2014-09-14 09:50:53 +00:00
if ( event . target === this . domNodes [ 0 ] ) {
2015-02-10 23:17:38 +00:00
$tw . dragInProgress = false ;
2014-09-14 09:50:53 +00:00
// 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 ) ;
}
2013-10-26 07:36:43 +00:00
}
} ;
2013-10-12 16:05:13 +00:00
/ *
Compute the internal state of the widget
* /
LinkWidget . prototype . execute = function ( ) {
2015-02-23 10:16:44 +00:00
// Pick up our attributes
2013-10-28 23:40:45 +00:00
this . to = this . getAttribute ( "to" , this . getVariable ( "currentTiddler" ) ) ;
2014-03-06 12:38:47 +00:00
this . tooltip = this . getAttribute ( "tooltip" ) ;
2014-06-17 06:54:10 +00:00
this [ "aria-label" ] = this . getAttribute ( "aria-label" ) ;
2014-08-08 16:19:48 +00:00
this . linkClasses = this . getAttribute ( "class" ) ;
2015-02-23 10:16:44 +00:00
this . tabIndex = this . getAttribute ( "tabindex" ) ;
this . draggable = this . getAttribute ( "draggable" , "yes" ) ;
this . linkTag = this . getAttribute ( "tag" , "a" ) ;
2013-10-12 16:05:13 +00:00
// Determine the link characteristics
this . isMissing = ! this . wiki . tiddlerExists ( this . to ) ;
this . isShadow = this . wiki . isShadowTiddler ( this . to ) ;
2016-04-04 11:43:40 +00:00
this . hideMissingLinks = ( $tw . wiki . getTiddlerText ( MISSING _LINK _CONFIG _TITLE , "yes" ) === "no" ) ;
2013-10-12 16:05:13 +00:00
// 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 ( ) ;
2016-04-04 11:43:40 +00:00
if ( changedAttributes . to || changedTiddlers [ this . to ] || changedAttributes [ "aria-label" ] || changedAttributes . tooltip || changedTiddlers [ MISSING _LINK _CONFIG _TITLE ] ) {
2013-10-12 16:05:13 +00:00
this . refreshSelf ( ) ;
return true ;
}
return this . refreshChildren ( changedTiddlers ) ;
} ;
exports . link = LinkWidget ;
} ) ( ) ;