1 | /* |
---|
2 | * WYMeditor : what you see is What You Mean web-based editor |
---|
3 | * Copyright (C) 2007 H.O.net - http://www.honet.be/ |
---|
4 | * Dual licensed under the MIT (MIT-license.txt) |
---|
5 | * and GPL (GPL-license.txt) licenses. |
---|
6 | * |
---|
7 | * For further information visit: |
---|
8 | * http://www.wymeditor.org/ |
---|
9 | * |
---|
10 | * File Name: |
---|
11 | * jquery.wymeditor.js |
---|
12 | * Main JS file with core class and functions. |
---|
13 | * See the documentation for more info. |
---|
14 | * |
---|
15 | * File Authors: |
---|
16 | * Jean-Francois Hovinne (jf.hovinne@wymeditor.org) |
---|
17 | */ |
---|
18 | |
---|
19 | var $j = jQuery; |
---|
20 | |
---|
21 | var aWYM_INSTANCES = new Array(); |
---|
22 | var sWYM_NAME = "name"; |
---|
23 | var sWYM_INDEX = "{Wym_Index}"; |
---|
24 | var sWYM_TOOLS = "{wym_tools}"; |
---|
25 | var sWYM_CLASSES = "{Wym_Classes}"; |
---|
26 | var sWYM_CONTAINERS = "{Wym_Containers}"; |
---|
27 | var sWYM_HTML = "{Wym_Html}"; |
---|
28 | var sWYM_IFRAME = "{Wym_Iframe}"; |
---|
29 | var sWYM_STATUS = "{Wym_Status}"; |
---|
30 | var sWYM_BODY = "body"; |
---|
31 | var sWYM_STRING = "string"; |
---|
32 | var sWYM_P = "p"; |
---|
33 | var sWYM_H1 = "h1"; |
---|
34 | var sWYM_H2 = "h2"; |
---|
35 | var sWYM_H3 = "h3"; |
---|
36 | var sWYM_H4 = "h4"; |
---|
37 | var sWYM_H5 = "h5"; |
---|
38 | var sWYM_H6 = "h6"; |
---|
39 | var sWYM_PRE = "pre"; |
---|
40 | var sWYM_BLOCKQUOTE = "blockquote"; |
---|
41 | var sWYM_TD = "td"; |
---|
42 | var sWYM_TH = "th"; |
---|
43 | var sWYM_LINK = "link"; |
---|
44 | var sWYM_IMAGE = "image"; |
---|
45 | var sWYM_TABLE = "table"; |
---|
46 | var sWYM_CREATE_LINK = "CreateLink"; |
---|
47 | var sWYM_INSERT_IMAGE = "InsertImage"; |
---|
48 | var sWYM_INSERT_TABLE = "InsertTable"; |
---|
49 | var sWYM_TOGGLE_HTML = "ToggleHtml"; |
---|
50 | |
---|
51 | |
---|
52 | var aWYM_CONTAINERS = new Array(sWYM_P,sWYM_H1,sWYM_H2,sWYM_H3,sWYM_H4, |
---|
53 | sWYM_H5,sWYM_H6,sWYM_PRE,sWYM_BLOCKQUOTE); |
---|
54 | |
---|
55 | var aWYM_KEY = { |
---|
56 | BACKSPACE: 8, |
---|
57 | ENTER: 13, |
---|
58 | END: 35, |
---|
59 | HOME: 36, |
---|
60 | LEFT: 37, |
---|
61 | UP: 38, |
---|
62 | RIGHT: 39, |
---|
63 | DOWN: 40, |
---|
64 | CURSOR: new Array(37, 38, 39, 40), |
---|
65 | DELETE: 46 |
---|
66 | }; |
---|
67 | |
---|
68 | var aWYM_NODE = { |
---|
69 | ELEMENT: 1, |
---|
70 | ATTRIBUTE: 2, |
---|
71 | TEXT: 3 |
---|
72 | }; |
---|
73 | |
---|
74 | |
---|
75 | /** |
---|
76 | * Replace an HTML element by WYMeditor |
---|
77 | * |
---|
78 | * @example $(".wymeditor").wymeditor( |
---|
79 | * { |
---|
80 | * |
---|
81 | * } |
---|
82 | * ); |
---|
83 | * @desc Example description here |
---|
84 | * |
---|
85 | * @name WYMeditor |
---|
86 | * @description WYMeditor is a web-based WYSIWYM XHTML editor |
---|
87 | * @param Hash hash A hash of parameters |
---|
88 | * @option Integer iExample Description here |
---|
89 | * @option String sExample Description here |
---|
90 | * |
---|
91 | * @type jQuery |
---|
92 | * @cat Plugins/WYMeditor |
---|
93 | * @author Jean-Francois Hovinne |
---|
94 | */ |
---|
95 | $j.fn.wymeditor = function(options, callback) { |
---|
96 | |
---|
97 | options = $j.extend({ |
---|
98 | |
---|
99 | sHtml: "", |
---|
100 | |
---|
101 | sBoxHtml: "<div class='wym_box'>" |
---|
102 | + "<div class='wym_area_top'>" + sWYM_TOOLS + "</div>" |
---|
103 | + "<div class='wym_area_left'></div>" |
---|
104 | + "<div class='wym_area_right'>" + sWYM_CONTAINERS + sWYM_CLASSES + "</div>" |
---|
105 | + "<div class='wym_area_main'>" + sWYM_HTML + sWYM_IFRAME + sWYM_STATUS + "</div>" |
---|
106 | + "<div class='wym_area_bottom'>" + "</div>" |
---|
107 | + "</div>", |
---|
108 | |
---|
109 | sIframeHtml: "<div class='wym_iframe wym_section'>" |
---|
110 | + "<iframe " |
---|
111 | + "src='wymeditor/wymiframe.html' " |
---|
112 | + "onload='window.parent.aWYM_INSTANCES[" + sWYM_INDEX + "].initIframe(this)' " |
---|
113 | + "></iframe>" |
---|
114 | + "</div>", |
---|
115 | |
---|
116 | sToolsHtml: "<div class='wym_tools wym_section'>" |
---|
117 | + "<h2>Tools</h2>" |
---|
118 | + "<ul>" |
---|
119 | + "<li class='wym_tools_strong'><a href='#' name='Bold'>{Strong}</a></li>" |
---|
120 | + "<li class='wym_tools_emphasis'><a href='#' name='Italic'>{Emphasis}</a></li>" |
---|
121 | + "<li class='wym_tools_superscript'><a href='#' name='Superscript'>{Superscript}</a></li>" |
---|
122 | + "<li class='wym_tools_subscript'><a href='#' name='Subscript'>{Subscript}</a></li>" |
---|
123 | + "<li class='wym_tools_ordered_list'><a href='#' name='InsertOrderedList'>{Ordered_List}</a></li>" |
---|
124 | + "<li class='wym_tools_unordered_list'><a href='#' name='InsertUnorderedList'>{Unordered_List}</a></li>" |
---|
125 | + "<li class='wym_tools_indent'><a href='#' name='Indent'>{Indent}</a></li>" |
---|
126 | + "<li class='wym_tools_outdent'><a href='#' name='Outdent'>{Outdent}</a></li>" |
---|
127 | + "<li class='wym_tools_undo'><a href='#' name='Undo'>{Undo}</a></li>" |
---|
128 | + "<li class='wym_tools_redo'><a href='#' name='Redo'>{Redo}</a></li>" |
---|
129 | + "<li class='wym_tools_link'><a href='#' name='CreateLink'>{Link}</a></li>" |
---|
130 | + "<li class='wym_tools_unlink'><a href='#' name='Unlink'>{Unlink}</a></li>" |
---|
131 | + "<li class='wym_tools_image'><a href='#' name='InsertImage'>{Image}</a></li>" |
---|
132 | + "<li class='wym_tools_table'><a href='#' name='InsertTable'>{Table}</a></li>" |
---|
133 | + "<li class='wym_tools_html'><a href='#' name='ToggleHtml'>{HTML}</a></li>" |
---|
134 | + "</ul>" |
---|
135 | + "</div>", |
---|
136 | |
---|
137 | sContainersHtml: "<div class='wym_containers wym_section'>" |
---|
138 | + "<h2>Containers</h2>" |
---|
139 | + "<ul>" |
---|
140 | + "<li class='wym_containers_p'><a href='#' name='P'>Paragraph</a></li>" |
---|
141 | + "<li class='wym_containers_h1'><a href='#' name='H1'>Heading 1</a></li>" |
---|
142 | + "<li class='wym_containers_h2'><a href='#' name='H2'>Heading 2</a></li>" |
---|
143 | + "<li class='wym_containers_h3'><a href='#' name='H3'>Heading 3</a></li>" |
---|
144 | + "<li class='wym_containers_h4'><a href='#' name='H4'>Heading 4</a></li>" |
---|
145 | + "<li class='wym_containers_h5'><a href='#' name='H5'>Heading 5</a></li>" |
---|
146 | + "<li class='wym_containers_h6'><a href='#' name='H6'>Heading 6</a></li>" |
---|
147 | + "<li class='wym_containers_pre'><a href='#' name='PRE'>Preformatted</a></li>" |
---|
148 | + "<li class='wym_containers_blockquote'><a href='#' name='BLOCKQUOTE'>Blockquote</a></li>" |
---|
149 | + "<li class='wym_containers_th'><a href='#' name='TH'>Table Header</a></li>" |
---|
150 | + "</ul>" |
---|
151 | + "</div>", |
---|
152 | |
---|
153 | sClassesHtml: "<div class='wym_classes wym_section'>" |
---|
154 | + "<h2>Classes</h2>" |
---|
155 | + "</div>", |
---|
156 | |
---|
157 | sStatusHtml: "<div class='wym_status wym_section'>" |
---|
158 | + "<h2>Status</h2>" |
---|
159 | + "</div>", |
---|
160 | |
---|
161 | sHtmlHtml: "<div class='wym_html wym_section'>" |
---|
162 | + "<h2>Source code</h2>" |
---|
163 | + "<textarea class='wym_html_val'></textarea>" |
---|
164 | + "</div>", |
---|
165 | |
---|
166 | sBoxSelector: ".wym_box", |
---|
167 | sToolsSelector: ".wym_tools", |
---|
168 | sClassesSelector: ".wym_classes", |
---|
169 | sContainersSelector:".wym_containers", |
---|
170 | sHtmlSelector: ".wym_html", |
---|
171 | sIframeSelector: ".wym_iframe iframe", |
---|
172 | sStatusSelector: ".wym_status", |
---|
173 | sToolsSelector: ".wym_tools a", |
---|
174 | sContainerSelector: ".wym_containers a", |
---|
175 | sHtmlValSelector: ".wym_html_val", |
---|
176 | |
---|
177 | sStringDelimiterLeft: "{", |
---|
178 | sStringDelimiterRight:"}" |
---|
179 | |
---|
180 | }, options); |
---|
181 | |
---|
182 | return this.each(function(i) { |
---|
183 | |
---|
184 | new Wymeditor($j(this),i,options,callback); |
---|
185 | }); |
---|
186 | }; |
---|
187 | |
---|
188 | /* @name extend |
---|
189 | * @description Returns the WYMeditor instance based on its index |
---|
190 | */ |
---|
191 | $j.extend({ |
---|
192 | wymeditors: function(i) { |
---|
193 | return (aWYM_INSTANCES[i]); |
---|
194 | }, |
---|
195 | wymstrings: function(sKey) { |
---|
196 | return (aWYM_STRINGS[sKey]); |
---|
197 | } |
---|
198 | }); |
---|
199 | |
---|
200 | /* @name Wymeditor |
---|
201 | * @description WYMeditor class |
---|
202 | */ |
---|
203 | function Wymeditor(elem,index,options,callback) { |
---|
204 | |
---|
205 | aWYM_INSTANCES[index] = this; |
---|
206 | |
---|
207 | this._element = elem; |
---|
208 | this._index = index; |
---|
209 | this._options = options; |
---|
210 | this._html = $j(elem).val(); |
---|
211 | this._callback = callback; |
---|
212 | |
---|
213 | if(this._options.sHtml) this._html = this._options.sHtml; |
---|
214 | |
---|
215 | this.selection = new WymSelection(); |
---|
216 | |
---|
217 | this.init(); |
---|
218 | |
---|
219 | }; |
---|
220 | |
---|
221 | /* @name init |
---|
222 | * @description Initializes a WYMeditor instance |
---|
223 | */ |
---|
224 | Wymeditor.prototype.init = function() { |
---|
225 | |
---|
226 | //copy the instance |
---|
227 | var wym = this; |
---|
228 | |
---|
229 | //load subclass - browser specific |
---|
230 | if ($j.browser.msie) { |
---|
231 | var WymClass = new WymClassExplorer(this); |
---|
232 | var WymSel = new WymSelExplorer(this); |
---|
233 | } |
---|
234 | else if ($j.browser.mozilla) { |
---|
235 | var WymClass = new WymClassMozilla(this); |
---|
236 | var WymSel = new WymSelMozilla(this); |
---|
237 | } |
---|
238 | else if ($j.browser.opera) { |
---|
239 | var WymClass = new WymClassOpera(this); |
---|
240 | var WymSel = new WymSelOpera(this); |
---|
241 | } |
---|
242 | else if ($j.browser.safari) { |
---|
243 | var WymClass = new WymClassSafari(this); |
---|
244 | } |
---|
245 | else { |
---|
246 | //TODO: handle unsupported browsers |
---|
247 | } |
---|
248 | |
---|
249 | //copy the subclass methods |
---|
250 | /* |
---|
251 | for (prop in WymClass) { |
---|
252 | this[prop] = WymClass[prop]; |
---|
253 | } |
---|
254 | |
---|
255 | for (prop in WymSel) { |
---|
256 | this.selection[prop] = WymSel[prop]; |
---|
257 | } |
---|
258 | */ |
---|
259 | $j.extend(this, WymClass); |
---|
260 | $j.extend(this.selection, WymSel); |
---|
261 | |
---|
262 | |
---|
263 | //load wymbox |
---|
264 | this._box = $j(this._element).hide().after(this._options.sBoxHtml).next(); |
---|
265 | |
---|
266 | //construct the iframe |
---|
267 | var sIframeHtml = this._options.sIframeHtml; |
---|
268 | sIframeHtml = sIframeHtml.replace(sWYM_INDEX,this._index); |
---|
269 | |
---|
270 | //construct wymbox |
---|
271 | var sBoxHtml = $j(this._box).html(); |
---|
272 | |
---|
273 | sBoxHtml = sBoxHtml.replace(sWYM_TOOLS, this._options.sToolsHtml); |
---|
274 | sBoxHtml = sBoxHtml.replace(sWYM_CONTAINERS, this._options.sContainersHtml); |
---|
275 | sBoxHtml = sBoxHtml.replace(sWYM_CLASSES, this._options.sClassesHtml); |
---|
276 | sBoxHtml = sBoxHtml.replace(sWYM_HTML, this._options.sHtmlHtml); |
---|
277 | sBoxHtml = sBoxHtml.replace(sWYM_IFRAME, sIframeHtml); |
---|
278 | sBoxHtml = sBoxHtml.replace(sWYM_STATUS, this._options.sStatusHtml); |
---|
279 | |
---|
280 | //l10n |
---|
281 | sBoxHtml = this.replaceStrings(sBoxHtml); |
---|
282 | |
---|
283 | //load html in wymbox |
---|
284 | $j(this._box).html(sBoxHtml); |
---|
285 | |
---|
286 | //hide the html value |
---|
287 | $j(this._box).find(this._options.sHtmlSelector).hide(); |
---|
288 | |
---|
289 | //handle click event on tools buttons |
---|
290 | $j(this._box).find(this._options.sToolsSelector).click(function() { |
---|
291 | wym.exec($(this).attr(sWYM_NAME)); |
---|
292 | return(false); |
---|
293 | }); |
---|
294 | |
---|
295 | //handle click event on containers buttons |
---|
296 | $j(this._box).find(this._options.sContainerSelector).click(function() { |
---|
297 | wym.container($(this).attr(sWYM_NAME)); |
---|
298 | return(false); |
---|
299 | }); |
---|
300 | |
---|
301 | //handle keyup event on html value: set the editor value |
---|
302 | $j(this._box).find(this._options.sHtmlValSelector).keyup(function() { |
---|
303 | $j(wym._doc.body).html($j(this).val()); |
---|
304 | }); |
---|
305 | }; |
---|
306 | |
---|
307 | Wymeditor.prototype.ready = function() { |
---|
308 | return(this._doc != null); |
---|
309 | }; |
---|
310 | |
---|
311 | |
---|
312 | /********** BASE METHODS **********/ |
---|
313 | Wymeditor.prototype.handleEvents = function() { |
---|
314 | // var _wym = this; |
---|
315 | $j(this._doc).bind('keydown', {wym: this._wym}, this.handleKeydown); |
---|
316 | $j(this._doc).bind('keypress', {wym: this._wym}, this.handleKeypress); |
---|
317 | $j(this._doc).bind('keyup', {wym: this._wym}, this.handleKeyup); |
---|
318 | // NOTE v.mische If you've multiple instances of WYMeditor, mousedown will |
---|
319 | // be triggered in the instance you were and not in the one you will be |
---|
320 | $j(this._doc).bind('mousedown', {wym: this._wym}, this.handleMousedown); |
---|
321 | $j(this._doc).bind('mouseup', {wym: this._wym}, this.handleMouseup); |
---|
322 | }; |
---|
323 | |
---|
324 | |
---|
325 | |
---|
326 | Wymeditor.prototype.handleKeydown = function(evt) { |
---|
327 | // alert("keydown:"+evt.data._iframe.contentWindow);//._iframe.contentWindow |
---|
328 | // var sBlockElements = sWYM_P+","+sWYM_H1+","+sWYM_H2+","+sWYM_H3+","+sWYM_H4 |
---|
329 | // +","+sWYM_H5+","+sWYM_H6+","+sWYM_PRE+","+sWYM_BLOCKQUOTE; |
---|
330 | |
---|
331 | var sContainers = aWYM_CONTAINERS.join(","); |
---|
332 | |
---|
333 | var _wym = evt.data.wym; |
---|
334 | var _sel = _wym.selection.getSelection(); |
---|
335 | |
---|
336 | /* |
---|
337 | // some small tests |
---|
338 | if (_sel.isAtStart(sContainers)) |
---|
339 | alert("isAtStart: "+_sel.startNode.parentNode.nodeName); |
---|
340 | if (_sel.isAtEnd(sContainers)) |
---|
341 | alert("isAtEnd: "+_sel.endNode.parentNode.nodeName); |
---|
342 | if (evt.keyCode==aWYM_KEY.DELETE) |
---|
343 | if(_sel.deleteIfExpanded()) |
---|
344 | return false; |
---|
345 | if (evt.keyCode==aWYM_KEY.HOME) |
---|
346 | { |
---|
347 | //_sel.cursorToStart(_sel.startNode); |
---|
348 | _sel.cursorToStart(_sel.container); |
---|
349 | return false; |
---|
350 | } |
---|
351 | if (evt.keyCode==aWYM_KEY.END) |
---|
352 | { |
---|
353 | //_sel.cursorToEnd(_sel.endNode); |
---|
354 | _sel.cursorToEnd(_sel.container); |
---|
355 | return false; |
---|
356 | } |
---|
357 | */ |
---|
358 | }; |
---|
359 | Wymeditor.prototype.handleKeypress = function(evt) { |
---|
360 | }; |
---|
361 | Wymeditor.prototype.handleKeyup = function(evt) { |
---|
362 | }; |
---|
363 | Wymeditor.prototype.handleMousedown = function(evt) { |
---|
364 | //alert("mouseDown:"+evt.data.wym._index); |
---|
365 | }; |
---|
366 | Wymeditor.prototype.handleMouseup = function(evt) { |
---|
367 | //alert("mouseUp:"+evt.data.wym._index); |
---|
368 | }; |
---|
369 | |
---|
370 | |
---|
371 | /* @name doc |
---|
372 | * @description Get the edited document |
---|
373 | */ |
---|
374 | Wymeditor.prototype.doc = function() { |
---|
375 | return(this._doc); |
---|
376 | } |
---|
377 | |
---|
378 | /* @name box |
---|
379 | * @description Get the edited document |
---|
380 | */ |
---|
381 | Wymeditor.prototype.box = function() { |
---|
382 | return(this._box); |
---|
383 | } |
---|
384 | |
---|
385 | /* @name html |
---|
386 | * @description Get/Set the html value |
---|
387 | */ |
---|
388 | Wymeditor.prototype.html = function(sHtml) { |
---|
389 | |
---|
390 | if(sHtml) $j(this._doc.body).html(sHtml); |
---|
391 | else return($j(this._doc.body).html()); |
---|
392 | }; |
---|
393 | |
---|
394 | /* @name exec |
---|
395 | * @description Executes a button command |
---|
396 | */ |
---|
397 | Wymeditor.prototype.exec = function(cmd) { |
---|
398 | |
---|
399 | //base function for execCommand |
---|
400 | //open a dialog or exec |
---|
401 | switch(cmd) { |
---|
402 | case sWYM_CREATE_LINK: |
---|
403 | var container = this.container(); |
---|
404 | if(container) this.dialog(sWYM_LINK); |
---|
405 | break; |
---|
406 | |
---|
407 | case sWYM_INSERT_IMAGE: |
---|
408 | this.dialog(sWYM_IMAGE); |
---|
409 | break; |
---|
410 | |
---|
411 | case sWYM_INSERT_TABLE: |
---|
412 | this.dialog(sWYM_TABLE); |
---|
413 | break; |
---|
414 | |
---|
415 | case sWYM_TOGGLE_HTML: |
---|
416 | this.update(); |
---|
417 | this.toggleHtml(); |
---|
418 | break; |
---|
419 | |
---|
420 | default: |
---|
421 | this._exec(cmd); |
---|
422 | break; |
---|
423 | } |
---|
424 | }; |
---|
425 | |
---|
426 | /* @name container |
---|
427 | * @description Get/Set the selected container |
---|
428 | */ |
---|
429 | Wymeditor.prototype.container = function(sType) { |
---|
430 | |
---|
431 | if(sType) { |
---|
432 | |
---|
433 | var container = null; |
---|
434 | |
---|
435 | if(sType.toLowerCase() == sWYM_TH) { |
---|
436 | |
---|
437 | container = this.container(); |
---|
438 | |
---|
439 | //find the TD or TH container |
---|
440 | switch(container.tagName.toLowerCase()) { |
---|
441 | |
---|
442 | case sWYM_TD: case sWYM_TH: |
---|
443 | break; |
---|
444 | default: |
---|
445 | var aTypes = new Array(sWYM_TD,sWYM_TH); |
---|
446 | container = this.findUp(aTypes); |
---|
447 | break; |
---|
448 | } |
---|
449 | |
---|
450 | //if it exists, switch |
---|
451 | if(container!=null) { |
---|
452 | |
---|
453 | sType = (container.tagName.toLowerCase() == sWYM_TD)? sWYM_TH: sWYM_TD; |
---|
454 | this.switchTo(container,sType); |
---|
455 | this.update(); |
---|
456 | } |
---|
457 | } else { |
---|
458 | |
---|
459 | //set the container type |
---|
460 | var aTypes=new Array(sWYM_P,sWYM_H1,sWYM_H2,sWYM_H3,sWYM_H4,sWYM_H5,sWYM_H6,sWYM_PRE,sWYM_BLOCKQUOTE); |
---|
461 | container = this.findUp(aTypes); |
---|
462 | |
---|
463 | if(container) { |
---|
464 | |
---|
465 | var newNode = null; |
---|
466 | |
---|
467 | //blockquotes must contain a block level element |
---|
468 | if(sType.toLowerCase() == sWYM_BLOCKQUOTE) { |
---|
469 | |
---|
470 | var blockquote = this.findUp(sWYM_BLOCKQUOTE); |
---|
471 | |
---|
472 | if(blockquote == null) { |
---|
473 | |
---|
474 | newNode = this._doc.createElement(sType); |
---|
475 | container.parentNode.insertBefore(newNode,container); |
---|
476 | newNode.appendChild(container); |
---|
477 | |
---|
478 | } else { |
---|
479 | |
---|
480 | var nodes = blockquote.childNodes; |
---|
481 | var lgt = nodes.length; |
---|
482 | for(var x=0; x<lgt; x++) { |
---|
483 | blockquote.parentNode.insertBefore(nodes.item(0),blockquote); |
---|
484 | } |
---|
485 | blockquote.parentNode.removeChild(blockquote); |
---|
486 | } |
---|
487 | } |
---|
488 | |
---|
489 | else this.switchTo(container,sType); |
---|
490 | |
---|
491 | this.update(); |
---|
492 | } |
---|
493 | } |
---|
494 | } |
---|
495 | else return(this.selected()); |
---|
496 | }; |
---|
497 | |
---|
498 | /* @name finUp |
---|
499 | * @description Returns the first parent or self container, based on its type |
---|
500 | */ |
---|
501 | Wymeditor.prototype.findUp = function(mFilter) { |
---|
502 | |
---|
503 | //mFilter is a string or an array of strings |
---|
504 | |
---|
505 | var container = this.container(); |
---|
506 | var tagname = container.tagName.toLowerCase(); |
---|
507 | |
---|
508 | if(typeof(mFilter) == sWYM_STRING) { |
---|
509 | |
---|
510 | while(tagname != mFilter && tagname != sWYM_BODY) { |
---|
511 | |
---|
512 | container = container.parentNode; |
---|
513 | tagname = container.tagName.toLowerCase(); |
---|
514 | } |
---|
515 | |
---|
516 | } else { |
---|
517 | |
---|
518 | var bFound = false; |
---|
519 | |
---|
520 | while(!bFound && tagname != sWYM_BODY) { |
---|
521 | for(var i = 0; i<mFilter.length; i++) { |
---|
522 | if(tagname == mFilter[i]) { |
---|
523 | bFound = true; |
---|
524 | break; |
---|
525 | } |
---|
526 | } |
---|
527 | if(!bFound) { |
---|
528 | container = container.parentNode; |
---|
529 | tagname = container.tagName.toLowerCase(); |
---|
530 | } |
---|
531 | } |
---|
532 | } |
---|
533 | |
---|
534 | if(tagname != sWYM_BODY) return(container); |
---|
535 | else return(null); |
---|
536 | }; |
---|
537 | |
---|
538 | /* @name switchTo |
---|
539 | * @description Switch the node's type |
---|
540 | */ |
---|
541 | Wymeditor.prototype.switchTo = function(node,sType) { |
---|
542 | |
---|
543 | var newNode = this._doc.createElement(sType); |
---|
544 | var html = $j(node).html(); |
---|
545 | node.parentNode.replaceChild(newNode,node); |
---|
546 | $j(newNode).html(html); |
---|
547 | }; |
---|
548 | |
---|
549 | /********** UI RELATED **********/ |
---|
550 | |
---|
551 | Wymeditor.prototype.replaceStrings = function(sVal) { |
---|
552 | |
---|
553 | for (var key in aWYM_STRINGS) { |
---|
554 | sVal = sVal.replace(this._options.sStringDelimiterLeft + key + this._options.sStringDelimiterRight, aWYM_STRINGS[key]); |
---|
555 | } |
---|
556 | return(sVal); |
---|
557 | }; |
---|
558 | |
---|
559 | /* @name status |
---|
560 | * @description Prints a status message |
---|
561 | */ |
---|
562 | Wymeditor.prototype.status = function(sMessage) { |
---|
563 | |
---|
564 | //print status message |
---|
565 | $j(this._box).find(this._options.sStatusSelector).html(sMessage); |
---|
566 | }; |
---|
567 | |
---|
568 | /* @name update |
---|
569 | * @description Updates the element and textarea values |
---|
570 | */ |
---|
571 | Wymeditor.prototype.update = function() { |
---|
572 | |
---|
573 | var html = this.xhtml(); |
---|
574 | $j(this._element).val(html); |
---|
575 | $j(this._box).find(this._options.sHtmlValSelector).val(html); |
---|
576 | }; |
---|
577 | |
---|
578 | /* @name dialog |
---|
579 | * @description Opens a dialog box |
---|
580 | */ |
---|
581 | Wymeditor.prototype.dialog = function(sType) { |
---|
582 | |
---|
583 | console.info(this.selection); |
---|
584 | console.info(this.selection.test); |
---|
585 | console.info(this.selection.isAtStart()); |
---|
586 | console.info(this.selection.myTest()); |
---|
587 | }; |
---|
588 | |
---|
589 | /* @name toggleHtml |
---|
590 | * @description Shows/Hides the HTML |
---|
591 | */ |
---|
592 | Wymeditor.prototype.toggleHtml = function() { |
---|
593 | |
---|
594 | $j(this._box).find(this._options.sHtmlSelector).toggle(); |
---|
595 | }; |
---|
596 | |
---|
597 | /********** SELECTION API **********/ |
---|
598 | |
---|
599 | function WymSelection() { |
---|
600 | this.test = "test from WymSelection"; |
---|
601 | }; |
---|
602 | |
---|
603 | |
---|
604 | WymSelection.prototype = { |
---|
605 | /* The following properties where set in the browser specific file (in |
---|
606 | * getSelection()): |
---|
607 | * this.original |
---|
608 | * this.startNode |
---|
609 | * this.endNode |
---|
610 | * this.startOffset |
---|
611 | * this.endOffset |
---|
612 | * this.collapsed |
---|
613 | * this.container |
---|
614 | */ |
---|
615 | |
---|
616 | /* The following methods are implemented in browser specific file: |
---|
617 | * - deleteIfExpanded() |
---|
618 | * - cursorToStart() |
---|
619 | * - cursorToEnd() |
---|
620 | */ |
---|
621 | |
---|
622 | |
---|
623 | isAtStart: function(jqexpr) |
---|
624 | { |
---|
625 | var parent = $j(this.startNode).parentsOrSelf(jqexpr); |
---|
626 | |
---|
627 | // jqexpr isn't a parent of the current cursor position |
---|
628 | if (parent.length==0) |
---|
629 | return false; |
---|
630 | else |
---|
631 | parent = parent[0]; |
---|
632 | |
---|
633 | |
---|
634 | for (var n=this.startNode; n!=parent; n=n.parentNode) |
---|
635 | { |
---|
636 | //if (n.nodeType == nodeType.TEXT) |
---|
637 | if (n.nodeType == aWYM_NODE.TEXT) |
---|
638 | { |
---|
639 | if (this.startOffset != 0) |
---|
640 | return false; |
---|
641 | } |
---|
642 | var firstChild = n.parentNode.firstChild; |
---|
643 | // node isn't first child => cursor can't be at the beginning |
---|
644 | // in gecko there the first child could be a phantom node |
---|
645 | |
---|
646 | // sometimes also whitespacenodes which aren't phatom nodes |
---|
647 | // get stripped, but this is ok, as this is a wysiwym editor |
---|
648 | if ((firstChild != n |
---|
649 | || ($(firstChild).isPhantomNode() |
---|
650 | && firstChild.nextSibling != n))) |
---|
651 | { |
---|
652 | return false; |
---|
653 | } |
---|
654 | } |
---|
655 | |
---|
656 | if (this.startOffset == 0) |
---|
657 | return true; |
---|
658 | else |
---|
659 | return false; |
---|
660 | }, |
---|
661 | |
---|
662 | isAtEnd: function(jqexpr) |
---|
663 | { |
---|
664 | var parent = $(this.endNode).parentsOrSelf(jqexpr); |
---|
665 | |
---|
666 | // jqexpr isn't a parent of the current cursor position |
---|
667 | if (parent.length==0) |
---|
668 | return false; |
---|
669 | else |
---|
670 | parent = parent[0]; |
---|
671 | |
---|
672 | |
---|
673 | // This is the case if, e.g ("|" = cursor): <p>textnode|<br/></p>, |
---|
674 | // there the offset of endNode (endOffset) is 1 (behind the first node |
---|
675 | // of <p>) |
---|
676 | if (this.endNode == parent) |
---|
677 | { |
---|
678 | // NOTE I don't know if it is a good idea to delete the <br> |
---|
679 | // here, as "atEnd()" probably shouldn't change the dom tree, |
---|
680 | // but only searching it |
---|
681 | if (this.endNode.lastChild.nodeName == "BR") |
---|
682 | this.endNode.removeChild(endNode.lastChild); |
---|
683 | |
---|
684 | // if cursor is really at the end |
---|
685 | if (this.endOffset > 0 && |
---|
686 | this.endNode.childNodes[this.endOffset-1].nextSibling==null) |
---|
687 | { |
---|
688 | return true; |
---|
689 | } |
---|
690 | } |
---|
691 | else |
---|
692 | { |
---|
693 | for (var n=this.endNode; n!=parent; n=n.parentNode) |
---|
694 | { |
---|
695 | if (n.nodeType == aWYM_NODE.TEXT) |
---|
696 | { |
---|
697 | if (this.endOffset != this.endNode.data.length) |
---|
698 | return false; |
---|
699 | } |
---|
700 | else |
---|
701 | { |
---|
702 | var lastChild = n.parentNode.lastChild; |
---|
703 | // node isn't last child => cursor can't be at the end |
---|
704 | // (is this true?) in gecko there the last child could be a |
---|
705 | // phantom node |
---|
706 | |
---|
707 | // sometimes also whitespacenodes which aren't phatom nodes |
---|
708 | // get stripped, but this is ok, as this is a wysiwym editor |
---|
709 | if ((lastChild != n) || |
---|
710 | ($(lastChild).isPhantomNode() |
---|
711 | && lastChild.previousSibling != n)) |
---|
712 | { |
---|
713 | return false; |
---|
714 | } |
---|
715 | } |
---|
716 | } |
---|
717 | } |
---|
718 | |
---|
719 | if (this.endOffset == this.endNode.length) |
---|
720 | return true; |
---|
721 | else |
---|
722 | return false; |
---|
723 | } |
---|
724 | }; |
---|
725 | |
---|
726 | WymSelection.prototype.myTest = function() { |
---|
727 | |
---|
728 | return("myTest from WymSelection"); |
---|
729 | }; |
---|
730 | |
---|
731 | |
---|
732 | /********** HELPERS **********/ |
---|
733 | |
---|
734 | // Returns true if it is a text node with whitespaces only |
---|
735 | $.fn.isPhantomNode = function() |
---|
736 | { |
---|
737 | if (this[0].nodeType == 3) |
---|
738 | return !(/[^\t\n\r ]/.test(this[0].data)); |
---|
739 | |
---|
740 | return false; |
---|
741 | } |
---|
742 | function isPhantomNode(n) |
---|
743 | { |
---|
744 | if (n.nodeType == 3) |
---|
745 | return !(/[^\t\n\r ]/.test(n.data)); |
---|
746 | |
---|
747 | return false; |
---|
748 | } |
---|
749 | |
---|
750 | |
---|
751 | |
---|
752 | // Returns the Parents or the node itself |
---|
753 | // jqexpr = a jQuery expression |
---|
754 | $.fn.parentsOrSelf = function(jqexpr) |
---|
755 | { |
---|
756 | var n = this; |
---|
757 | |
---|
758 | if (n[0].nodeType == 3) |
---|
759 | n = n.parents().lt(1); |
---|
760 | |
---|
761 | // if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug) |
---|
762 | if (n.filter(jqexpr).size() == 1) |
---|
763 | return n; |
---|
764 | else |
---|
765 | return n.parents(jqexpr).lt(1); |
---|
766 | } |
---|
767 | |
---|
768 | |
---|
769 | // from http://forum.de.selfhtml.org/archiv/2004/3/t76079/#m438193 (2007-02-06) |
---|
770 | Array.prototype.contains = function (elem) { |
---|
771 | // var i; |
---|
772 | for (var i = 0; i < this.length; i++) { |
---|
773 | if (this[i] === elem) { |
---|
774 | return true; |
---|
775 | } |
---|
776 | } |
---|
777 | return false; |
---|
778 | }; |
---|
779 | |
---|