1 | define([ |
---|
2 | "dojo", |
---|
3 | "dijit", |
---|
4 | "dojox", |
---|
5 | "dijit/_editor/_Plugin", |
---|
6 | "dijit/form/Button", |
---|
7 | "dojo/_base/declare", |
---|
8 | "dojo/string" |
---|
9 | ], function(dojo, dijit, dojox, _Plugin) { |
---|
10 | |
---|
11 | var AutoUrlLink = dojo.declare("dojox.editor.plugins.AutoUrlLink", [_Plugin], { |
---|
12 | // summary: |
---|
13 | // This plugin can recognize a URL like string |
---|
14 | // (such as http://www.website.com) and turn it into |
---|
15 | // a hyperlink that points to that URL. |
---|
16 | |
---|
17 | // _template: [private] String |
---|
18 | // The link template |
---|
19 | _template: "<a _djrealurl='${url}' href='${url}'>${url}</a>", |
---|
20 | |
---|
21 | setEditor: function(/*dijit.Editor*/ editor){ |
---|
22 | // summary: |
---|
23 | // Called by the editor it belongs to. |
---|
24 | // editor: |
---|
25 | // The editor it belongs to. |
---|
26 | this.editor = editor; |
---|
27 | if(!dojo.isIE){ |
---|
28 | // IE will recognize URL as a link automatically |
---|
29 | // No need to re-invent the wheel. |
---|
30 | dojo.some(editor._plugins, function(plugin){ |
---|
31 | // Need to detect which enter key mode it is now |
---|
32 | if(plugin.isInstanceOf(dijit._editor.plugins.EnterKeyHandling)){ |
---|
33 | this.blockNodeForEnter = plugin.blockNodeForEnter; |
---|
34 | return true; |
---|
35 | } |
---|
36 | return false; |
---|
37 | }, this); |
---|
38 | this.connect(editor, "onKeyPress", "_keyPress"); |
---|
39 | this.connect(editor, "onClick", "_recognize"); |
---|
40 | this.connect(editor, "onBlur", "_recognize"); |
---|
41 | } |
---|
42 | }, |
---|
43 | |
---|
44 | _keyPress: function(evt){ |
---|
45 | // summary: |
---|
46 | // Handle the keypress event and dispatch it to the target handler |
---|
47 | // evt: |
---|
48 | // The keypress event object. |
---|
49 | // tags: |
---|
50 | // protected |
---|
51 | var ks = dojo.keys, v = 118, V = 86, |
---|
52 | kc = evt.keyCode, cc = evt.charCode; |
---|
53 | if(cc == ks.SPACE || (evt.ctrlKey && (cc == v || cc == V))){ |
---|
54 | setTimeout(dojo.hitch(this, "_recognize"), 0); |
---|
55 | }else if(kc == ks.ENTER){ |
---|
56 | // Handle the enter event after EnterKeyHandling finishes its job |
---|
57 | setTimeout(dojo.hitch(this, function(){ |
---|
58 | this._recognize({enter: true}); |
---|
59 | }), 0); |
---|
60 | }else{ |
---|
61 | // _saved: The previous dom node when the cursor is at a new dom node. |
---|
62 | // When we click elsewhere, the previous dom node |
---|
63 | // should be examed to see if there is any URL need to be activated |
---|
64 | this._saved = this.editor.window.getSelection().anchorNode; |
---|
65 | } |
---|
66 | }, |
---|
67 | |
---|
68 | _recognize: function(args){ |
---|
69 | // summary: |
---|
70 | // Recognize the URL like strings and turn them into a link |
---|
71 | // tags: |
---|
72 | // private |
---|
73 | var template = this._template, |
---|
74 | isEnter = args ? args.enter : false, |
---|
75 | ed = this.editor, |
---|
76 | selection = ed.window.getSelection(); |
---|
77 | console.log("_recognize: isEnter = ", isEnter, ", selection is ", selection, selection.anchorNode, this._findLastEditingNode(selection.anchorNode)) |
---|
78 | if(selection){ |
---|
79 | var node = isEnter ? this._findLastEditingNode(selection.anchorNode) : |
---|
80 | (this._saved || selection.anchorNode), |
---|
81 | bm = this._saved = selection.anchorNode, |
---|
82 | bmOff = selection.anchorOffset; |
---|
83 | |
---|
84 | if(node.nodeType == 3 && !this._inLink(node)){ |
---|
85 | var linked = false, result = this._findUrls(node, bm, bmOff), |
---|
86 | range = ed.document.createRange(), |
---|
87 | item, cost = 0, isSameNode = (bm == node); |
---|
88 | |
---|
89 | item = result.shift(); |
---|
90 | while(item){ |
---|
91 | // Covert a URL to a link. |
---|
92 | range.setStart(node, item.start); |
---|
93 | range.setEnd(node, item.end); |
---|
94 | selection.removeAllRanges(); |
---|
95 | selection.addRange(range); |
---|
96 | ed.execCommand("insertHTML", dojo.string.substitute(template, {url: range.toString()})); |
---|
97 | cost += item.end; |
---|
98 | item = result.shift(); |
---|
99 | linked = true; |
---|
100 | } |
---|
101 | |
---|
102 | // If bm and node are the some dom node, caculate the actual bookmark offset |
---|
103 | // If the position of the cursor is modified (turned into a link, etc.), no |
---|
104 | // need to recover the cursor position |
---|
105 | if(isSameNode && (bmOff = bmOff - cost) <= 0){ return; } |
---|
106 | |
---|
107 | // We didn't update anything, so don't collapse selections. |
---|
108 | if(!linked) { return ; } |
---|
109 | try{ |
---|
110 | // Try to recover the cursor position |
---|
111 | range.setStart(bm, 0); |
---|
112 | range.setEnd(bm, bmOff); |
---|
113 | selection.removeAllRanges(); |
---|
114 | selection.addRange(range); |
---|
115 | ed._sCall("collapse", []); |
---|
116 | }catch(e){} |
---|
117 | } |
---|
118 | } |
---|
119 | }, |
---|
120 | |
---|
121 | _inLink: function(/*DomNode*/ node){ |
---|
122 | // summary: |
---|
123 | // Check if the node is already embraced within a `<a>...</a>` tag. |
---|
124 | // node: |
---|
125 | // The node to be examined. |
---|
126 | // tags: |
---|
127 | // private |
---|
128 | var editNode = this.editor.editNode, |
---|
129 | result = false, tagName; |
---|
130 | |
---|
131 | node = node.parentNode; |
---|
132 | while(node && node !== editNode){ |
---|
133 | tagName = node.tagName ? node.tagName.toLowerCase() : ""; |
---|
134 | if(tagName == "a"){ |
---|
135 | result = true; |
---|
136 | break; |
---|
137 | } |
---|
138 | node = node.parentNode; |
---|
139 | } |
---|
140 | return result; |
---|
141 | }, |
---|
142 | |
---|
143 | _findLastEditingNode: function(/*DomNode*/ node){ |
---|
144 | // summary: |
---|
145 | // Find the last node that was edited so that we can |
---|
146 | // get the last edited text. |
---|
147 | // node: |
---|
148 | // The current node that the cursor is at. |
---|
149 | // tags: |
---|
150 | // private |
---|
151 | var blockTagNames = dijit.range.BlockTagNames, |
---|
152 | editNode = this.editor.editNode, blockNode; |
---|
153 | |
---|
154 | if(!node){ return node; } |
---|
155 | if(this.blockNodeForEnter == "BR" && |
---|
156 | (!(blockNode = dijit.range.getBlockAncestor(node, null, editNode).blockNode) || |
---|
157 | blockNode.tagName.toUpperCase() != "LI")){ |
---|
158 | while((node = node.previousSibling) && node.nodeType != 3){} |
---|
159 | }else{ |
---|
160 | // EnterKeyHandling is under "DIV" or "P" mode or |
---|
161 | // it's in a LI element. Find the last editing block |
---|
162 | if((blockNode || (blockNode = dijit.range.getBlockAncestor(node, null, editNode).blockNode)) && |
---|
163 | blockNode.tagName.toUpperCase() == "LI"){ |
---|
164 | node = blockNode; |
---|
165 | }else{ |
---|
166 | node = dijit.range.getBlockAncestor(node, null, editNode).blockNode; |
---|
167 | } |
---|
168 | // Find the last editing text node |
---|
169 | while((node = node.previousSibling) && !(node.tagName && node.tagName.match(blockTagNames))){} |
---|
170 | if(node){ |
---|
171 | node = node.lastChild; |
---|
172 | while(node){ |
---|
173 | if(node.nodeType == 3 && dojo.trim(node.nodeValue) != ""){ |
---|
174 | break; |
---|
175 | }else if(node.nodeType == 1){ |
---|
176 | node = node.lastChild; |
---|
177 | }else{ |
---|
178 | node = node.previousSibling; |
---|
179 | } |
---|
180 | } |
---|
181 | } |
---|
182 | } |
---|
183 | return node; |
---|
184 | }, |
---|
185 | |
---|
186 | _findUrls: function(/*DomNode*/ node, /*DomNode*/ bm, /*Number*/ bmOff){ |
---|
187 | // summary: |
---|
188 | // Find the occurrace of the URL strings. |
---|
189 | // FF, Chrome && Safri have a behavior that when insertHTML is executed, |
---|
190 | // the orignal referrence to the text node will be the text node next to |
---|
191 | // the inserted anchor automatically. So we have to re-caculate the index of |
---|
192 | // the following URL occurrence. |
---|
193 | // value: |
---|
194 | // A text to be scanned. |
---|
195 | // tags: |
---|
196 | // private |
---|
197 | var pattern = /(http|https|ftp):\/\/[^\s]+/ig, |
---|
198 | list = [], baseIndex = 0, |
---|
199 | value = node.nodeValue, result, ch; |
---|
200 | |
---|
201 | if(node === bm && bmOff < value.length){ |
---|
202 | // Break the text so that it may not grab extra words. |
---|
203 | // Such as if you type: |
---|
204 | // foo http://foo.com|bar (And | is where you press enter). |
---|
205 | // It will grab the bar word as part of the link. That's annoying/bad. |
---|
206 | // Also it prevents recognizing the text after the cursor. |
---|
207 | value = value.substr(0, bmOff); |
---|
208 | } |
---|
209 | |
---|
210 | while((result = pattern.exec(value)) != null){ |
---|
211 | if(result.index == 0 || (ch = value.charAt(result.index - 1)) == " " || ch == "\xA0"){ |
---|
212 | list.push({start: result.index - baseIndex, end: result.index + result[0].length - baseIndex}); |
---|
213 | baseIndex = result.index + result[0].length; |
---|
214 | } |
---|
215 | } |
---|
216 | |
---|
217 | return list; |
---|
218 | } |
---|
219 | }); |
---|
220 | |
---|
221 | // Register this plugin. |
---|
222 | dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){ |
---|
223 | if(o.plugin){ return; } |
---|
224 | var name = o.args.name.toLowerCase(); |
---|
225 | if(name === "autourllink"){ |
---|
226 | o.plugin = new AutoUrlLink(); |
---|
227 | } |
---|
228 | }); |
---|
229 | |
---|
230 | return AutoUrlLink; |
---|
231 | |
---|
232 | }); |
---|