2 * BBCode Plugin for CKEditor
5 * @copyright 2001-2014 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
10 var $insertedText
= null;
12 CKEDITOR
.on('instanceReady', function(event
) {
14 * Fixes issues with pasted html.
16 event
.editor
.on('paste', function(ev
) {
17 if (ev
.data
.type
== 'html') {
18 var $value
= ev
.data
.dataValue
;
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(/ /gi," ");
26 // convert div-separated content into new lines
27 $value
= $value
.replace(/<div([^>])>/gi, '');
28 $value
= $value
.replace(/<\/div>/gi, "\n");
30 // convert lists into new lines
31 $value
= $value
.replace(/<\/li>/gi, "\n");
33 $value
= $value
.replace(/<[^>]+>/g, '');
35 // fix multiple new lines
36 $value
= $value
.replace(/\n{3,}/gi,"\n\n");
38 ev
.data
.dataValue
= $value
;
44 // prevent drag and drop of images in Firefox
45 if ($.browser
.mozilla
) {
46 event
.editor
.document
.on('drop', function(ev
) {
47 ev
.data
.preventDefault(true);
51 event
.editor
.on('insertText', function(ev
) {
52 $insertedText
= ev
.data
;
54 event
.editor
.on('mode', function(ev
) {
55 if ($.browser
.mozilla
&& ev
.editor
.mode
=== 'wysiwyg') {
61 insertFakeSubmitButton(ev
);
63 event
.editor
.on('afterSetData', function(ev
) {
64 insertFakeSubmitButton(ev
);
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
);
73 insertFakeSubmitButton(event
);
75 // remove stupid title tag
76 // @todo: obsolete in ckeditor 4.2
77 $(event
.editor
.container
.$).find('.cke_wysiwyg_div').removeAttr('title');
79 if ($.browser
.mozilla
) {
85 * Inserts a fake submit button, Chrome only.
89 function insertFakeSubmitButton(event
) {
90 if (event
.editor
.mode
=== 'source' || !WCF
.Browser
.isChrome()) {
94 // place button outside of <body> to prevent it being removed once deleting content
95 $('<button accesskey="s" />').hide().appendTo($(event
.editor
.container
.$).find('.cke_wysiwyg_div'));
100 * Disables object resizing and table handles in Firefox.
102 function fixFirefox() {
103 document
.designMode
= 'on';
104 document
.execCommand('enableObjectResizing', false, false);
105 document
.execCommand('enableInlineTableEditing', false, false);
106 document
.designMode
= 'off';
110 * Removes obsolete dialog elements.
112 CKEDITOR
.on('dialogDefinition', function(event
) {
114 var $name
= event
.data
.name
;
115 var $definition
= event
.data
.definition
;
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');
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');
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');
139 else if ($name
== 'table') {
140 $definition
.removeContents('advanced');
141 $definition
.width
= 210;
142 $definition
.height
= 1;
144 $tab
= $definition
.getContents('info');
146 $tab
.remove('selHeaders');
147 $tab
.remove('cmbAlign');
148 $tab
.remove('txtHeight');
149 $tab
.remove('txtCaption');
150 $tab
.remove('txtSummary');
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';
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();
163 if ($targetName
== 'a') {
164 $target
= $target
.getChild( 0 );
166 else if ($targetName
!= 'img') {
170 var $src
= $target
.getAttribute('cke_src');
171 var $title
= $target
.getAttribute('title');
173 event
.editor
.insertText(' ' + $title
+ ' ');
175 $definition
.dialog
.hide();
176 ev
.data
.preventDefault();
182 * Enables this plugin.
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
;
194 * Removes the unicode zero width space (0x200B).
196 * @param string string
199 var removeCrap = function(string
) {
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
];
213 * Converts bbcodes to html.
215 var toHtml = function(data
, fixForBody
) {
216 //if ($.trim(data) === "") return "<p></p>";
218 // remove 0x200B (unicode zero width space)
219 data
= removeCrap(data
);
221 if ($insertedText
!== null) {
222 data
= $insertedText
;
223 $insertedText
= null;
225 if (data
== ' ') return ' ';
229 // Convert & to its HTML entity.
230 data
= data
.replace(/&/g
, '&');
232 // Convert < and > to their HTML entities.
233 data
= data
.replace(/</g
, '<');
234 data
= data
.replace(/>/g
, '>');
237 // Convert line breaks to <br>.
238 data
= data
.replace(/(?:\r\n|\n|\r)/g, '<br>');
247 var $cachedCodes
= { };
248 data
= data
.replace(/\[code(.+?)\[\/code]/gi, function(match
) {
249 var $key
= match
.hashCode();
250 $cachedCodes
[$key
] = match
.replace(/\$/g, '$$$$');
251 return '@@' + $key
+ '@@';
255 data
= data
.replace(/\[url\]([^"]+?)\[\/url]/gi, '<a href="$1">$1</a>');
256 data
= data
.replace(/\[url\='([^'"]+)'](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
257 data
= data
.replace(/\[url\=([^'"\]]+)](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
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>');
264 data
= data
.replace(/\[b\](.*?)\[\/b]/gi, '<b>$1</b>');
267 data
= data
.replace(/\[i\](.*?)\[\/i]/gi, '<i>$1</i>');
270 data
= data
.replace(/\[u\](.*?)\[\/u]/gi, '<u>$1</u>');
273 data
= data
.replace(/\[s\](.*?)\[\/s]/gi, '<s>$1</s>');
276 data
= data
.replace(/\[sub\](.*?)\[\/sub]/gi, '<sub>$1</sub>');
279 data
= data
.replace(/\[sup\](.*?)\[\/sup]/gi, '<sup>$1</sup>');
282 data
= data
.replace(/\[img\]([^"]+?)\[\/img\]/gi,'<img src="$1" />');
283 data
= data
.replace(/\[img='?([^"]*?)'?,'?(left|right)'?\]\[\/img\]/gi,'<img src="$1" style="float: $2" />');
284 data
= data
.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,'<img src="$1" />');
287 // data = data.replace(/\[quote\]/gi, '<blockquote>');
288 // data = data.replace(/\[\/quote\]/gi, '</blockquote>');
291 data
= data
.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
294 data
= data
.replace(/\[color=([#a-z0-9]*?)\](.*?)\[\/color\]/gi,'<span style="color: $1">$2</span>');
297 data
= data
.replace(/\[font='?([a-z,\- ]*?)'?\](.*?)\[\/font\]/gi,'<span style="font-family: $1">$2</span>');
300 data
= data
.replace(/\[align=(left|right|center|justify)\](.*?)\[\/align\]/gi,'<div style="text-align: $1">$2</div>');
303 data
= data
.replace(/\[\*\](.*?)(?=\[\*\]|\[\/list\])/gi,'<li>$1</li>');
306 data
= data
.replace(/\[list\]/gi, '<ul>');
307 data
= data
.replace(/\[list=1\]/gi, '<ul style="list-style-type: decimal">');
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">');
310 data
= data
.replace(/\[\/list]/gi, '</ul>');
313 data
= data
.replace(/\[table\]/gi, '<table border="1" cellspacing="1" cellpadding="1" style="width: 500px;">');
314 data
= data
.replace(/\[\/table\]/gi, '</table>');
316 data
= data
.replace(/\[tr\]/gi, '<tr>');
317 data
= data
.replace(/\[\/tr\]/gi, '</tr>');
319 data
= data
.replace(/\[td\]/gi, '<td>');
320 data
= data
.replace(/\[\/td\]/gi, '</td>');
323 for (var i
= 0; i
< this.editor
.config
.smiley_descriptions
.length
; i
++) {
324 var smileyCode
= this.editor
.config
.smiley_descriptions
[i
].replace(/</g, '<').replace(/>/g
, '>');
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
+'" />');
329 // remove "javascript:"
330 data
= data
.replace(/(javascript):/gi, '$1<span></span>:');
333 if ($.getLength($cachedCodes
)) {
334 for (var $key
in $cachedCodes
) {
335 var $regex
= new RegExp('@@' + $key
+ '@@', 'g');
336 data
= data
.replace($regex
, $cachedCodes
[$key
]);
344 * Converts html to bbcodes.
346 var toDataFormat = function(html
, fixForBody
) {
347 if (html
== '<br>' || html
== '<p><br></p>') {
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(/ /gi," ");
359 html
= html
.replace(/<a [^>]*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]');
362 html
= html
.replace(/<a [^>]*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, function(match
, x
, url
, text
) {
363 if (url
== text
) return '[url]' + url
+ '[/url]';
365 return "[url='" + url
+ "']" + text
+ "[/url]";
369 html
= html
.replace(/<(?:b|strong)>/gi, '[b]');
370 html
= html
.replace(/<\/(?:b|strong)>/gi, '[/b]');
373 html
= html
.replace(/<(?:i|em)>/gi, '[i]');
374 html
= html
.replace(/<\/(?:i|em)>/gi, '[/i]');
377 html
= html
.replace(/<u>/gi, '[u]');
378 html
= html
.replace(/<\/u>/gi, '[/u]');
381 html
= html
.replace(/<s>/gi, '[s]');
382 html
= html
.replace(/<\/s>/gi, '[/s]');
385 html
= html
.replace(/<sub>/gi, '[sub]');
386 html
= html
.replace(/<\/sub>/gi, '[/sub]');
389 html
= html
.replace(/<sup>/gi, '[sup]');
390 html
= html
.replace(/<\/sup>/gi, '[/sup]');
393 html
= html
.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?>/gi, '$1'); // firefox
394 html
= html
.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?>/gi, '$1'); // chrome, ie
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]');
401 // html = html.replace(/<blockquote>/gi, '[quote]');
402 // html = html.replace(/\n*<\/blockquote>/gi, '[/quote]');
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));
408 return "[color=#" + $hex
+ "]" + text
+ "[/color]";
410 html
= html
.replace(/<span style="color: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[color=$1]$2[/color]");
413 html
= html
.replace(/<span style="font-size: ?(\d+)pt;?">([\s\S]*?)<\/span>/gi, "[size=$1]$2[/size]");
416 html
= html
.replace(/<span style="font-family: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[font='$1']$2[/font]");
419 html
= html
.replace(/<div style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/div>/gi, "[align=$1]$2[/align]");
422 html
= html
.replace(/<li>/gi, '[*]');
423 html
= html
.replace(/<\/li>/gi, '');
426 html
= html
.replace(/<ul>/gi, '[list]');
427 html
= html
.replace(/<(ol|ul style="list-style-type: decimal")>/gi, '[list=1]');
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]');
429 html
= html
.replace(/<\/(ul|ol)>/gi, '[/list]');
432 html
= html
.replace(/<table[^>]*>/gi, '[table]');
433 html
= html
.replace(/<\/table>/gi, '[/table]');
435 // remove empty <tr>s
436 html
= html
.replace(/<tr><\/tr>/gi, '');
438 html
= html
.replace(/<tr>/gi, '[tr]');
439 html
= html
.replace(/<\/tr>/gi, '[/tr]');
442 html
= html
.replace(/<td style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/td>/gi, "[td][align=$1]$2[/align][/td]");
445 html
= html
.replace(/<td>/gi, '[td]');
446 html
= html
.replace(/<\/td>/gi, '[/td]');
448 // Remove remaining tags.
449 html
= html
.replace(/<[^>]+>/g, '');
451 // Restore <, > and &
452 html
= html
.replace(/</g, '<');
453 html
= html
.replace(/>/g, '>');
454 html
= html
.replace(/&/g, '&');
457 html
= html
.replace(/%28/g, '(');
458 html
= html
.replace(/%29/g, ')');
461 html
= html
.replace(/%20/g, ' ');