Fixed time zone calculation issue
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / 3rdParty / ckeditor / plugins / wbbcode / plugin.js
CommitLineData
c088b28c
AE
1/**
2 * BBCode Plugin for CKEditor
3 *
4 * @author Marcel Werk
ca4ba303 5 * @copyright 2001-2014 WoltLab GmbH
c088b28c 6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
d45eaff6 7 */
d45eaff6
MW
8(function() {
9 var $pasted = false;
10 var $insertedText = null;
11
12 CKEDITOR.on('instanceReady', function(event) {
13 /**
14 * Fixes issues with pasted html.
15 */
16 event.editor.on('paste', function(ev) {
17 if (ev.data.type == 'html') {
18 var $value = ev.data.dataValue;
19
20 // Convert <br> to line breaks.
21 $value = $value.replace(/<br><\/p>/gi,"\n\n");
22 $value = $value.replace(/<br>/gi, "\n");
23 $value = $value.replace(/<\/p>/gi,"\n\n");
24 $value = $value.replace(/&nbsp;/gi," ");
ceea0870
AE
25
26 // convert div-separated content into new lines
8b7c6738
AE
27 $value = $value.replace(/<div([^>])>/gi, '');
28 $value = $value.replace(/<\/div>/gi, "\n");
d45eaff6 29
ceea0870
AE
30 // convert lists into new lines
31 $value = $value.replace(/<\/li>/gi, "\n");
d45eaff6
MW
32 // remove html tags
33 $value = $value.replace(/<[^>]+>/g, '');
8b7c6738 34
d45eaff6
MW
35 // fix multiple new lines
36 $value = $value.replace(/\n{3,}/gi,"\n\n");
37
38 ev.data.dataValue = $value;
39
40 $pasted = true;
41 }
42 }, null, null, 9);
43
6414c2f1 44 // prevent drag and drop of images in Firefox
93e30a24
AE
45 if ($.browser.mozilla) {
46 event.editor.document.on('drop', function(ev) {
47 ev.data.preventDefault(true);
48 });
49 }
6414c2f1 50
d45eaff6
MW
51 event.editor.on('insertText', function(ev) {
52 $insertedText = ev.data;
53 }, null, null, 1);
54 event.editor.on('mode', function(ev) {
8cacfb77
AE
55 if ($.browser.mozilla && ev.editor.mode === 'wysiwyg') {
56 fixFirefox();
57 }
58
d45eaff6
MW
59 ev.editor.focus();
60
61 insertFakeSubmitButton(ev);
62 });
63 event.editor.on('afterSetData', function(ev) {
64 insertFakeSubmitButton(ev);
65 });
66
67 event.editor.on('key', function(ev) {
68 if (ev.data.keyCode == CKEDITOR.ALT + 83) { // [Alt] + [S]
69 WCF.Message.Submit.execute(ev.editor.name);
70 }
71 });
72
73 insertFakeSubmitButton(event);
78e9a6ca
MW
74
75 // remove stupid title tag
6e725d5c
MW
76 // @todo: obsolete in ckeditor 4.2
77 $(event.editor.container.$).find('.cke_wysiwyg_div').removeAttr('title');
8cacfb77
AE
78
79 if ($.browser.mozilla) {
80 fixFirefox();
81 }
d45eaff6
MW
82 });
83
84 /**
85 * Inserts a fake submit button, Chrome only.
86 *
87 * @param object event
88 */
89 function insertFakeSubmitButton(event) {
90 if (event.editor.mode === 'source' || !WCF.Browser.isChrome()) {
91 return;
92 }
93
94 // place button outside of <body> to prevent it being removed once deleting content
6414c2f1 95 $('<button accesskey="s" />').hide().appendTo($(event.editor.container.$).find('.cke_wysiwyg_div'));
d45eaff6
MW
96
97 }
98
8cacfb77
AE
99 /**
100 * Disables object resizing and table handles in Firefox.
101 */
102 function fixFirefox() {
103 document.designMode = 'on';
104 document.execCommand('enableObjectResizing', false, false);
105 document.execCommand('enableInlineTableEditing', false, false);
106 document.designMode = 'off';
107 }
108
d45eaff6
MW
109 /**
110 * Removes obsolete dialog elements.
111 */
112 CKEDITOR.on('dialogDefinition', function(event) {
113 var $tab;
114 var $name = event.data.name;
115 var $definition = event.data.definition;
d45eaff6
MW
116 if ($name == 'link') {
117 $definition.removeContents('target');
118 $definition.removeContents('upload');
119 $definition.removeContents('advanced');
120 $tab = $definition.getContents('info');
121 $tab.remove('emailSubject');
122 $tab.remove('emailBody');
123 }
124 else if ($name == 'image') {
125 $definition.removeContents('advanced');
126 $tab = $definition.getContents('Link');
127 $tab.remove('cmbTarget');
128 $tab = $definition.getContents('info');
129 $tab.remove('txtAlt');
130 $tab.remove('basic');
487d6cd8
AE
131
132 // remove preview, do NOT use $tab.remove() because that breaks the plugin
133 $definition.dialog.on('show', function(event) {
134 var $container = $(event.sender._.element.$).find('div[name=info]')
135 $container.find('> table > tbody > tr:eq(1)').hide();
136 $container.parent().css('height', 'auto');
137 });
d45eaff6
MW
138 }
139 else if ($name == 'table') {
140 $definition.removeContents('advanced');
141 $definition.width = 210;
142 $definition.height = 1;
143
144 $tab = $definition.getContents('info');
145
146 $tab.remove('selHeaders');
147 $tab.remove('cmbAlign');
148 $tab.remove('txtHeight');
149 $tab.remove('txtCaption');
150 $tab.remove('txtSummary');
151
152 // don't remove these fields as we need their default values
153 $tab.get('txtBorder').style = 'display: none';
154 $tab.get('txtWidth').style = 'display: none';
155 $tab.get('txtCellSpace').style = 'display: none';
156 $tab.get('txtCellPad').style = 'display: none';
157 }
158 else if ($name == 'smiley') {
159 $definition.contents[0].elements[0].onClick = function(ev) {
160 var $target = ev.data.getTarget();
161 var $targetName = $target.getName();
162
163 if ($targetName == 'a') {
164 $target = $target.getChild( 0 );
165 }
166 else if ($targetName != 'img') {
167 return;
168 }
169
170 var $src = $target.getAttribute('cke_src');
171 var $title = $target.getAttribute('title');
172
173 event.editor.insertText(' ' + $title + ' ');
174
175 $definition.dialog.hide();
176 ev.data.preventDefault();
177 };
178 }
179 });
180
181 /**
182 * Enables this plugin.
183 */
184 CKEDITOR.plugins.add('wbbcode', {
185 requires: ['htmlwriter'],
186 init: function(editor) {
187 editor.dataProcessor = new CKEDITOR.htmlDataProcessor(editor);
188 editor.dataProcessor.toHtml = toHtml;
189 editor.dataProcessor.toDataFormat = toDataFormat;
190 }
191 });
192
193 /**
194 * Removes the unicode zero width space (0x200B).
195 *
196 * @param string string
197 * @return string
198 */
199 var removeCrap = function(string) {
200 var $string = '';
201
202 for (var $i = 0, $length = string.length; $i < $length; $i++) {
203 var $byte = string.charCodeAt($i).toString(16);
204 if ($byte != '200b') {
205 $string += string[$i];
206 }
207 }
208
209 return $string;
210 }
211
212 /**
213 * Converts bbcodes to html.
214 */
215 var toHtml = function(data, fixForBody) {
67ae95eb 216 //if ($.trim(data) === "") return "<p></p>";
adffb9ac 217
d45eaff6
MW
218 // remove 0x200B (unicode zero width space)
219 data = removeCrap(data);
220
221 if ($insertedText !== null) {
222 data = $insertedText;
223 $insertedText = null;
224
225 if (data == ' ') return '&nbsp;';
226 }
227
228 if (!$pasted) {
229 // Convert & to its HTML entity.
230 data = data.replace(/&/g, '&amp;');
231
232 // Convert < and > to their HTML entities.
233 data = data.replace(/</g, '&lt;');
234 data = data.replace(/>/g, '&gt;');
235 }
236
237 // Convert line breaks to <br>.
238 data = data.replace(/(?:\r\n|\n|\r)/g, '<br>');
239
240 if ($pasted) {
241 $pasted = false;
242 // skip
243 return data;
244 }
245
246 // cache code tags
247 var $cachedCodes = { };
248 data = data.replace(/\[code(.+?)\[\/code]/gi, function(match) {
249 var $key = match.hashCode();
89dd0b2c 250 $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
d45eaff6
MW
251 return '@@' + $key + '@@';
252 });
253
254 // [url]
255 data = data.replace(/\[url\]([^"]+?)\[\/url]/gi, '<a href="$1">$1</a>');
13996ba9
MW
256 data = data.replace(/\[url\='([^'"]+)'](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
257 data = data.replace(/\[url\=([^'"\]]+)](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
d45eaff6
MW
258
259 // [email]
260 data = data.replace(/\[email\]([^"]+?)\[\/email]/gi, '<a href="mailto:$1">$1</a>');
261 data = data.replace(/\[email\=([^"\]]+)](.+?)\[\/email]/gi, '<a href="mailto:$1">$2</a>');
262
263 // [b]
264 data = data.replace(/\[b\](.*?)\[\/b]/gi, '<b>$1</b>');
265
266 // [i]
267 data = data.replace(/\[i\](.*?)\[\/i]/gi, '<i>$1</i>');
268
269 // [u]
270 data = data.replace(/\[u\](.*?)\[\/u]/gi, '<u>$1</u>');
271
272 // [s]
679b13aa 273 data = data.replace(/\[s\](.*?)\[\/s]/gi, '<s>$1</s>');
d45eaff6
MW
274
275 // [sub]
276 data = data.replace(/\[sub\](.*?)\[\/sub]/gi, '<sub>$1</sub>');
277
278 // [sup]
279 data = data.replace(/\[sup\](.*?)\[\/sup]/gi, '<sup>$1</sup>');
280
281 // [img]
282 data = data.replace(/\[img\]([^"]+?)\[\/img\]/gi,'<img src="$1" />');
25e14f52 283 data = data.replace(/\[img='?([^"]*?)'?,'?(left|right)'?\]\[\/img\]/gi,'<img src="$1" style="float: $2" />');
d45eaff6
MW
284 data = data.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,'<img src="$1" />');
285
286 // [quote]
287 // data = data.replace(/\[quote\]/gi, '<blockquote>');
288 // data = data.replace(/\[\/quote\]/gi, '</blockquote>');
289
290 // [size]
291 data = data.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
292
293 // [color]
294 data = data.replace(/\[color=([#a-z0-9]*?)\](.*?)\[\/color\]/gi,'<span style="color: $1">$2</span>');
295
296 // [font]
297 data = data.replace(/\[font='?([a-z,\- ]*?)'?\](.*?)\[\/font\]/gi,'<span style="font-family: $1">$2</span>');
298
299 // [align]
300 data = data.replace(/\[align=(left|right|center|justify)\](.*?)\[\/align\]/gi,'<div style="text-align: $1">$2</div>');
301
302 // [*]
303 data = data.replace(/\[\*\](.*?)(?=\[\*\]|\[\/list\])/gi,'<li>$1</li>');
304
305 // [list]
306 data = data.replace(/\[list\]/gi, '<ul>');
307 data = data.replace(/\[list=1\]/gi, '<ul style="list-style-type: decimal">');
2ee70bbe
MW
308 data = data.replace(/\[list=a\]/gi, '<ul style="list-style-type: lower-latin">');
309 data = data.replace(/\[list=(none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)\]/gi, '<ul style="list-style-type: $1">');
d45eaff6
MW
310 data = data.replace(/\[\/list]/gi, '</ul>');
311
312 // [table]
313 data = data.replace(/\[table\]/gi, '<table border="1" cellspacing="1" cellpadding="1" style="width: 500px;">');
314 data = data.replace(/\[\/table\]/gi, '</table>');
315 // [tr]
316 data = data.replace(/\[tr\]/gi, '<tr>');
317 data = data.replace(/\[\/tr\]/gi, '</tr>');
318 // [td]
319 data = data.replace(/\[td\]/gi, '<td>');
320 data = data.replace(/\[\/td\]/gi, '</td>');
321
322 // smileys
323 for (var i = 0; i < this.editor.config.smiley_descriptions.length; i++) {
324 var smileyCode = this.editor.config.smiley_descriptions[i].replace(/</g, '&lt;').replace(/>/g, '&gt;');
325 var regExp = new RegExp('(\\s|>|^)'+WCF.String.escapeRegExp(smileyCode)+'(?=\\s|<|$)', 'gi');
326 data = data.replace(regExp, '$1<img src="' + this.editor.config.smiley_path + this.editor.config.smiley_images[i] + '" class="smiley" alt="'+smileyCode+'" />');
327 }
328
329 // remove "javascript:"
330 data = data.replace(/(javascript):/gi, '$1<span></span>:');
331
332 // insert codes
333 if ($.getLength($cachedCodes)) {
334 for (var $key in $cachedCodes) {
335 var $regex = new RegExp('@@' + $key + '@@', 'g');
336 data = data.replace($regex, $cachedCodes[$key]);
337 }
338 }
339
340 return data;
341 };
342
343 /**
344 * Converts html to bbcodes.
345 */
346 var toDataFormat = function(html, fixForBody) {
347 if (html == '<br>' || html == '<p><br></p>') {
348 return "";
349 }
350
351 // Convert <br> to line breaks.
352 html = html.replace(/<br><\/p>/gi,"\n");
353 html = html.replace(/<br(?=[ \/>]).*?>/gi, '\r\n');
354 html = html.replace(/<p>/gi,"");
355 html = html.replace(/<\/p>/gi,"\n");
356 html = html.replace(/&nbsp;/gi," ");
357
358 // [email]
4efc488e 359 html = html.replace(/<a [^>]*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]');
d45eaff6
MW
360
361 // [url]
4efc488e 362 html = html.replace(/<a [^>]*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, function(match, x, url, text) {
49630032
MW
363 if (url == text) return '[url]' + url + '[/url]';
364
365 return "[url='" + url + "']" + text + "[/url]";
366 });
d45eaff6
MW
367
368 // [b]
369 html = html.replace(/<(?:b|strong)>/gi, '[b]');
370 html = html.replace(/<\/(?:b|strong)>/gi, '[/b]');
371
372 // [i]
373 html = html.replace(/<(?:i|em)>/gi, '[i]');
374 html = html.replace(/<\/(?:i|em)>/gi, '[/i]');
375
376 // [u]
377 html = html.replace(/<u>/gi, '[u]');
378 html = html.replace(/<\/u>/gi, '[/u]');
379
380 // [s]
679b13aa
MW
381 html = html.replace(/<s>/gi, '[s]');
382 html = html.replace(/<\/s>/gi, '[/s]');
d45eaff6 383
4efc488e 384 // [sub]
d45eaff6
MW
385 html = html.replace(/<sub>/gi, '[sub]');
386 html = html.replace(/<\/sub>/gi, '[/sub]');
387
388 // [sup]
389 html = html.replace(/<sup>/gi, '[sup]');
390 html = html.replace(/<\/sup>/gi, '[/sup]');
391
392 // smileys
393 html = html.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?>/gi, '$1'); // firefox
394 html = html.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?>/gi, '$1'); // chrome, ie
395
396 // [img]
397 html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1 style="float: (left|right)".*?>/gi, "[img='$2',$3][/img]");
398 html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]');
399
400 // [quote]
401 // html = html.replace(/<blockquote>/gi, '[quote]');
402 // html = html.replace(/\n*<\/blockquote>/gi, '[/quote]');
403
404 // [color]
405 html = html.replace(/<span style="color: ?rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\);?">([\s\S]*?)<\/span>/gi, function(match, r, g, b, text) {
406 var $hex = ("0123456789ABCDEF".charAt((r - r % 16) / 16) + '' + "0123456789ABCDEF".charAt(r % 16)) + '' + ("0123456789ABCDEF".charAt((g - g % 16) / 16) + '' + "0123456789ABCDEF".charAt(g % 16)) + '' + ("0123456789ABCDEF".charAt((b - b % 16) / 16) + '' + "0123456789ABCDEF".charAt(b % 16));
407
408 return "[color=#" + $hex + "]" + text + "[/color]";
409 });
410 html = html.replace(/<span style="color: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[color=$1]$2[/color]");
411
412 // [size]
413 html = html.replace(/<span style="font-size: ?(\d+)pt;?">([\s\S]*?)<\/span>/gi, "[size=$1]$2[/size]");
414
415 // [font]
416 html = html.replace(/<span style="font-family: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[font='$1']$2[/font]");
417
418 // [align]
419 html = html.replace(/<div style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/div>/gi, "[align=$1]$2[/align]");
420
421 // [*]
422 html = html.replace(/<li>/gi, '[*]');
423 html = html.replace(/<\/li>/gi, '');
424
425 // [list]
426 html = html.replace(/<ul>/gi, '[list]');
427 html = html.replace(/<(ol|ul style="list-style-type: decimal")>/gi, '[list=1]');
2ee70bbe 428 html = html.replace(/<ul style="list-style-type: (none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)">/gi, '[list=$1]');
d45eaff6
MW
429 html = html.replace(/<\/(ul|ol)>/gi, '[/list]');
430
431 // [table]
432 html = html.replace(/<table[^>]*>/gi, '[table]');
433 html = html.replace(/<\/table>/gi, '[/table]');
434
435 // remove empty <tr>s
436 html = html.replace(/<tr><\/tr>/gi, '');
437 // [tr]
438 html = html.replace(/<tr>/gi, '[tr]');
439 html = html.replace(/<\/tr>/gi, '[/tr]');
440
d124bc6e
MW
441 // [td]+[align]
442 html = html.replace(/<td style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/td>/gi, "[td][align=$1]$2[/align][/td]");
443
d45eaff6
MW
444 // [td]
445 html = html.replace(/<td>/gi, '[td]');
446 html = html.replace(/<\/td>/gi, '[/td]');
447
448 // Remove remaining tags.
449 html = html.replace(/<[^>]+>/g, '');
450
451 // Restore <, > and &
452 html = html.replace(/&lt;/g, '<');
453 html = html.replace(/&gt;/g, '>');
454 html = html.replace(/&amp;/g, '&');
455
456 // Restore (and )
457 html = html.replace(/%28/g, '(');
458 html = html.replace(/%29/g, ')');
459
460 // Restore %20
461 html = html.replace(/%20/g, ' ');
462
463 return html;
7fd6e30b 464 };
d45eaff6 465})();