Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / 3rdParty / redactor / plugins / wbbcode.js
1 if (!RedactorPlugins) var RedactorPlugins = {};
2
3 /**
4 * Provides the smiley button and modifies the source mode to transform HTML into BBCodes.
5 *
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>
9 */
10 RedactorPlugins.wbbcode = {
11 /**
12 * Initializes the RedactorPlugins.wbbcode plugin.
13 */
14 init: function() {
15 this._createSmileyDropdown();
16
17
18 this.buttonReplace('smiley', 'wsmiley', 'Smiley', $.proxy(function(btnName, $button, btnObject, e) {
19 this.dropdownShow(e, btnName);
20 }, this));
21 this.buttonAwesome('wsmiley', 'fa-smile-o');
22
23 this.opts.initCallback = $.proxy(function() {
24 if (this.$source.val().length) {
25 this.toggle();
26 this.toggle();
27 }
28 }, this);
29 },
30
31 /**
32 * Creates the smiley dropdown.
33 */
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);
37
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));
41 }
42
43 $(this.$toolbar).append($dropdown);
44 },
45
46 /**
47 * Inserts smiley on click.
48 *
49 * @param object event
50 */
51 _onSmileyPick: function(event) {
52 var $smileyCode = $(event.currentTarget).data('smileyCode');
53 this.insertSmiley($smileyCode, __REDACTOR_SMILIES[$smileyCode], false);
54 },
55
56 /**
57 * Inserts a smiley, optionally trying to register a new smiley.
58 *
59 * @param string smileyCode
60 * @param string smileyPath
61 * @param boolean registerSmiley
62 */
63 insertSmiley: function(smileyCode, smileyPath, registerSmiley) {
64 if (registerSmiley) {
65 this.registerSmiley(smileyCode, smileyPath);
66 }
67
68 if (this.opts.visual) {
69 this.bufferSet();
70
71 this.$editor.focus();
72
73 this.insertHtml('&nbsp;<img src="' + smileyPath + '" class="smiley" alt="' + smileyCode + '" />&nbsp;');
74
75 if (this.opts.air) this.$air.fadeOut(100);
76 this.sync();
77 }
78 else {
79 this.insertAtCaret(' ' + smileyCode + ' ');
80 }
81 },
82
83 /**
84 * Registers a new smiley, returns false if the smiley code is already registered.
85 *
86 * @param string smileyCode
87 * @param string smileyPath
88 * @return boolean
89 */
90 registerSmiley: function(smileyCode, smileyPath) {
91 if (__REDACTOR_SMILIES[smileyCode]) {
92 return false;
93 }
94
95 __REDACTOR_SMILIES[smileyCode] = smileyPath;
96
97 return true;
98 },
99
100 /**
101 * Overwrites $.Redactor.toggle() to transform the source mode into a BBCode view.
102 *
103 * @see $.Redactor.toggle()
104 * @param string direct
105 */
106 toggle: function(direct) {
107 if (this.opts.visual) {
108 this._convertParagraphs();
109 this.toggleCode(direct);
110 this._convertFromHtml();
111
112 this.buttonGet('html').children('i').removeClass('fa-square-o').addClass('fa-square');
113 }
114 else {
115 this._convertToHtml();
116 this.toggleVisual();
117
118 this.buttonGet('html').children('i').removeClass('fa-square').addClass('fa-square-o');
119 }
120 },
121
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
127 return $html;
128 }
129
130 return $html + '<br>';
131 });
132 this.sync();
133 },
134
135 /**
136 * Converts source contents from HTML into BBCode.
137 */
138 _convertFromHtml: function() {
139 var html = this.$source.val();
140
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, '');
144
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(/&nbsp;/gi," ");
148
149 // [email]
150 html = html.replace(/<a [^>]*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]');
151
152 // [url]
153 html = html.replace(/<a [^>]*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, function(match, x, url, text) {
154 if (url == text) return '[url]' + url + '[/url]';
155
156 return "[url='" + url + "']" + text + "[/url]";
157 });
158
159 // [b]
160 html = html.replace(/<(?:b|strong)>/gi, '[b]');
161 html = html.replace(/<\/(?:b|strong)>/gi, '[/b]');
162
163 // [i]
164 html = html.replace(/<(?:i|em)>/gi, '[i]');
165 html = html.replace(/<\/(?:i|em)>/gi, '[/i]');
166
167 // [u]
168 html = html.replace(/<u>/gi, '[u]');
169 html = html.replace(/<\/u>/gi, '[/u]');
170
171 // [s]
172 html = html.replace(/<(?:s(trike)?|del)>/gi, '[s]');
173 html = html.replace(/<\/(?:s(trike)?|del)>/gi, '[/s]');
174
175 // [sub]
176 html = html.replace(/<sub>/gi, '[sub]');
177 html = html.replace(/<\/sub>/gi, '[/sub]');
178
179 // [sup]
180 html = html.replace(/<sup>/gi, '[sup]');
181 html = html.replace(/<\/sup>/gi, '[/sup]');
182
183 // smileys
184 html = html.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?>/gi, '$1'); // firefox
185 html = html.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?>/gi, '$1'); // chrome, ie
186
187 // [img]
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]');
190
191 // [quote]
192 // html = html.replace(/<blockquote>/gi, '[quote]');
193 // html = html.replace(/\n*<\/blockquote>/gi, '[/quote]');
194
195 // [color]
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));
198
199 return "[color=#" + $hex + "]" + text + "[/color]";
200 });
201 html = html.replace(/<span style="color: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[color=$1]$2[/color]");
202
203 // [size]
204 html = html.replace(/<span style="font-size: ?(\d+)pt;?">([\s\S]*?)<\/span>/gi, "[size=$1]$2[/size]");
205
206 // [font]
207 html = html.replace(/<span style="font-family: ?(.*?);?">([\s\S]*?)<\/span>/gi, function(match, fontFamily, text) {
208 return "[font='" + fontFamily.replace(/'/g, '') + "']" + text + "[/font]";
209 });
210
211 // [align]
212 html = html.replace(/<div style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/div>/gi, "[align=$1]$2[/align]");
213
214 // [*]
215 html = html.replace(/<li>/gi, '[*]');
216 html = html.replace(/<\/li>/gi, '');
217
218 // [list]
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]');
223
224 // [table]
225 html = html.replace(/<table[^>]*>/gi, '[table]');
226 html = html.replace(/<\/table>/gi, '[/table]');
227
228 // remove empty <tr>s
229 html = html.replace(/<tr><\/tr>/gi, '');
230 // [tr]
231 html = html.replace(/<tr>/gi, '[tr]');
232 html = html.replace(/<\/tr>/gi, '[/tr]');
233
234 // [td]+[align]
235 html = html.replace(/<td style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/td>/gi, "[td][align=$1]$2[/align][/td]");
236
237 // [td]
238 html = html.replace(/<td>/gi, '[td]');
239 html = html.replace(/<\/td>/gi, '[/td]');
240
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 + '@@';
247 });
248
249 // Remove remaining tags.
250 html = html.replace(/<[^>]+>/g, '');
251
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]);
257 }
258 }
259
260 // Restore <, > and &
261 html = html.replace(/&lt;/g, '<');
262 html = html.replace(/&gt;/g, '>');
263 html = html.replace(/&amp;/g, '&');
264
265 // Restore ( and )
266 html = html.replace(/%28/g, '(');
267 html = html.replace(/%29/g, ')');
268
269 // Restore %20
270 html = html.replace(/%20/g, ' ');
271
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];
276
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 + '@@';
282 });
283 }
284
285 // trim leading tabs
286 var $tmp = html.split("\n");
287 for (var $i = 0, $length = $tmp.length; $i < $length; $i++) {
288 $tmp[$i] = $tmp[$i].replace(/^\s*/, '');
289 }
290 html = $tmp.join("\n");
291
292 // insert codes
293 if ($.getLength($cachedCodes)) {
294 for (var $key in $cachedCodes) {
295 var $regex = new RegExp('@@' + $key + '@@', 'g');
296 html = html.replace($regex, $cachedCodes[$key]);
297 }
298 }
299
300 this.$source.val(html);
301 },
302
303 /**
304 * Converts source contents from BBCode to HTML.
305 */
306 _convertToHtml: function() {
307 var data = this.$source.val();
308
309 // remove 0x200B (unicode zero width space)
310 data = this.removeZeroWidthSpace(data);
311
312 //if (!$pasted) {
313 // Convert & to its HTML entity.
314 data = data.replace(/&/g, '&amp;');
315
316 // Convert < and > to their HTML entities.
317 data = data.replace(/</g, '&lt;');
318 data = data.replace(/>/g, '&gt;');
319 //}
320
321 /*if ($pasted) {
322 $pasted = false;
323 // skip
324 return data;
325 }*/
326
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];
331
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 + '@@';
337 });
338 }
339
340 // [url]
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>');
344
345 // [email]
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>');
348
349 // [b]
350 data = data.replace(/\[b\](.*?)\[\/b]/gi, '<b>$1</b>');
351
352 // [i]
353 data = data.replace(/\[i\](.*?)\[\/i]/gi, '<i>$1</i>');
354
355 // [u]
356 data = data.replace(/\[u\](.*?)\[\/u]/gi, '<u>$1</u>');
357
358 // [s]
359 data = data.replace(/\[s\](.*?)\[\/s]/gi, '<strike>$1</strike>');
360
361 // [sub]
362 data = data.replace(/\[sub\](.*?)\[\/sub]/gi, '<sub>$1</sub>');
363
364 // [sup]
365 data = data.replace(/\[sup\](.*?)\[\/sup]/gi, '<sup>$1</sup>');
366
367 // [img]
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" />');
371
372 // [quote]
373 // data = data.replace(/\[quote\]/gi, '<blockquote>');
374 // data = data.replace(/\[\/quote\]/gi, '</blockquote>');
375
376 // [size]
377 data = data.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
378
379 // [color]
380 data = data.replace(/\[color=([#a-z0-9]*?)\](.*?)\[\/color\]/gi,'<span style="color: $1">$2</span>');
381
382 // [font]
383 data = data.replace(/\[font='?([a-z,\- ]*?)'?\](.*?)\[\/font\]/gi,'<span style="font-family: $1">$2</span>');
384
385 // [align]
386 data = data.replace(/\[align=(left|right|center|justify)\](.*?)\[\/align\]/gi,'<div style="text-align: $1">$2</div>');
387
388 // [*]
389 data = data.replace(/\[\*\](.*?)(?=\[\*\]|\[\/list\])/gi,'<li>$1</li>');
390
391 // [list]
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>');
397
398 // [table]
399 data = data.replace(/\[table\]/gi, '<table border="1" cellspacing="1" cellpadding="1" style="width: 500px;">');
400 data = data.replace(/\[\/table\]/gi, '</table>');
401 // [tr]
402 data = data.replace(/\[tr\]/gi, '<tr>');
403 data = data.replace(/\[\/tr\]/gi, '</tr>');
404 // [td]
405 data = data.replace(/\[td\]/gi, '<td>');
406 data = data.replace(/\[\/td\]/gi, '</td>');
407
408 // smileys
409 for (var smileyCode in __REDACTOR_SMILIES) {
410 $smileyCode = smileyCode.replace(/</g, '&lt;').replace(/>/g, '&gt;');
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 + '" />');
413 }
414
415 // remove "javascript:"
416 data = data.replace(/(javascript):/gi, '$1<span></span>:');
417
418 // unify line breaks
419 data = data.replace(/(\r|\r\n)/, "\n");
420
421 // convert line breaks into <p></p> or empty lines to <p><br></p>
422 var $tmp = data.split("\n");
423 data = '';
424 for (var $i = 0, $length = $tmp.length; $i < $length; $i++) {
425 var $line = $.trim($tmp[$i]);
426 if (!$line) {
427 $line = '<br>';
428 }
429
430 data += '<p>' + $line + '</p>';
431 }
432
433 // insert codes
434 if ($.getLength($cachedCodes)) {
435 for (var $key in $cachedCodes) {
436 var $regex = new RegExp('@@' + $key + '@@', 'g');
437 data = data.replace($regex, $cachedCodes[$key]);
438 }
439 }
440
441 // preserve leading whitespaces in [code] tags
442 data = data.replace(/\[code\][\S\s]*?\[\/code\]/, '<pre>$&</pre>');
443
444 this.$source.val(data);
445 }
446 };