diff --git a/app/assets/javascripts/ckeditor/bbcode/dev/bbcode.html b/app/assets/javascripts/ckeditor/bbcode/dev/bbcode.html
new file mode 100644
index 0000000..f60e39f
--- /dev/null
+++ b/app/assets/javascripts/ckeditor/bbcode/dev/bbcode.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+ This sample shows how to configure CKEditor to output BBCode format instead of HTML.
+ Please note that the editor configuration was modified to reflect what is needed in a BBCode editing environment.
+ Smiley images, for example, were stripped to the emoticons that are commonly used in some BBCode dialects.
+
+
+ Please note that currently there is no standard for the BBCode markup language, so its implementation
+ for different platforms (message boards, blogs etc.) can vary. This means that before using CKEditor to
+ output BBCode you may need to adjust the implementation to your own environment.
+
+
+ A snippet of the configuration code can be seen below; check the source of this page for
+ a full definition:
+
+
+CKEDITOR.inline( 'editor1', {
+ extraPlugins : 'bbcode',
+ (below configurations details omitted:)
+ toolbar : ...,
+ fontSize_sizes : ...,
+ smiley_images : ...,
+ smiley_descriptions : ...
+});
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/ckeditor/bbcode/plugin.js b/app/assets/javascripts/ckeditor/bbcode/plugin.js
new file mode 100644
index 0000000..1890d6e
--- /dev/null
+++ b/app/assets/javascripts/ckeditor/bbcode/plugin.js
@@ -0,0 +1,777 @@
+/**
+ * @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.html or http://ckeditor.com/license
+ */
+
+(function() {
+ CKEDITOR.on( 'dialogDefinition', function( ev ) {
+ var tab,
+ name = ev.data.name,
+ definition = ev.data.definition;
+
+ if ( name == 'link' ) {
+ definition.removeContents( 'target' );
+ definition.removeContents( 'upload' );
+ definition.removeContents( 'advanced' );
+ tab = definition.getContents( 'info' );
+ tab.remove( 'emailSubject' );
+ tab.remove( 'emailBody' );
+ } else if ( name == 'image' ) {
+ definition.removeContents( 'advanced' );
+ tab = definition.getContents( 'Link' );
+ tab.remove( 'cmbTarget' );
+ tab = definition.getContents( 'info' );
+ tab.remove( 'txtAlt' );
+ tab.remove( 'basic' );
+ }
+ });
+
+ var bbcodeMap = { b: 'strong', u: 'u', i: 'em', color: 'span', size: 'span', quote: 'blockquote', code: 'code', url: 'a', email: 'span', img: 'span', '*': 'li', list: 'ol' },
+ convertMap = { strong: 'b', b: 'b', u: 'u', em: 'i', i: 'i', code: 'code', li: '*' },
+ tagnameMap = { strong: 'b', em: 'i', u: 'u', li: '*', ul: 'list', ol: 'list', code: 'code', a: 'link', img: 'img', blockquote: 'quote' },
+ stylesMap = { color: 'color', size: 'font-size' },
+ attributesMap = { url: 'href', email: 'mailhref', quote: 'cite', list: 'listType' };
+
+ // List of block-like tags.
+ var dtd = CKEDITOR.dtd,
+ blockLikeTags = CKEDITOR.tools.extend( { table:1 }, dtd.$block, dtd.$listItem, dtd.$tableContent, dtd.$list );
+
+ var semicolonFixRegex = /\s*(?:;\s*|$)/;
+
+ function serializeStyleText( stylesObject ) {
+ var styleText = '';
+ for ( var style in stylesObject ) {
+ var styleVal = stylesObject[ style ],
+ text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );
+
+ styleText += text;
+ }
+ return styleText;
+ }
+
+ // Maintain the map of smiley-to-description.
+ var smileyMap = { smiley: ':)', sad: ':(', wink: ';)', laugh: ':D', cheeky: ':P', blush: ':*)', surprise: ':-o', indecision: ':|', angry: '>:(', angel: 'o:)', cool: '8-)', devil: '>:-)', crying: ';(', kiss: ':-*' },
+ smileyReverseMap = {},
+ smileyRegExp = [];
+
+ // Build regexp for the list of smiley text.
+ for ( var i in smileyMap ) {
+ smileyReverseMap[ smileyMap[ i ] ] = i;
+ smileyRegExp.push( smileyMap[ i ].replace( /\(|\)|\:|\/|\*|\-|\|/g, function( match ) {
+ return '\\' + match;
+ }));
+ }
+
+ smileyRegExp = new RegExp( smileyRegExp.join( '|' ), 'g' );
+
+ var decodeHtml = (function() {
+ var regex = [],
+ entities = {
+ nbsp: '\u00A0', // IE | FF
+ shy: '\u00AD', // IE
+ gt: '\u003E', // IE | FF | -- | Opera
+ lt: '\u003C' // IE | FF | Safari | Opera
+ };
+
+ for ( var entity in entities )
+ regex.push( entity );
+
+ regex = new RegExp( '&(' + regex.join( '|' ) + ');', 'g' );
+
+ return function( html ) {
+ return html.replace( regex, function( match, entity ) {
+ return entities[ entity ];
+ });
+ };
+ })();
+
+ CKEDITOR.BBCodeParser = function() {
+ this._ = {
+ bbcPartsRegex: /(?:\[([^\/\]=]*?)(?:=([^\]]*?))?\])|(?:\[\/([a-z]{1,16})\])/ig
+ };
+ };
+
+ CKEDITOR.BBCodeParser.prototype = {
+ parse: function( bbcode ) {
+ var parts, part,
+ lastIndex = 0;
+
+ while ( ( parts = this._.bbcPartsRegex.exec( bbcode ) ) ) {
+ var tagIndex = parts.index;
+ if ( tagIndex > lastIndex ) {
+ var text = bbcode.substring( lastIndex, tagIndex );
+ this.onText( text, 1 );
+ }
+
+ lastIndex = this._.bbcPartsRegex.lastIndex;
+
+ // "parts" is an array with the following items:
+ // 0 : The entire match for opening/closing tags and line-break;
+ // 1 : line-break;
+ // 2 : open of tag excludes option;
+ // 3 : tag option;
+ // 4 : close of tag;
+
+ part = ( parts[ 1 ] || parts[ 3 ] || '' ).toLowerCase();
+ // Unrecognized tags should be delivered as a simple text (#7860).
+ if ( part && !bbcodeMap[ part ] ) {
+ this.onText( parts[ 0 ] );
+ continue;
+ }
+
+ // Opening tag
+ if ( parts[ 1 ] ) {
+ var tagName = bbcodeMap[ part ],
+ attribs = {},
+ styles = {},
+ optionPart = parts[ 2 ];
+
+ if ( optionPart ) {
+ if ( part == 'list' ) {
+ if ( !isNaN( optionPart ) )
+ optionPart = 'decimal';
+ else if ( /^[a-z]+$/.test( optionPart ) )
+ optionPart = 'lower-alpha';
+ else if ( /^[A-Z]+$/.test( optionPart ) )
+ optionPart = 'upper-alpha';
+ }
+
+ if ( stylesMap[ part ] ) {
+ // Font size represents percentage.
+ if ( part == 'size' )
+ optionPart += '%';
+
+ styles[ stylesMap[ part ] ] = optionPart;
+ attribs.style = serializeStyleText( styles );
+ } else if ( attributesMap[ part ] )
+ attribs[ attributesMap[ part ] ] = optionPart;
+ }
+
+ // Two special handling - image and email, protect them
+ // as "span" with an attribute marker.
+ if ( part == 'email' || part == 'img' )
+ attribs[ 'bbcode' ] = part;
+
+ this.onTagOpen( tagName, attribs, CKEDITOR.dtd.$empty[ tagName ] );
+ }
+ // Closing tag
+ else if ( parts[ 3 ] )
+ this.onTagClose( bbcodeMap[ part ] );
+ }
+
+ if ( bbcode.length > lastIndex )
+ this.onText( bbcode.substring( lastIndex, bbcode.length ), 1 );
+ }
+ };
+
+ /**
+ * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.
+ *
+ * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '