blob: 7d90440667ab1884bbaef97bca01050f07fd653a [file] [log] [blame]
aviau039001d2016-09-29 16:39:05 -04001/*
2 * Copyright (c) 2016 SoapBox Innovations Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21*/
22
23'use strict';
24
25;(function (window, linkify, $) {
26 var linkifyJquery = function (jquery, linkify) {
27 'use strict';
28
29 /**
30 Linkify a HTML DOM node
31 */
32
33 var tokenize = linkify.tokenize;
34 var options = linkify.options;
35 var Options = options.Options;
36
37
38 var TEXT_TOKEN = linkify.parser.TOKENS.TEXT;
39
40 var HTML_NODE = 1;
41 var TXT_NODE = 3;
42 /**
43 Given a parent element and child node that the parent contains, replaces
44 that child with the given array of new children
45 */
46 function replaceChildWithChildren(parent, oldChild, newChildren) {
47 var lastNewChild = newChildren[newChildren.length - 1];
48 parent.replaceChild(lastNewChild, oldChild);
49 for (var i = newChildren.length - 2; i >= 0; i--) {
50 parent.insertBefore(newChildren[i], lastNewChild);
51 lastNewChild = newChildren[i];
52 }
53 }
54
55 /**
56 Given an array of MultiTokens, return an array of Nodes that are either
57 (a) Plain Text nodes (node type 3)
58 (b) Anchor tag nodes (usually, unless tag name is overridden in the options)
59
60 Takes the same options as linkifyElement and an optional doc element
61 (this should be passed in by linkifyElement)
62 */
63 function tokensToNodes(tokens, opts, doc) {
64 var result = [];
65
66 for (var _iterator = tokens, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
67 var _ref;
68
69 if (_isArray) {
70 if (_i >= _iterator.length) break;
71 _ref = _iterator[_i++];
72 } else {
73 _i = _iterator.next();
74 if (_i.done) break;
75 _ref = _i.value;
76 }
77
78 var token = _ref;
79
80 if (token.type === 'nl' && opts.nl2br) {
81 result.push(doc.createElement('br'));
82 continue;
83 } else if (!token.isLink || !opts.check(token)) {
84 result.push(doc.createTextNode(token.toString()));
85 continue;
86 }
87
88 var _opts$resolve = opts.resolve(token);
89
90 var formatted = _opts$resolve.formatted;
91 var formattedHref = _opts$resolve.formattedHref;
92 var tagName = _opts$resolve.tagName;
93 var className = _opts$resolve.className;
94 var target = _opts$resolve.target;
95 var events = _opts$resolve.events;
96 var attributes = _opts$resolve.attributes;
97
98 // Build the link
99
100 var link = doc.createElement(tagName);
101 link.setAttribute('href', formattedHref);
102
103 if (className) {
104 link.setAttribute('class', className);
105 }
106
107 if (target) {
108 link.setAttribute('target', target);
109 }
110
111 // Build up additional attributes
112 if (attributes) {
113 for (var attr in attributes) {
114 link.setAttribute(attr, attributes[attr]);
115 }
116 }
117
118 if (events) {
119 for (var event in events) {
120 if (link.addEventListener) {
121 link.addEventListener(event, events[event]);
122 } else if (link.attachEvent) {
123 link.attachEvent('on' + event, events[event]);
124 }
125 }
126 }
127
128 link.appendChild(doc.createTextNode(formatted));
129 result.push(link);
130 }
131
132 return result;
133 }
134
135 // Requires document.createElement
136 function linkifyElementHelper(element, opts, doc) {
137
138 // Can the element be linkified?
139 if (!element || element.nodeType !== HTML_NODE) {
140 throw new Error('Cannot linkify ' + element + ' - Invalid DOM Node type');
141 }
142
143 var ignoreTags = opts.ignoreTags;
144
145 // Is this element already a link?
146 if (element.tagName === 'A' || options.contains(ignoreTags, element.tagName)) {
147 // No need to linkify
148 return element;
149 }
150
151 var childElement = element.firstChild;
152
153 while (childElement) {
154
155 switch (childElement.nodeType) {
156 case HTML_NODE:
157 linkifyElementHelper(childElement, opts, doc);
158 break;
159 case TXT_NODE:
160
161 var str = childElement.nodeValue;
162 var tokens = tokenize(str);
163
164 if (tokens.length === 0 || tokens.length === 1 && tokens[0] instanceof TEXT_TOKEN) {
165 // No node replacement required
166 break;
167 }
168
169 var nodes = tokensToNodes(tokens, opts, doc);
170
171 // Swap out the current child for the set of nodes
172 replaceChildWithChildren(element, childElement, nodes);
173
174 // so that the correct sibling is selected next
175 childElement = nodes[nodes.length - 1];
176
177 break;
178 }
179
180 childElement = childElement.nextSibling;
181 }
182
183 return element;
184 }
185
186 function linkifyElement(element, opts) {
187 var doc = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
188
189
190 try {
191 doc = doc || document || window && window.document || global && global.document;
192 } catch (e) {/* do nothing for now */}
193
194 if (!doc) {
195 throw new Error('Cannot find document implementation. ' + 'If you are in a non-browser environment like Node.js, ' + 'pass the document implementation as the third argument to linkifyElement.');
196 }
197
198 opts = new Options(opts);
199 return linkifyElementHelper(element, opts, doc);
200 }
201
202 // Maintain reference to the recursive helper to cache option-normalization
203 linkifyElement.helper = linkifyElementHelper;
204 linkifyElement.normalize = function (opts) {
205 return new Options(opts);
206 };
207
208 // Applies the plugin to jQuery
209 function apply($) {
210 var doc = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
211
212
213 $.fn = $.fn || {};
214
215 try {
216 doc = doc || document || window && window.document || global && global.document;
217 } catch (e) {/* do nothing for now */}
218
219 if (!doc) {
220 throw new Error('Cannot find document implementation. ' + 'If you are in a non-browser environment like Node.js, ' + 'pass the document implementation as the second argument to linkify/jquery');
221 }
222
223 if (typeof $.fn.linkify === 'function') {
224 // Already applied
225 return;
226 }
227
228 function jqLinkify(opts) {
229 opts = linkifyElement.normalize(opts);
230 return this.each(function () {
231 linkifyElement.helper(this, opts, doc);
232 });
233 }
234
235 $.fn.linkify = jqLinkify;
236
237 $(doc).ready(function () {
238 $('[data-linkify]').each(function () {
239 var $this = $(this);
240 var data = $this.data();
241 var target = data.linkify;
242 var nl2br = data.linkifyNlbr;
243 var options = {
244 attributes: data.linkifyAttributes,
245 defaultProtocol: data.linkifyDefaultProtocol,
246 events: data.linkifyEvents,
247 format: data.linkifyFormat,
248 formatHref: data.linkifyFormatHref,
249 nl2br: !!nl2br && nl2br !== 0 && nl2br !== 'false',
250 tagName: data.linkifyTagname,
251 target: data.linkifyTarget,
252 className: data.linkifyClassName || data.linkifyLinkclass, // linkClass is deprecated
253 validate: data.linkifyValidate,
254 ignoreTags: data.linkifyIgnoreTags
255 };
256 var $target = target === 'this' ? $this : $this.find(target);
257 $target.linkify(options);
258 });
259 });
260 }
261
262 // Try assigning linkifyElement to the browser scope
263 try {
264 var a = !define && (window.linkifyElement = linkifyElement);
265 } catch (e) {}
266
267 return apply;
268 }($, linkify);
269 if (typeof $.fn.linkify !== 'function') {
270 linkifyJquery($);
271 }
272})(window, linkify, jQuery);