1 if (!RedactorPlugins
) var RedactorPlugins
= {};
4 * Provides the smiley button and modifies the source mode to transform HTML into BBCodes.
6 * @author Alexander Ebert, Marcel Werk
7 * @copyright 2001-2014 WoltLab GmbH
8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
10 RedactorPlugins
.wbbcode
= {
12 * Initializes the RedactorPlugins.wbbcode plugin.
15 this._createSmileyDropdown();
18 this.buttonReplace('smiley', 'wsmiley', 'Smiley', $.proxy(function(btnName
, $button
, btnObject
, e
) {
19 this.dropdownShow(e
, btnName
);
21 this.buttonAwesome('wsmiley', 'fa-smile-o');
23 this.opts
.initCallback
= $.proxy(function() {
24 if (this.$source
.val().length
) {
32 * Creates the smiley dropdown.
34 _createSmileyDropdown: function() {
35 var $dropdown
= $('<div class="redactor_dropdown redactor_dropdown_box_wsmiley" style="display: none; width: 195px;" />');
36 var $list
= $('<ul class="smileyList" />').appendTo($dropdown
);
38 for (var $smileyCode
in __REDACTOR_SMILIES
) {
39 var $insertLink
= $('<li><img src="' + __REDACTOR_SMILIES
[$smileyCode
] + '" class="smiley" /></li>').data('smileyCode', $smileyCode
);
40 $insertLink
.appendTo($list
).click($.proxy(this._onSmileyPick
, this));
43 $(this.$toolbar
).append($dropdown
);
47 * Inserts smiley on click.
51 _onSmileyPick: function(event
) {
52 var $smileyCode
= $(event
.currentTarget
).data('smileyCode');
53 this.insertSmiley($smileyCode
, __REDACTOR_SMILIES
[$smileyCode
], false);
57 * Inserts a smiley, optionally trying to register a new smiley.
59 * @param string smileyCode
60 * @param string smileyPath
61 * @param boolean registerSmiley
63 insertSmiley: function(smileyCode
, smileyPath
, registerSmiley
) {
65 this.registerSmiley(smileyCode
, smileyPath
);
68 if (this.opts
.visual
) {
73 this.insertHtml(' <img src="' + smileyPath
+ '" class="smiley" alt="' + smileyCode
+ '" /> ');
75 if (this.opts
.air
) this.$air
.fadeOut(100);
79 this.insertAtCaret(' ' + smileyCode
+ ' ');
84 * Registers a new smiley, returns false if the smiley code is already registered.
86 * @param string smileyCode
87 * @param string smileyPath
90 registerSmiley: function(smileyCode
, smileyPath
) {
91 if (__REDACTOR_SMILIES
[smileyCode
]) {
95 __REDACTOR_SMILIES
[smileyCode
] = smileyPath
;
101 * Overwrites $.Redactor.toggle() to transform the source mode into a BBCode view.
103 * @see $.Redactor.toggle()
104 * @param string direct
106 toggle: function(direct
) {
107 if (this.opts
.visual
) {
108 this._convertParagraphs();
109 this.toggleCode(direct
);
110 this._convertFromHtml();
112 this.buttonGet('html').children('i').removeClass('fa-square-o').addClass('fa-square');
115 this._convertToHtml();
118 this.buttonGet('html').children('i').removeClass('fa-square').addClass('fa-square-o');
122 _convertParagraphs: function() {
123 this.$editor
.find('p').replaceWith(function() {
124 var $html
= $(this).html();
125 if ($html
== '<br>') {
126 // an empty line is presented by <p><br></p> but in the textarea this equals only a single new line
130 return $html
+ '<br>';
136 * Converts source contents from HTML into BBCode.
138 _convertFromHtml: function() {
139 var html
= this.$source
.val();
141 // drop line break right before/after a <pre> tag (used by [code]-BBCode)
142 html
= html
.replace(/<br>\n<pre>\n/g, '');
143 html
= html
.replace(/<\/pre>\n<br>\n/g, '');
145 // drop <br>, they are pointless because the editor already adds a newline after them
146 html
= html
.replace(/<br>/g, '');
147 html
= html
.replace(/ /gi," ");
150 html
= html
.replace(/<a [^>]*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]');
153 html
= html
.replace(/<a [^>]*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, function(match
, x
, url
, text
) {
154 if (url
== text
) return '[url]' + url
+ '[/url]';
156 return "[url='" + url
+ "']" + text
+ "[/url]";
160 html
= html
.replace(/<(?:b|strong)>/gi, '[b]');
161 html
= html
.replace(/<\/(?:b|strong)>/gi, '[/b]');
164 html
= html
.replace(/<(?:i|em)>/gi, '[i]');
165 html
= html
.replace(/<\/(?:i|em)>/gi, '[/i]');
168 html
= html
.replace(/<u>/gi, '[u]');
169 html
= html
.replace(/<\/u>/gi, '[/u]');
172 html
= html
.replace(/<(?:s(trike)?|del)>/gi, '[s]');
173 html
= html
.replace(/<\/(?:s(trike)?|del)>/gi, '[/s]');
176 html
= html
.replace(/<sub>/gi, '[sub]');
177 html
= html
.replace(/<\/sub>/gi, '[/sub]');
180 html
= html
.replace(/<sup>/gi, '[sup]');
181 html
= html
.replace(/<\/sup>/gi, '[/sup]');
184 html
= html
.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?>/gi, '$1'); // firefox
185 html
= html
.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?>/gi, '$1'); // chrome, ie
188 html
= html
.replace(/<img [^>]*?src=(["'])([^"']+?)\1 style="float: (left|right)[^"]*".*?>/gi, "[img='$2',$3][/img]");
189 html
= html
.replace(/<img [^>]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]');
192 // html = html.replace(/<blockquote>/gi, '[quote]');
193 // html = html.replace(/\n*<\/blockquote>/gi, '[/quote]');
196 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
) {
197 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));
199 return "[color=#" + $hex
+ "]" + text
+ "[/color]";
201 html
= html
.replace(/<span style="color: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[color=$1]$2[/color]");
204 html
= html
.replace(/<span style="font-size: ?(\d+)pt;?">([\s\S]*?)<\/span>/gi, "[size=$1]$2[/size]");
207 html
= html
.replace(/<span style="font-family: ?(.*?);?">([\s\S]*?)<\/span>/gi, function(match
, fontFamily
, text
) {
208 return "[font='" + fontFamily
.replace(/'/g, '') + "']" + text + "[/font
]";
212 html = html.replace(/<div style="text
-align
: ?(left
|center
|right
|justify
);? ?">([\s\S]*?)<\/div>/gi, "[align
=$1]$2[/align
]");
215 html = html.replace(/<li>/gi, '[*]');
216 html = html.replace(/<\/li>/gi, '');
219 html = html.replace(/<ul>/gi, '[list]');
220 html = html.replace(/<(ol|ul style="list
-style
-type
: decimal")>/gi, '[list=1]');
221 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]');
222 html = html.replace(/<\/(ul|ol)>/gi, '[/list]');
225 html = html.replace(/<table[^>]*>/gi, '[table]');
226 html = html.replace(/<\/table>/gi, '[/table]');
228 // remove empty <tr>s
229 html = html.replace(/<tr><\/tr>/gi, '');
231 html = html.replace(/<tr>/gi, '[tr]');
232 html = html.replace(/<\/tr>/gi, '[/tr]');
235 html = html.replace(/<td style="text
-align
: ?(left
|center
|right
|justify
);? ?">([\s\S]*?)<\/td>/gi, "[td
][align
=$1]$2[/align][/td
]");
238 html = html.replace(/<td>/gi, '[td]');
239 html = html.replace(/<\/td>/gi, '[/td]');
241 // cache redactor's selection markers
242 var $cachedMarkers = { };
243 html.replace(/<span id="selection
-marker
-\d
+" class="redactor
-selection
-marker
"><\/span>/, function(match) {
244 var $key = match.hashCode();
245 $cachedMarkers[$key] = match.replace(/\$/g, '$$$$');
246 return '@@' + $key + '@@';
249 // Remove remaining tags.
250 html = html.replace(/<[^>]+>/g, '');
252 // insert redactor's selection markers
253 if ($.getLength($cachedMarkers)) {
254 for (var $key in $cachedMarkers) {
255 var $regex = new RegExp('@@' + $key + '@@', 'g');
256 data = data.replace($regex, $cachedMarkers[$key]);
260 // Restore <, > and &
261 html = html.replace(/</g, '<');
262 html = html.replace(/>/g, '>');
263 html = html.replace(/&/g, '&');
266 html = html.replace(/%28/g, '(');
267 html = html.replace(/%29/g, ')');
270 html = html.replace(/%20/g, ' ');
272 // cache source code tags to preserve leading tabs
273 var $cachedCodes = { };
274 for (var $i = 0, $length = __REDACTOR_SOURCE_BBCODES.length; $i < $length; $i++) {
275 var $bbcode = __REDACTOR_SOURCE_BBCODES[$i];
277 var $regExp = new RegExp('\\[' + $bbcode + '([\\S\\s]+?)\\[\\/' + $bbcode + '\\]', 'gi');
278 html = html.replace($regExp, function(match) {
279 var $key = match.hashCode();
280 $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
281 return '@@' + $key + '@@';
286 var $tmp = html.split("\n");
287 for (var $i = 0, $length = $tmp.length; $i < $length; $i++) {
288 $tmp[$i] = $tmp[$i].replace(/^\s*/, '');
290 html = $tmp.join("\n");
293 if ($.getLength($cachedCodes)) {
294 for (var $key in $cachedCodes) {
295 var $regex = new RegExp('@@' + $key + '@@', 'g');
296 html = html.replace($regex, $cachedCodes[$key]);
300 this.$source.val(html);
304 * Converts source contents from BBCode to HTML.
306 _convertToHtml: function() {
307 var data = this.$source.val();
309 // remove 0x200B (unicode zero width space)
310 data = this.removeZeroWidthSpace(data);
313 // Convert & to its HTML entity.
314 data = data.replace(/&/g, '&');
316 // Convert < and > to their HTML entities.
317 data = data.replace(/</g, '<');
318 data = data.replace(/>/g, '>');
327 // cache source code tags
328 var $cachedCodes = { };
329 for (var $i = 0, $length = __REDACTOR_SOURCE_BBCODES.length; $i < $length; $i++) {
330 var $bbcode = __REDACTOR_SOURCE_BBCODES[$i];
332 var $regExp = new RegExp('\\[' + $bbcode + '([\\S\\s]+?)\\[\\/' + $bbcode + '\\]', 'gi');
333 data = data.replace($regExp, function(match) {
334 var $key = match.hashCode();
335 $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
336 return '@@' + $key + '@@';
341 data = data.replace(/\[url\]([^"]+?)\[\/url
]/gi
, '<a href="$1">$1</a>');
342 data
= data
.replace(/\[url\='([^'"]+)'](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
343 data
= data
.replace(/\[url\=([^'"\]]+)](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
346 data
= data
.replace(/\[email\]([^"]+?)\[\/email]/gi, '<a href="mailto:$1">$1</a>');
347 data
= data
.replace(/\[email\=([^"\]]+)](.+?)\[\/email]/gi, '<a href="mailto:$1">$2</a>');
350 data
= data
.replace(/\[b\](.*?)\[\/b]/gi, '<b>$1</b>');
353 data
= data
.replace(/\[i\](.*?)\[\/i]/gi, '<i>$1</i>');
356 data
= data
.replace(/\[u\](.*?)\[\/u]/gi, '<u>$1</u>');
359 data
= data
.replace(/\[s\](.*?)\[\/s]/gi, '<strike>$1</strike>');
362 data
= data
.replace(/\[sub\](.*?)\[\/sub]/gi, '<sub>$1</sub>');
365 data
= data
.replace(/\[sup\](.*?)\[\/sup]/gi, '<sup>$1</sup>');
368 data
= data
.replace(/\[img\]([^"]+?)\[\/img\]/gi,'<img src="$1" />');
369 data
= data
.replace(/\[img='?([^"]*?)'?,'?(left|right)'?\]\[\/img\]/gi,'<img src="$1" style="float: $2" />');
370 data
= data
.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,'<img src="$1" />');
373 // data = data.replace(/\[quote\]/gi, '<blockquote>');
374 // data = data.replace(/\[\/quote\]/gi, '</blockquote>');
377 data
= data
.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
380 data
= data
.replace(/\[color=([#a-z0-9]*?)\](.*?)\[\/color\]/gi,'<span style="color: $1">$2</span>');
383 data
= data
.replace(/\[font='?([a-z,\- ]*?)'?\](.*?)\[\/font\]/gi,'<span style="font-family: $1">$2</span>');
386 data
= data
.replace(/\[align=(left|right|center|justify)\](.*?)\[\/align\]/gi,'<div style="text-align: $1">$2</div>');
389 data
= data
.replace(/\[\*\](.*?)(?=\[\*\]|\[\/list\])/gi,'<li>$1</li>');
392 data
= data
.replace(/\[list\]/gi, '<ul>');
393 data
= data
.replace(/\[list=1\]/gi, '<ul style="list-style-type: decimal">');
394 data
= data
.replace(/\[list=a\]/gi, '<ul style="list-style-type: lower-latin">');
395 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">');
396 data
= data
.replace(/\[\/list]/gi, '</ul>');
399 data
= data
.replace(/\[table\]/gi, '<table border="1" cellspacing="1" cellpadding="1" style="width: 500px;">');
400 data
= data
.replace(/\[\/table\]/gi, '</table>');
402 data
= data
.replace(/\[tr\]/gi, '<tr>');
403 data
= data
.replace(/\[\/tr\]/gi, '</tr>');
405 data
= data
.replace(/\[td\]/gi, '<td>');
406 data
= data
.replace(/\[\/td\]/gi, '</td>');
409 for (var smileyCode
in __REDACTOR_SMILIES
) {
410 $smileyCode
= smileyCode
.replace(/</g, '<').replace(/>/g
, '>');
411 var regExp
= new RegExp('(\\s|>|^)' + WCF
.String
.escapeRegExp($smileyCode
) + '(?=\\s|<|$)', 'gi');
412 data
= data
.replace(regExp
, '$1<img src="' + __REDACTOR_SMILIES
[smileyCode
] + '" class="smiley" alt="' + $smileyCode
+ '" />');
415 // remove "javascript:"
416 data
= data
.replace(/(javascript):/gi, '$1<span></span>:');
419 data
= data
.replace(/(\r|\r\n)/, "\n");
421 // convert line breaks into <p></p> or empty lines to <p><br></p>
422 var $tmp
= data
.split("\n");
424 for (var $i
= 0, $length
= $tmp
.length
; $i
< $length
; $i
++) {
425 var $line
= $.trim($tmp
[$i
]);
430 data
+= '<p>' + $line
+ '</p>';
434 if ($.getLength($cachedCodes
)) {
435 for (var $key
in $cachedCodes
) {
436 var $regex
= new RegExp('@@' + $key
+ '@@', 'g');
437 data
= data
.replace($regex
, $cachedCodes
[$key
]);
441 // preserve leading whitespaces in [code] tags
442 data
= data
.replace(/\[code\][\S\s]*?\[\/code\]/, '<pre>$&</pre>');
444 this.$source
.val(data
);