Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/editor/link.js
2868 views
1
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview A utility class for managing editable links.
17
*
18
* @author [email protected] (Nick Santos)
19
*/
20
21
goog.provide('goog.editor.Link');
22
23
goog.require('goog.array');
24
goog.require('goog.dom');
25
goog.require('goog.dom.NodeType');
26
goog.require('goog.dom.Range');
27
goog.require('goog.dom.TagName');
28
goog.require('goog.editor.BrowserFeature');
29
goog.require('goog.editor.Command');
30
goog.require('goog.editor.Field');
31
goog.require('goog.editor.node');
32
goog.require('goog.editor.range');
33
goog.require('goog.string');
34
goog.require('goog.string.Unicode');
35
goog.require('goog.uri.utils');
36
goog.require('goog.uri.utils.ComponentIndex');
37
38
39
40
/**
41
* Wrap an editable link.
42
* @param {HTMLAnchorElement} anchor The anchor element.
43
* @param {boolean} isNew Whether this is a new link.
44
* @constructor
45
* @final
46
*/
47
goog.editor.Link = function(anchor, isNew) {
48
/**
49
* The link DOM element.
50
* @type {HTMLAnchorElement}
51
* @private
52
*/
53
this.anchor_ = anchor;
54
55
/**
56
* Whether this link represents a link just added to the document.
57
* @type {boolean}
58
* @private
59
*/
60
this.isNew_ = isNew;
61
62
63
/**
64
* Any extra anchors created by the browser from a selection in the same
65
* operation that created the primary link
66
* @type {!Array<HTMLAnchorElement>}
67
* @private
68
*/
69
this.extraAnchors_ = [];
70
};
71
72
73
/**
74
* @return {HTMLAnchorElement} The anchor element.
75
*/
76
goog.editor.Link.prototype.getAnchor = function() {
77
return this.anchor_;
78
};
79
80
81
/**
82
* @return {!Array<HTMLAnchorElement>} The extra anchor elements, if any,
83
* created by the browser from a selection.
84
*/
85
goog.editor.Link.prototype.getExtraAnchors = function() {
86
return this.extraAnchors_;
87
};
88
89
90
/**
91
* @return {string} The inner text for the anchor.
92
*/
93
goog.editor.Link.prototype.getCurrentText = function() {
94
if (!this.currentText_) {
95
var anchor = this.getAnchor();
96
97
var leaf = goog.editor.node.getLeftMostLeaf(anchor);
98
if (leaf.tagName && leaf.tagName == goog.dom.TagName.IMG) {
99
this.currentText_ = leaf.getAttribute('alt');
100
} else {
101
this.currentText_ = goog.dom.getRawTextContent(this.getAnchor());
102
}
103
}
104
return this.currentText_;
105
};
106
107
108
/**
109
* @return {boolean} Whether the link is new.
110
*/
111
goog.editor.Link.prototype.isNew = function() {
112
return this.isNew_;
113
};
114
115
116
/**
117
* Set the url without affecting the isNew() status of the link.
118
* @param {string} url A URL.
119
*/
120
goog.editor.Link.prototype.initializeUrl = function(url) {
121
this.getAnchor().href = url;
122
};
123
124
125
/**
126
* Removes the link, leaving its contents in the document. Note that this
127
* object will no longer be usable/useful after this call.
128
*/
129
goog.editor.Link.prototype.removeLink = function() {
130
goog.dom.flattenElement(this.anchor_);
131
this.anchor_ = null;
132
while (this.extraAnchors_.length) {
133
goog.dom.flattenElement(/** @type {Element} */ (this.extraAnchors_.pop()));
134
}
135
};
136
137
138
/**
139
* Change the link.
140
* @param {string} newText New text for the link. If the link contains all its
141
* text in one descendent, newText will only replace the text in that
142
* one node. Otherwise, we'll change the innerHTML of the whole
143
* link to newText.
144
* @param {string} newUrl A new URL.
145
*/
146
goog.editor.Link.prototype.setTextAndUrl = function(newText, newUrl) {
147
var anchor = this.getAnchor();
148
anchor.href = newUrl;
149
150
// If the text did not change, don't update link text.
151
var currentText = this.getCurrentText();
152
if (newText != currentText) {
153
var leaf = goog.editor.node.getLeftMostLeaf(anchor);
154
155
if (leaf.tagName && leaf.tagName == goog.dom.TagName.IMG) {
156
leaf.setAttribute('alt', newText ? newText : '');
157
} else {
158
if (leaf.nodeType == goog.dom.NodeType.TEXT) {
159
leaf = leaf.parentNode;
160
}
161
162
if (goog.dom.getRawTextContent(leaf) != currentText) {
163
leaf = anchor;
164
}
165
166
goog.dom.removeChildren(leaf);
167
var domHelper = goog.dom.getDomHelper(leaf);
168
goog.dom.appendChild(leaf, domHelper.createTextNode(newText));
169
}
170
171
// The text changed, so force getCurrentText to recompute.
172
this.currentText_ = null;
173
}
174
175
this.isNew_ = false;
176
};
177
178
179
/**
180
* Places the cursor to the right of the anchor.
181
* Note that this is different from goog.editor.range's placeCursorNextTo
182
* in that it specifically handles the placement of a cursor in browsers
183
* that trap you in links, by adding a space when necessary and placing the
184
* cursor after that space.
185
*/
186
goog.editor.Link.prototype.placeCursorRightOf = function() {
187
var anchor = this.getAnchor();
188
// If the browser gets stuck in a link if we place the cursor next to it,
189
// we'll place the cursor after a space instead.
190
if (goog.editor.BrowserFeature.GETS_STUCK_IN_LINKS) {
191
var spaceNode;
192
var nextSibling = anchor.nextSibling;
193
194
// Check if there is already a space after the link. Only handle the
195
// simple case - the next node is a text node that starts with a space.
196
if (nextSibling && nextSibling.nodeType == goog.dom.NodeType.TEXT &&
197
(goog.string.startsWith(nextSibling.data, goog.string.Unicode.NBSP) ||
198
goog.string.startsWith(nextSibling.data, ' '))) {
199
spaceNode = nextSibling;
200
} else {
201
// If there isn't an obvious space to use, create one after the link.
202
var dh = goog.dom.getDomHelper(anchor);
203
spaceNode = dh.createTextNode(goog.string.Unicode.NBSP);
204
goog.dom.insertSiblingAfter(spaceNode, anchor);
205
}
206
207
// Move the selection after the space.
208
var range = goog.dom.Range.createCaret(spaceNode, 1);
209
range.select();
210
} else {
211
goog.editor.range.placeCursorNextTo(anchor, false);
212
}
213
};
214
215
216
/**
217
* Updates the cursor position and link bubble for this link.
218
* @param {goog.editor.Field} field The field in which the link is created.
219
* @param {string} url The link url.
220
* @private
221
*/
222
goog.editor.Link.prototype.updateLinkDisplay_ = function(field, url) {
223
this.initializeUrl(url);
224
this.placeCursorRightOf();
225
field.execCommand(goog.editor.Command.UPDATE_LINK_BUBBLE);
226
};
227
228
229
/**
230
* @return {string?} The modified string for the link if the link
231
* text appears to be a valid link. Returns null if this is not
232
* a valid link address.
233
*/
234
goog.editor.Link.prototype.getValidLinkFromText = function() {
235
var text = goog.string.trim(this.getCurrentText());
236
if (goog.editor.Link.isLikelyUrl(text)) {
237
if (text.search(/:/) < 0) {
238
return 'http://' + goog.string.trimLeft(text);
239
}
240
return text;
241
} else if (goog.editor.Link.isLikelyEmailAddress(text)) {
242
return 'mailto:' + text;
243
}
244
return null;
245
};
246
247
248
/**
249
* After link creation, finish creating the link depending on the type
250
* of link being created.
251
* @param {goog.editor.Field} field The field where this link is being created.
252
*/
253
goog.editor.Link.prototype.finishLinkCreation = function(field) {
254
var linkFromText = this.getValidLinkFromText();
255
if (linkFromText) {
256
this.updateLinkDisplay_(field, linkFromText);
257
} else {
258
field.execCommand(goog.editor.Command.MODAL_LINK_EDITOR, this);
259
}
260
};
261
262
263
/**
264
* Initialize a new link.
265
* @param {HTMLAnchorElement} anchor The anchor element.
266
* @param {string} url The initial URL.
267
* @param {string=} opt_target The target.
268
* @param {Array<HTMLAnchorElement>=} opt_extraAnchors Extra anchors created
269
* by the browser when parsing a selection.
270
* @return {!goog.editor.Link} The link.
271
*/
272
goog.editor.Link.createNewLink = function(
273
anchor, url, opt_target, opt_extraAnchors) {
274
var link = new goog.editor.Link(anchor, true);
275
link.initializeUrl(url);
276
277
if (opt_target) {
278
anchor.target = opt_target;
279
}
280
if (opt_extraAnchors) {
281
link.extraAnchors_ = opt_extraAnchors;
282
}
283
284
return link;
285
};
286
287
288
/**
289
* Initialize a new link using text in anchor, or empty string if there is no
290
* likely url in the anchor.
291
* @param {HTMLAnchorElement} anchor The anchor element with likely url content.
292
* @param {string=} opt_target The target.
293
* @return {!goog.editor.Link} The link.
294
*/
295
goog.editor.Link.createNewLinkFromText = function(anchor, opt_target) {
296
var link = new goog.editor.Link(anchor, true);
297
var text = link.getValidLinkFromText();
298
link.initializeUrl(text ? text : '');
299
if (opt_target) {
300
anchor.target = opt_target;
301
}
302
return link;
303
};
304
305
306
/**
307
* Returns true if str could be a URL, false otherwise
308
*
309
* Ex: TR_Util.isLikelyUrl_("http://www.google.com") == true
310
* TR_Util.isLikelyUrl_("www.google.com") == true
311
*
312
* @param {string} str String to check if it looks like a URL.
313
* @return {boolean} Whether str could be a URL.
314
*/
315
goog.editor.Link.isLikelyUrl = function(str) {
316
// Whitespace means this isn't a domain.
317
if (/\s/.test(str)) {
318
return false;
319
}
320
321
if (goog.editor.Link.isLikelyEmailAddress(str)) {
322
return false;
323
}
324
325
// Add a scheme if the url doesn't have one - this helps the parser.
326
var addedScheme = false;
327
if (!/^[^:\/?#.]+:/.test(str)) {
328
str = 'http://' + str;
329
addedScheme = true;
330
}
331
332
// Parse the domain.
333
var parts = goog.uri.utils.split(str);
334
335
// Relax the rules for special schemes.
336
var scheme = parts[goog.uri.utils.ComponentIndex.SCHEME];
337
if (goog.array.indexOf(['mailto', 'aim'], scheme) != -1) {
338
return true;
339
}
340
341
// Require domains to contain a '.', unless the domain is fully qualified and
342
// forbids domains from containing invalid characters.
343
var domain = parts[goog.uri.utils.ComponentIndex.DOMAIN];
344
if (!domain || (addedScheme && domain.indexOf('.') == -1) ||
345
(/[^\w\d\-\u0100-\uffff.%]/.test(domain))) {
346
return false;
347
}
348
349
// Require http and ftp paths to start with '/'.
350
var path = parts[goog.uri.utils.ComponentIndex.PATH];
351
return !path || path.indexOf('/') == 0;
352
};
353
354
355
/**
356
* Regular expression that matches strings that could be an email address.
357
* @type {RegExp}
358
* @private
359
*/
360
goog.editor.Link.LIKELY_EMAIL_ADDRESS_ = new RegExp(
361
'^' + // Test from start of string
362
'[\\w-]+(\\.[\\w-]+)*' + // Dot-delimited alphanumerics and dashes
363
// (name)
364
'\\@' + // @
365
'([\\w-]+\\.)+' + // Alphanumerics, dashes and dots (domain)
366
'(\\d+|\\w\\w+)$', // Domain ends in at least one number or 2 letters
367
'i');
368
369
370
/**
371
* Returns true if str could be an email address, false otherwise
372
*
373
* Ex: goog.editor.Link.isLikelyEmailAddress_("some word") == false
374
* goog.editor.Link.isLikelyEmailAddress_("[email protected]") == true
375
*
376
* @param {string} str String to test for being email address.
377
* @return {boolean} Whether "str" looks like an email address.
378
*/
379
goog.editor.Link.isLikelyEmailAddress = function(str) {
380
return goog.editor.Link.LIKELY_EMAIL_ADDRESS_.test(str);
381
};
382
383
384
/**
385
* Determines whether or not a url is an email link.
386
* @param {string} url A url.
387
* @return {boolean} Whether the url is a mailto link.
388
*/
389
goog.editor.Link.isMailto = function(url) {
390
return !!url && goog.string.startsWith(url, 'mailto:');
391
};
392
393