Browse Source

SCEditor v 3.2.0

development
Visman 2 years ago
parent
commit
54901939bf
53 changed files with 15457 additions and 0 deletions
  1. 2646 0
      public/js/sc/formats/bbcode.js
  2. 1269 0
      public/js/sc/formats/xhtml.js
  3. 132 0
      public/js/sc/icons/material.js
  4. 112 0
      public/js/sc/icons/monocons.js
  5. 68 0
      public/js/sc/languages/ar.js
  6. 68 0
      public/js/sc/languages/ca.js
  7. 68 0
      public/js/sc/languages/cn.js
  8. 71 0
      public/js/sc/languages/cs.js
  9. 61 0
      public/js/sc/languages/de.js
  10. 68 0
      public/js/sc/languages/el.js
  11. 7 0
      public/js/sc/languages/en-US.js
  12. 12 0
      public/js/sc/languages/en.js
  13. 68 0
      public/js/sc/languages/es.js
  14. 57 0
      public/js/sc/languages/et.js
  15. 69 0
      public/js/sc/languages/fa.js
  16. 70 0
      public/js/sc/languages/fi.js
  17. 70 0
      public/js/sc/languages/fr.js
  18. 68 0
      public/js/sc/languages/gl.js
  19. 69 0
      public/js/sc/languages/hu.js
  20. 68 0
      public/js/sc/languages/id.js
  21. 72 0
      public/js/sc/languages/it.js
  22. 71 0
      public/js/sc/languages/ja.js
  23. 68 0
      public/js/sc/languages/lt.js
  24. 70 0
      public/js/sc/languages/nb.js
  25. 72 0
      public/js/sc/languages/nl.js
  26. 68 0
      public/js/sc/languages/pl.js
  27. 67 0
      public/js/sc/languages/pt-BR.js
  28. 69 0
      public/js/sc/languages/pt.js
  29. 60 0
      public/js/sc/languages/ru.js
  30. 70 0
      public/js/sc/languages/sk.js
  31. 58 0
      public/js/sc/languages/sv.js
  32. 80 0
      public/js/sc/languages/template.js
  33. 66 0
      public/js/sc/languages/tr.js
  34. 68 0
      public/js/sc/languages/tw.js
  35. 57 0
      public/js/sc/languages/uk.js
  36. 68 0
      public/js/sc/languages/vi.js
  37. 157 0
      public/js/sc/plugins/alternative-lists.js
  38. 110 0
      public/js/sc/plugins/autosave.js
  39. 106 0
      public/js/sc/plugins/autoyoutube.js
  40. 222 0
      public/js/sc/plugins/dragdrop.js
  41. 127 0
      public/js/sc/plugins/format.js
  42. 78 0
      public/js/sc/plugins/plaintext.js
  43. 372 0
      public/js/sc/plugins/undo.js
  44. 97 0
      public/js/sc/plugins/v1compat.js
  45. 4383 0
      public/js/sc/sceditor.js
  46. 85 0
      public/js/sc/themes/content/default.css
  47. 530 0
      public/js/sc/themes/default.css
  48. 548 0
      public/js/sc/themes/defaultdark.css
  49. BIN
      public/js/sc/themes/famfamfam.png
  50. 604 0
      public/js/sc/themes/modern.css
  51. 596 0
      public/js/sc/themes/office-toolbar.css
  52. 618 0
      public/js/sc/themes/office.css
  53. 619 0
      public/js/sc/themes/square.css

+ 2646 - 0
public/js/sc/formats/bbcode.js

@@ -0,0 +1,2646 @@
+/**
+ * SCEditor BBCode Plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @fileoverview SCEditor BBCode Format
+ * @author Sam Clarke
+ */
+(function (sceditor) {
+	/*eslint max-depth: off*/
+	'use strict';
+
+	var escapeEntities  = sceditor.escapeEntities;
+	var escapeUriScheme = sceditor.escapeUriScheme;
+	var dom             = sceditor.dom;
+	var utils           = sceditor.utils;
+
+	var css    = dom.css;
+	var attr   = dom.attr;
+	var is     = dom.is;
+	var extend = utils.extend;
+	var each   = utils.each;
+
+	var EMOTICON_DATA_ATTR = 'data-sceditor-emoticon';
+
+	var getEditorCommand = sceditor.command.get;
+
+	var QuoteType = {
+		/** @lends BBCodeParser.QuoteType */
+		/**
+		 * Always quote the attribute value
+		 * @type {Number}
+		 */
+		always: 1,
+
+		/**
+		 * Never quote the attributes value
+		 * @type {Number}
+		 */
+		never: 2,
+
+		/**
+		 * Only quote the attributes value when it contains spaces to equals
+		 * @type {Number}
+		 */
+		auto: 3
+	};
+
+	var defaultCommandsOverrides = {
+		bold: {
+			txtExec: ['[b]', '[/b]']
+		},
+		italic: {
+			txtExec: ['[i]', '[/i]']
+		},
+		underline: {
+			txtExec: ['[u]', '[/u]']
+		},
+		strike: {
+			txtExec: ['[s]', '[/s]']
+		},
+		subscript: {
+			txtExec: ['[sub]', '[/sub]']
+		},
+		superscript: {
+			txtExec: ['[sup]', '[/sup]']
+		},
+		left: {
+			txtExec: ['[left]', '[/left]']
+		},
+		center: {
+			txtExec: ['[center]', '[/center]']
+		},
+		right: {
+			txtExec: ['[right]', '[/right]']
+		},
+		justify: {
+			txtExec: ['[justify]', '[/justify]']
+		},
+		font: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('font')._dropDown(
+					editor,
+					caller,
+					function (fontName) {
+						editor.insertText(
+							'[font=' + fontName + ']',
+							'[/font]'
+						);
+					}
+				);
+			}
+		},
+		size: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('size')._dropDown(
+					editor,
+					caller,
+					function (fontSize) {
+						editor.insertText(
+							'[size=' + fontSize + ']',
+							'[/size]'
+						);
+					}
+				);
+			}
+		},
+		color: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('color')._dropDown(
+					editor,
+					caller,
+					function (color) {
+						editor.insertText(
+							'[color=' + color + ']',
+							'[/color]'
+						);
+					}
+				);
+			}
+		},
+		bulletlist: {
+			txtExec: function (caller, selected) {
+				this.insertText(
+					'[ul]\n[li]' +
+					selected.split(/\r?\n/).join('[/li]\n[li]') +
+					'[/li]\n[/ul]'
+				);
+			}
+		},
+		orderedlist: {
+			txtExec: function (caller, selected) {
+				this.insertText(
+					'[ol]\n[li]' +
+					selected.split(/\r?\n/).join('[/li]\n[li]') +
+					'[/li]\n[/ol]'
+				);
+			}
+		},
+		table: {
+			txtExec: ['[table][tr][td]', '[/td][/tr][/table]']
+		},
+		horizontalrule: {
+			txtExec: ['[hr]']
+		},
+		code: {
+			txtExec: ['[code]', '[/code]']
+		},
+		image: {
+			txtExec: function (caller, selected) {
+				var	editor  = this;
+
+				getEditorCommand('image')._dropDown(
+					editor,
+					caller,
+					selected,
+					function (url, width, height) {
+						var attrs  = '';
+
+						if (width) {
+							attrs += ' width=' + width;
+						}
+
+						if (height) {
+							attrs += ' height=' + height;
+						}
+
+						editor.insertText(
+							'[img' + attrs + ']' + url + '[/img]'
+						);
+					}
+				);
+			}
+		},
+		email: {
+			txtExec: function (caller, selected) {
+				var	editor  = this;
+
+				getEditorCommand('email')._dropDown(
+					editor,
+					caller,
+					function (url, text) {
+						editor.insertText(
+							'[email=' + url + ']' +
+								(text || selected || url) +
+							'[/email]'
+						);
+					}
+				);
+			}
+		},
+		link: {
+			txtExec: function (caller, selected) {
+				var	editor  = this;
+
+				getEditorCommand('link')._dropDown(
+					editor,
+					caller,
+					function (url, text) {
+						editor.insertText(
+							'[url=' + url + ']' +
+								(text || selected || url) +
+							'[/url]'
+						);
+					}
+				);
+			}
+		},
+		quote: {
+			txtExec: ['[quote]', '[/quote]']
+		},
+		youtube: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('youtube')._dropDown(
+					editor,
+					caller,
+					function (id) {
+						editor.insertText('[youtube]' + id + '[/youtube]');
+					}
+				);
+			}
+		},
+		rtl: {
+			txtExec: ['[rtl]', '[/rtl]']
+		},
+		ltr: {
+			txtExec: ['[ltr]', '[/ltr]']
+		}
+	};
+
+	var bbcodeHandlers = {
+		// START_COMMAND: Bold
+		b: {
+			tags: {
+				b: null,
+				strong: null
+			},
+			styles: {
+				// 401 is for FF 3.5
+				'font-weight': ['bold', 'bolder', '401', '700', '800', '900']
+			},
+			format: '[b]{0}[/b]',
+			html: '<strong>{0}</strong>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Italic
+		i: {
+			tags: {
+				i: null,
+				em: null
+			},
+			styles: {
+				'font-style': ['italic', 'oblique']
+			},
+			format: '[i]{0}[/i]',
+			html: '<em>{0}</em>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Underline
+		u: {
+			tags: {
+				u: null
+			},
+			styles: {
+				'text-decoration': ['underline']
+			},
+			format: '[u]{0}[/u]',
+			html: '<u>{0}</u>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Strikethrough
+		s: {
+			tags: {
+				s: null,
+				strike: null
+			},
+			styles: {
+				'text-decoration': ['line-through']
+			},
+			format: '[s]{0}[/s]',
+			html: '<s>{0}</s>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Subscript
+		sub: {
+			tags: {
+				sub: null
+			},
+			format: '[sub]{0}[/sub]',
+			html: '<sub>{0}</sub>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Superscript
+		sup: {
+			tags: {
+				sup: null
+			},
+			format: '[sup]{0}[/sup]',
+			html: '<sup>{0}</sup>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Font
+		font: {
+			tags: {
+				font: {
+					face: null
+				}
+			},
+			styles: {
+				'font-family': null
+			},
+			quoteType: QuoteType.never,
+			format: function (element, content) {
+				var font;
+
+				if (!is(element, 'font') || !(font = attr(element, 'face'))) {
+					font = css(element, 'font-family');
+				}
+
+				return '[font=' + _stripQuotes(font) + ']' +
+					content + '[/font]';
+			},
+			html: '<font face="{defaultattr}">{0}</font>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Size
+		size: {
+			tags: {
+				font: {
+					size: null
+				}
+			},
+			styles: {
+				'font-size': null
+			},
+			format: function (element, content) {
+				var	fontSize = attr(element, 'size'),
+					size     = 2;
+
+				if (!fontSize) {
+					fontSize = css(element, 'fontSize');
+				}
+
+				// Most browsers return px value but IE returns 1-7
+				if (fontSize.indexOf('px') > -1) {
+					// convert size to an int
+					fontSize = fontSize.replace('px', '') - 0;
+
+					if (fontSize < 12) {
+						size = 1;
+					}
+					if (fontSize > 15) {
+						size = 3;
+					}
+					if (fontSize > 17) {
+						size = 4;
+					}
+					if (fontSize > 23) {
+						size = 5;
+					}
+					if (fontSize > 31) {
+						size = 6;
+					}
+					if (fontSize > 47) {
+						size = 7;
+					}
+				} else {
+					size = fontSize;
+				}
+
+				return '[size=' + size + ']' + content + '[/size]';
+			},
+			html: '<font size="{defaultattr}">{!0}</font>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Color
+		color: {
+			tags: {
+				font: {
+					color: null
+				}
+			},
+			styles: {
+				color: null
+			},
+			quoteType: QuoteType.never,
+			format: function (elm, content) {
+				var	color;
+
+				if (!is(elm, 'font') || !(color = attr(elm, 'color'))) {
+					color = elm.style.color || css(elm, 'color');
+				}
+
+				return '[color=' + _normaliseColour(color) + ']' +
+					content + '[/color]';
+			},
+			html: function (token, attrs, content) {
+				return '<font color="' +
+					escapeEntities(_normaliseColour(attrs.defaultattr), true) +
+					'">' + content + '</font>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Lists
+		ul: {
+			tags: {
+				ul: null
+			},
+			breakStart: true,
+			isInline: false,
+			skipLastLineBreak: true,
+			format: '[ul]{0}[/ul]',
+			html: '<ul>{0}</ul>'
+		},
+		list: {
+			breakStart: true,
+			isInline: false,
+			skipLastLineBreak: true,
+			html: '<ul>{0}</ul>'
+		},
+		ol: {
+			tags: {
+				ol: null
+			},
+			breakStart: true,
+			isInline: false,
+			skipLastLineBreak: true,
+			format: '[ol]{0}[/ol]',
+			html: '<ol>{0}</ol>'
+		},
+		li: {
+			tags: {
+				li: null
+			},
+			isInline: false,
+			closedBy: ['/ul', '/ol', '/list', '*', 'li'],
+			format: '[li]{0}[/li]',
+			html: '<li>{0}</li>'
+		},
+		'*': {
+			isInline: false,
+			closedBy: ['/ul', '/ol', '/list', '*', 'li'],
+			html: '<li>{0}</li>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Table
+		table: {
+			tags: {
+				table: null
+			},
+			isInline: false,
+			isHtmlInline: true,
+			skipLastLineBreak: true,
+			format: '[table]{0}[/table]',
+			html: '<table>{0}</table>'
+		},
+		tr: {
+			tags: {
+				tr: null
+			},
+			isInline: false,
+			skipLastLineBreak: true,
+			format: '[tr]{0}[/tr]',
+			html: '<tr>{0}</tr>'
+		},
+		th: {
+			tags: {
+				th: null
+			},
+			allowsEmpty: true,
+			isInline: false,
+			format: '[th]{0}[/th]',
+			html: '<th>{0}</th>'
+		},
+		td: {
+			tags: {
+				td: null
+			},
+			allowsEmpty: true,
+			isInline: false,
+			format: '[td]{0}[/td]',
+			html: '<td>{0}</td>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Emoticons
+		emoticon: {
+			allowsEmpty: true,
+			tags: {
+				img: {
+					src: null,
+					'data-sceditor-emoticon': null
+				}
+			},
+			format: function (element, content) {
+				return attr(element, EMOTICON_DATA_ATTR) + content;
+			},
+			html: '{0}'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Horizontal Rule
+		hr: {
+			tags: {
+				hr: null
+			},
+			allowsEmpty: true,
+			isSelfClosing: true,
+			isInline: false,
+			format: '[hr]{0}',
+			html: '<hr />'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Image
+		img: {
+			allowsEmpty: true,
+			tags: {
+				img: {
+					src: null
+				}
+			},
+			allowedChildren: ['#'],
+			quoteType: QuoteType.never,
+			format: function (element, content) {
+				var	width, height,
+					attribs   = '',
+					style     = function (name) {
+						return element.style ? element.style[name] : null;
+					};
+
+				// check if this is an emoticon image
+				if (attr(element, EMOTICON_DATA_ATTR)) {
+					return content;
+				}
+
+				width = attr(element, 'width') || style('width');
+				height = attr(element, 'height') || style('height');
+
+				// only add width and height if one is specified
+				if ((element.complete && (width || height)) ||
+					(width && height)) {
+
+					attribs = '=' + dom.width(element) + 'x' +
+						dom.height(element);
+				}
+
+				return '[img' + attribs + ']' + attr(element, 'src') + '[/img]';
+			},
+			html: function (token, attrs, content) {
+				var	undef, width, height, match,
+					attribs = '';
+
+				// handle [img width=340 height=240]url[/img]
+				width  = attrs.width;
+				height = attrs.height;
+
+				// handle [img=340x240]url[/img]
+				if (attrs.defaultattr) {
+					match = attrs.defaultattr.split(/x/i);
+
+					width  = match[0];
+					height = (match.length === 2 ? match[1] : match[0]);
+				}
+
+				if (width !== undef) {
+					attribs += ' width="' + escapeEntities(width, true) + '"';
+				}
+
+				if (height !== undef) {
+					attribs += ' height="' + escapeEntities(height, true) + '"';
+				}
+
+				return '<img' + attribs +
+					' src="' + escapeUriScheme(content) + '" />';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: URL
+		url: {
+			allowsEmpty: true,
+			tags: {
+				a: {
+					href: null
+				}
+			},
+			quoteType: QuoteType.never,
+			format: function (element, content) {
+				var url = attr(element, 'href');
+
+				// make sure this link is not an e-mail,
+				// if it is return e-mail BBCode
+				if (url.substr(0, 7) === 'mailto:') {
+					return '[email="' + url.substr(7) + '"]' +
+						content + '[/email]';
+				}
+
+				return '[url=' + url + ']' + content + '[/url]';
+			},
+			html: function (token, attrs, content) {
+				attrs.defaultattr =
+					escapeEntities(attrs.defaultattr, true) || content;
+
+				return '<a href="' + escapeUriScheme(attrs.defaultattr) + '">' +
+					content + '</a>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: E-mail
+		email: {
+			quoteType: QuoteType.never,
+			html: function (token, attrs, content) {
+				return '<a href="mailto:' +
+					(escapeEntities(attrs.defaultattr, true) || content) +
+					'">' + content + '</a>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Quote
+		quote: {
+			tags: {
+				blockquote: null
+			},
+			isInline: false,
+			quoteType: QuoteType.never,
+			format: function (element, content) {
+				var authorAttr = 'data-author';
+				var	author = '';
+				var cite;
+				var children = element.children;
+
+				for (var i = 0; !cite && i < children.length; i++) {
+					if (is(children[i], 'cite')) {
+						cite = children[i];
+					}
+				}
+
+				if (cite || attr(element, authorAttr)) {
+					author = cite && cite.textContent ||
+						attr(element, authorAttr);
+
+					attr(element, authorAttr, author);
+
+					if (cite) {
+						element.removeChild(cite);
+					}
+
+					content	= this.elementToBbcode(element);
+					author  = '=' + author.replace(/(^\s+|\s+$)/g, '');
+
+					if (cite) {
+						element.insertBefore(cite, element.firstChild);
+					}
+				}
+
+				return '[quote' + author + ']' + content + '[/quote]';
+			},
+			html: function (token, attrs, content) {
+				if (attrs.defaultattr) {
+					content = '<cite>' + escapeEntities(attrs.defaultattr) +
+						'</cite>' + content;
+				}
+
+				return '<blockquote>' + content + '</blockquote>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Code
+		code: {
+			tags: {
+				code: null
+			},
+			isInline: false,
+			allowedChildren: ['#', '#newline'],
+			format: '[code]{0}[/code]',
+			html: '<code>{0}</code>'
+		},
+		// END_COMMAND
+
+
+		// START_COMMAND: Left
+		left: {
+			styles: {
+				'text-align': [
+					'left',
+					'-webkit-left',
+					'-moz-left',
+					'-khtml-left'
+				]
+			},
+			isInline: false,
+			allowsEmpty: true,
+			format: '[left]{0}[/left]',
+			html: '<div align="left">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Centre
+		center: {
+			styles: {
+				'text-align': [
+					'center',
+					'-webkit-center',
+					'-moz-center',
+					'-khtml-center'
+				]
+			},
+			isInline: false,
+			allowsEmpty: true,
+			format: '[center]{0}[/center]',
+			html: '<div align="center">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Right
+		right: {
+			styles: {
+				'text-align': [
+					'right',
+					'-webkit-right',
+					'-moz-right',
+					'-khtml-right'
+				]
+			},
+			isInline: false,
+			allowsEmpty: true,
+			format: '[right]{0}[/right]',
+			html: '<div align="right">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Justify
+		justify: {
+			styles: {
+				'text-align': [
+					'justify',
+					'-webkit-justify',
+					'-moz-justify',
+					'-khtml-justify'
+				]
+			},
+			isInline: false,
+			allowsEmpty: true,
+			format: '[justify]{0}[/justify]',
+			html: '<div align="justify">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: YouTube
+		youtube: {
+			allowsEmpty: true,
+			tags: {
+				iframe: {
+					'data-youtube-id': null
+				}
+			},
+			format: function (element, content) {
+				element = attr(element, 'data-youtube-id');
+
+				return element ? '[youtube]' + element + '[/youtube]' : content;
+			},
+			html: '<iframe width="560" height="315" frameborder="0" ' +
+				'src="https://www.youtube-nocookie.com/embed/{0}?wmode=opaque" ' +
+				'data-youtube-id="{0}" allowfullscreen></iframe>'
+		},
+		// END_COMMAND
+
+
+		// START_COMMAND: Rtl
+		rtl: {
+			styles: {
+				direction: ['rtl']
+			},
+			isInline: false,
+			format: '[rtl]{0}[/rtl]',
+			html: '<div style="direction: rtl">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Ltr
+		ltr: {
+			styles: {
+				direction: ['ltr']
+			},
+			isInline: false,
+			format: '[ltr]{0}[/ltr]',
+			html: '<div style="direction: ltr">{0}</div>'
+		},
+		// END_COMMAND
+
+		// this is here so that commands above can be removed
+		// without having to remove the , after the last one.
+		// Needed for IE.
+		ignore: {}
+	};
+
+	/**
+	 * Formats a string replacing {name} with the values of
+	 * obj.name properties.
+	 *
+	 * If there is no property for the specified {name} then
+	 * it will be left intact.
+	 *
+	 * @param  {string} str
+	 * @param  {Object} obj
+	 * @return {string}
+	 * @since 2.0.0
+	 */
+	function formatBBCodeString(str, obj) {
+		return str.replace(/\{([^}]+)\}/g, function (match, group) {
+			var	undef,
+				escape = true;
+
+			if (group.charAt(0) === '!') {
+				escape = false;
+				group = group.substring(1);
+			}
+
+			if (group === '0') {
+				escape = false;
+			}
+
+			if (obj[group] === undef) {
+				return match;
+			}
+
+			return escape ? escapeEntities(obj[group], true) : obj[group];
+		});
+	}
+
+	function isFunction(fn) {
+		return typeof fn === 'function';
+	}
+
+	/**
+	 * Removes any leading or trailing quotes ('")
+	 *
+	 * @return string
+	 * @since v1.4.0
+	 */
+	function _stripQuotes(str) {
+		return str ?
+			str.replace(/\\(.)/g, '$1').replace(/^(["'])(.*?)\1$/, '$2') : str;
+	}
+
+	/**
+	 * Formats a string replacing {0}, {1}, {2}, ect. with
+	 * the params provided
+	 *
+	 * @param {string} str The string to format
+	 * @param {...string} arg The strings to replace
+	 * @return {string}
+	 * @since v1.4.0
+	 */
+	function _formatString(str) {
+		var	undef;
+		var args = arguments;
+
+		return str.replace(/\{(\d+)\}/g, function (_, matchNum) {
+			return args[matchNum - 0 + 1] !== undef ?
+				args[matchNum - 0 + 1] :
+				'{' + matchNum + '}';
+		});
+	}
+
+	var TOKEN_OPEN = 'open';
+	var TOKEN_CONTENT = 'content';
+	var TOKEN_NEWLINE = 'newline';
+	var TOKEN_CLOSE = 'close';
+
+
+	/*
+	 * @typedef {Object} TokenizeToken
+	 * @property {string} type
+	 * @property {string} name
+	 * @property {string} val
+	 * @property {Object.<string, string>} attrs
+	 * @property {array} children
+	 * @property {TokenizeToken} closing
+	 */
+
+	/**
+	 * Tokenize token object
+	 *
+	 * @param  {string} type The type of token this is,
+	 *                       should be one of tokenType
+	 * @param  {string} name The name of this token
+	 * @param  {string} val The originally matched string
+	 * @param  {array} attrs Any attributes. Only set on
+	 *                       TOKEN_TYPE_OPEN tokens
+	 * @param  {array} children Any children of this token
+	 * @param  {TokenizeToken} closing This tokens closing tag.
+	 *                                 Only set on TOKEN_TYPE_OPEN tokens
+	 * @class {TokenizeToken}
+	 * @name {TokenizeToken}
+	 * @memberOf BBCodeParser.prototype
+	 */
+	// eslint-disable-next-line max-params
+	function TokenizeToken(type, name, val, attrs, children, closing) {
+		var base      = this;
+
+		base.type     = type;
+		base.name     = name;
+		base.val      = val;
+		base.attrs    = attrs || {};
+		base.children = children || [];
+		base.closing  = closing || null;
+	};
+
+	TokenizeToken.prototype = {
+		/** @lends BBCodeParser.prototype.TokenizeToken */
+		/**
+		 * Clones this token
+		 *
+		 * @return {TokenizeToken}
+		 */
+		clone: function () {
+			var base = this;
+
+			return new TokenizeToken(
+				base.type,
+				base.name,
+				base.val,
+				extend({}, base.attrs),
+				[],
+				base.closing ? base.closing.clone() : null
+			);
+		},
+		/**
+		 * Splits this token at the specified child
+		 *
+		 * @param  {TokenizeToken} splitAt The child to split at
+		 * @return {TokenizeToken} The right half of the split token or
+		 *                         empty clone if invalid splitAt lcoation
+		 */
+		splitAt: function (splitAt) {
+			var offsetLength;
+			var base         = this;
+			var	clone        = base.clone();
+			var offset       = base.children.indexOf(splitAt);
+
+			if (offset > -1) {
+				// Work out how many items are on the right side of the split
+				// to pass to splice()
+				offsetLength   = base.children.length - offset;
+				clone.children = base.children.splice(offset, offsetLength);
+			}
+
+			return clone;
+		}
+	};
+
+
+	/**
+	 * SCEditor BBCode parser class
+	 *
+	 * @param {Object} options
+	 * @class BBCodeParser
+	 * @name BBCodeParser
+	 * @since v1.4.0
+	 */
+	function BBCodeParser(options) {
+		var base = this;
+
+		base.opts = extend({}, BBCodeParser.defaults, options);
+
+		/**
+		 * Takes a BBCode string and splits it into open,
+		 * content and close tags.
+		 *
+		 * It does no checking to verify a tag has a matching open
+		 * or closing tag or if the tag is valid child of any tag
+		 * before it. For that the tokens should be passed to the
+		 * parse function.
+		 *
+		 * @param {string} str
+		 * @return {array}
+		 * @memberOf BBCodeParser.prototype
+		 */
+		base.tokenize = function (str) {
+			var	matches, type, i;
+			var tokens = [];
+			// The token types in reverse order of precedence
+			// (they're looped in reverse)
+			var tokenTypes = [
+				{
+					type: TOKEN_CONTENT,
+					regex: /^([^\[\r\n]+|\[)/
+				},
+				{
+					type: TOKEN_NEWLINE,
+					regex: /^(\r\n|\r|\n)/
+				},
+				{
+					type: TOKEN_OPEN,
+					regex: /^\[[^\[\]]+\]/
+				},
+				// Close must come before open as they are
+				// the same except close has a / at the start.
+				{
+					type: TOKEN_CLOSE,
+					regex: /^\[\/[^\[\]]+\]/
+				}
+			];
+
+			strloop:
+			while (str.length) {
+				i = tokenTypes.length;
+				while (i--) {
+					type = tokenTypes[i].type;
+
+					// Check if the string matches any of the tokens
+					if (!(matches = str.match(tokenTypes[i].regex)) ||
+						!matches[0]) {
+						continue;
+					}
+
+					// Add the match to the tokens list
+					tokens.push(tokenizeTag(type, matches[0]));
+
+					// Remove the match from the string
+					str = str.substr(matches[0].length);
+
+					// The token has been added so start again
+					continue strloop;
+				}
+
+				// If there is anything left in the string which doesn't match
+				// any of the tokens then just assume it's content and add it.
+				if (str.length) {
+					tokens.push(tokenizeTag(TOKEN_CONTENT, str));
+				}
+
+				str = '';
+			}
+
+			return tokens;
+		};
+
+		/**
+		 * Extracts the name an params from a tag
+		 *
+		 * @param {string} type
+		 * @param {string} val
+		 * @return {Object}
+		 * @private
+		 */
+		function tokenizeTag(type, val) {
+			var matches, attrs, name,
+				openRegex  = /\[([^\]\s=]+)(?:([^\]]+))?\]/,
+				closeRegex = /\[\/([^\[\]]+)\]/;
+
+			// Extract the name and attributes from opening tags and
+			// just the name from closing tags.
+			if (type === TOKEN_OPEN && (matches = val.match(openRegex))) {
+				name = lower(matches[1]);
+
+				if (matches[2] && (matches[2] = matches[2].trim())) {
+					attrs = tokenizeAttrs(matches[2]);
+				}
+			}
+
+			if (type === TOKEN_CLOSE &&
+				(matches = val.match(closeRegex))) {
+				name = lower(matches[1]);
+			}
+
+			if (type === TOKEN_NEWLINE) {
+				name = '#newline';
+			}
+
+			// Treat all tokens without a name and
+			// all unknown BBCodes as content
+			if (!name || ((type === TOKEN_OPEN || type === TOKEN_CLOSE) &&
+				!bbcodeHandlers[name])) {
+
+				type = TOKEN_CONTENT;
+				name = '#';
+			}
+
+			return new TokenizeToken(type, name, val, attrs);
+		}
+
+		/**
+		 * Extracts the individual attributes from a string containing
+		 * all the attributes.
+		 *
+		 * @param {string} attrs
+		 * @return {Object} Assoc array of attributes
+		 * @private
+		 */
+		function tokenizeAttrs(attrs) {
+			var	matches,
+				/*
+				([^\s=]+)				Anything that's not a space or equals
+				=						Equals sign =
+				(?:
+					(?:
+						(["'])					The opening quote
+						(
+							(?:\\\2|[^\2])*?	Anything that isn't the
+												unescaped opening quote
+						)
+						\2						The opening quote again which
+												will close the string
+					)
+						|				If not a quoted string then match
+					(
+						(?:.(?!\s\S+=))*.?		Anything that isn't part of
+												[space][non-space][=] which
+												would be a new attribute
+					)
+				)
+				*/
+				attrRegex = /([^\s=]+)=(?:(?:(["'])((?:\\\2|[^\2])*?)\2)|((?:.(?!\s\S+=))*.))/g,
+				ret       = {};
+
+			// if only one attribute then remove the = from the start and
+			// strip any quotes
+			if (attrs.charAt(0) === '=' && attrs.indexOf('=', 1) < 0) {
+				ret.defaultattr = _stripQuotes(attrs.substr(1));
+			} else {
+				if (attrs.charAt(0) === '=') {
+					attrs = 'defaultattr' + attrs;
+				}
+
+				// No need to strip quotes here, the regex will do that.
+				while ((matches = attrRegex.exec(attrs))) {
+					ret[lower(matches[1])] =
+						_stripQuotes(matches[3]) || matches[4];
+				}
+			}
+
+			return ret;
+		}
+
+		/**
+		 * Parses a string into an array of BBCodes
+		 *
+		 * @param  {string}  str
+		 * @param  {boolean} preserveNewLines If to preserve all new lines, not
+		 *                                    strip any based on the passed
+		 *                                    formatting options
+		 * @return {array}                    Array of BBCode objects
+		 * @memberOf BBCodeParser.prototype
+		 */
+		base.parse = function (str, preserveNewLines) {
+			var ret  = parseTokens(base.tokenize(str));
+			var opts = base.opts;
+
+			if (opts.fixInvalidNesting) {
+				fixNesting(ret);
+			}
+
+			normaliseNewLines(ret, null, preserveNewLines);
+
+			if (opts.removeEmptyTags) {
+				removeEmpty(ret);
+			}
+
+			return ret;
+		};
+
+		/**
+		 * Checks if an array of TokenizeToken's contains the
+		 * specified token.
+		 *
+		 * Checks the tokens name and type match another tokens
+		 * name and type in the array.
+		 *
+		 * @param  {string}    name
+		 * @param  {string} type
+		 * @param  {array}     arr
+		 * @return {Boolean}
+		 * @private
+		 */
+		function hasTag(name, type, arr) {
+			var i = arr.length;
+
+			while (i--) {
+				if (arr[i].type === type && arr[i].name === name) {
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+		/**
+		 * Checks if the child tag is allowed as one
+		 * of the parent tags children.
+		 *
+		 * @param  {TokenizeToken}  parent
+		 * @param  {TokenizeToken}  child
+		 * @return {Boolean}
+		 * @private
+		 */
+		function isChildAllowed(parent, child) {
+			var	parentBBCode    = parent ? bbcodeHandlers[parent.name] : {},
+				allowedChildren = parentBBCode.allowedChildren;
+
+			if (base.opts.fixInvalidChildren && allowedChildren) {
+				return allowedChildren.indexOf(child.name || '#') > -1;
+			}
+
+			return true;
+		}
+
+		// TODO: Tidy this parseTokens() function up a bit.
+		/**
+		 * Parses an array of tokens created by tokenize()
+		 *
+		 * @param  {array} toks
+		 * @return {array} Parsed tokens
+		 * @see tokenize()
+		 * @private
+		 */
+		function parseTokens(toks) {
+			var	token, bbcode, curTok, clone, i, next,
+				cloned     = [],
+				output     = [],
+				openTags   = [],
+				/**
+				 * Returns the currently open tag or undefined
+				 * @return {TokenizeToken}
+				 */
+				currentTag = function () {
+					return last(openTags);
+				},
+				/**
+				 * Adds a tag to either the current tags children
+				 * or to the output array.
+				 * @param {TokenizeToken} token
+				 * @private
+				 */
+				addTag = function (token) {
+					if (currentTag()) {
+						currentTag().children.push(token);
+					} else {
+						output.push(token);
+					}
+				},
+				/**
+				 * Checks if this tag closes the current tag
+				 * @param  {string} name
+				 * @return {Void}
+				 */
+				closesCurrentTag = function (name) {
+					return currentTag() &&
+						(bbcode = bbcodeHandlers[currentTag().name]) &&
+						bbcode.closedBy &&
+						bbcode.closedBy.indexOf(name) > -1;
+				};
+
+			while ((token = toks.shift())) {
+				next = toks[0];
+
+				/*
+				 * Fixes any invalid children.
+				 *
+				 * If it is an element which isn't allowed as a child of it's
+				 * parent then it will be converted to content of the parent
+				 * element. i.e.
+				 *     [code]Code [b]only[/b] allows text.[/code]
+				 * Will become:
+				 *     <code>Code [b]only[/b] allows text.</code>
+				 * Instead of:
+				 *     <code>Code <b>only</b> allows text.</code>
+				 */
+				// Ignore tags that can't be children
+				if (!isChildAllowed(currentTag(), token)) {
+
+					// exclude closing tags of current tag
+					if (token.type !== TOKEN_CLOSE || !currentTag() ||
+							token.name !== currentTag().name) {
+						token.name = '#';
+						token.type = TOKEN_CONTENT;
+					}
+				}
+
+				switch (token.type) {
+					case TOKEN_OPEN:
+						// Check it this closes a parent,
+						// e.g. for lists [*]one [*]two
+						if (closesCurrentTag(token.name)) {
+							openTags.pop();
+						}
+
+						addTag(token);
+						bbcode = bbcodeHandlers[token.name];
+
+						// If this tag is not self closing and it has a closing
+						// tag then it is open and has children so add it to the
+						// list of open tags. If has the closedBy property then
+						// it is closed by other tags so include everything as
+						// it's children until one of those tags is reached.
+						if (bbcode && !bbcode.isSelfClosing &&
+							(bbcode.closedBy ||
+								hasTag(token.name, TOKEN_CLOSE, toks))) {
+							openTags.push(token);
+						} else if (!bbcode || !bbcode.isSelfClosing) {
+							token.type = TOKEN_CONTENT;
+						}
+						break;
+
+					case TOKEN_CLOSE:
+						// check if this closes the current tag,
+						// e.g. [/list] would close an open [*]
+						if (currentTag() && token.name !== currentTag().name &&
+							closesCurrentTag('/' + token.name)) {
+
+							openTags.pop();
+						}
+
+						// If this is closing the currently open tag just pop
+						// the close tag off the open tags array
+						if (currentTag() && token.name === currentTag().name) {
+							currentTag().closing = token;
+							openTags.pop();
+
+						// If this is closing an open tag that is the parent of
+						// the current tag then clone all the tags including the
+						// current one until reaching the parent that is being
+						// closed. Close the parent and then add the clones back
+						// in.
+						} else if (hasTag(token.name, TOKEN_OPEN, openTags)) {
+
+							// Remove the tag from the open tags
+							while ((curTok = openTags.pop())) {
+
+								// If it's the tag that is being closed then
+								// discard it and break the loop.
+								if (curTok.name === token.name) {
+									curTok.closing = token;
+									break;
+								}
+
+								// Otherwise clone this tag and then add any
+								// previously cloned tags as it's children
+								clone = curTok.clone();
+
+								if (cloned.length) {
+									clone.children.push(last(cloned));
+								}
+
+								cloned.push(clone);
+							}
+
+							// Place block linebreak before cloned tags
+							if (next && next.type === TOKEN_NEWLINE) {
+								bbcode = bbcodeHandlers[token.name];
+								if (bbcode && bbcode.isInline === false) {
+									addTag(next);
+									toks.shift();
+								}
+							}
+
+							// Add the last cloned child to the now current tag
+							// (the parent of the tag which was being closed)
+							addTag(last(cloned));
+
+							// Add all the cloned tags to the open tags list
+							i = cloned.length;
+							while (i--) {
+								openTags.push(cloned[i]);
+							}
+
+							cloned.length = 0;
+
+						// This tag is closing nothing so treat it as content
+						} else {
+							token.type = TOKEN_CONTENT;
+							addTag(token);
+						}
+						break;
+
+					case TOKEN_NEWLINE:
+						// handle things like
+						//     [*]list\nitem\n[*]list1
+						// where it should come out as
+						//     [*]list\nitem[/*]\n[*]list1[/*]
+						// instead of
+						//     [*]list\nitem\n[/*][*]list1[/*]
+						if (currentTag() && next && closesCurrentTag(
+							(next.type === TOKEN_CLOSE ? '/' : '') +
+							next.name
+						)) {
+							// skip if the next tag is the closing tag for
+							// the option tag, i.e. [/*]
+							if (!(next.type === TOKEN_CLOSE &&
+								next.name === currentTag().name)) {
+								bbcode = bbcodeHandlers[currentTag().name];
+
+								if (bbcode && bbcode.breakAfter) {
+									openTags.pop();
+								} else if (bbcode &&
+									bbcode.isInline === false &&
+									base.opts.breakAfterBlock &&
+									bbcode.breakAfter !== false) {
+									openTags.pop();
+								}
+							}
+						}
+
+						addTag(token);
+						break;
+
+					default: // content
+						addTag(token);
+						break;
+				}
+			}
+
+			return output;
+		}
+
+		/**
+		 * Normalise all new lines
+		 *
+		 * Removes any formatting new lines from the BBCode
+		 * leaving only content ones. I.e. for a list:
+		 *
+		 * [list]
+		 * [*] list item one
+		 * with a line break
+		 * [*] list item two
+		 * [/list]
+		 *
+		 * would become
+		 *
+		 * [list] [*] list item one
+		 * with a line break [*] list item two [/list]
+		 *
+		 * Which makes it easier to convert to HTML or add
+		 * the formatting new lines back in when converting
+		 * back to BBCode
+		 *
+		 * @param  {array} children
+		 * @param  {TokenizeToken} parent
+		 * @param  {boolean} onlyRemoveBreakAfter
+		 * @return {void}
+		 */
+		function normaliseNewLines(children, parent, onlyRemoveBreakAfter) {
+			var	token, left, right, parentBBCode, bbcode,
+				removedBreakEnd, removedBreakBefore, remove;
+			var childrenLength = children.length;
+			// TODO: this function really needs tidying up
+			if (parent) {
+				parentBBCode = bbcodeHandlers[parent.name];
+			}
+
+			var i = childrenLength;
+			while (i--) {
+				if (!(token = children[i])) {
+					continue;
+				}
+
+				if (token.type === TOKEN_NEWLINE) {
+					left   = i > 0 ? children[i - 1] : null;
+					right  = i < childrenLength - 1 ? children[i + 1] : null;
+					remove = false;
+
+					// Handle the start and end new lines
+					// e.g. [tag]\n and \n[/tag]
+					if (!onlyRemoveBreakAfter && parentBBCode &&
+						parentBBCode.isSelfClosing !== true) {
+						// First child of parent so must be opening line break
+						// (breakStartBlock, breakStart) e.g. [tag]\n
+						if (!left) {
+							if (parentBBCode.isInline === false &&
+								base.opts.breakStartBlock &&
+								parentBBCode.breakStart !== false) {
+								remove = true;
+							}
+
+							if (parentBBCode.breakStart) {
+								remove = true;
+							}
+						// Last child of parent so must be end line break
+						// (breakEndBlock, breakEnd)
+						// e.g. \n[/tag]
+						// remove last line break (breakEndBlock, breakEnd)
+						} else if (!removedBreakEnd && !right) {
+							if (parentBBCode.isInline === false &&
+								base.opts.breakEndBlock &&
+								parentBBCode.breakEnd !== false) {
+								remove = true;
+							}
+
+							if (parentBBCode.breakEnd) {
+								remove = true;
+							}
+
+							removedBreakEnd = remove;
+						}
+					}
+
+					if (left && left.type === TOKEN_OPEN) {
+						if ((bbcode = bbcodeHandlers[left.name])) {
+							if (!onlyRemoveBreakAfter) {
+								if (bbcode.isInline === false &&
+									base.opts.breakAfterBlock &&
+									bbcode.breakAfter !== false) {
+									remove = true;
+								}
+
+								if (bbcode.breakAfter) {
+									remove = true;
+								}
+							} else if (bbcode.isInline === false) {
+								remove = true;
+							}
+						}
+					}
+
+					if (!onlyRemoveBreakAfter && !removedBreakBefore &&
+						right && right.type === TOKEN_OPEN) {
+
+						if ((bbcode = bbcodeHandlers[right.name])) {
+							if (bbcode.isInline === false &&
+								base.opts.breakBeforeBlock &&
+								bbcode.breakBefore !== false) {
+								remove = true;
+							}
+
+							if (bbcode.breakBefore) {
+								remove = true;
+							}
+
+							removedBreakBefore = remove;
+
+							if (remove) {
+								children.splice(i, 1);
+								continue;
+							}
+						}
+					}
+
+					if (remove) {
+						children.splice(i, 1);
+					}
+
+					// reset double removedBreakBefore removal protection.
+					// This is needed for cases like \n\n[\tag] where
+					// only 1 \n should be removed but without this they both
+					// would be.
+					removedBreakBefore = false;
+				} else if (token.type === TOKEN_OPEN) {
+					normaliseNewLines(token.children, token,
+						onlyRemoveBreakAfter);
+				}
+			}
+		}
+
+		/**
+		 * Fixes any invalid nesting.
+		 *
+		 * If it is a block level element inside 1 or more inline elements
+		 * then those inline elements will be split at the point where the
+		 * block level is and the block level element placed between the split
+		 * parts. i.e.
+		 *     [inline]A[blocklevel]B[/blocklevel]C[/inline]
+		 * Will become:
+		 *     [inline]A[/inline][blocklevel]B[/blocklevel][inline]C[/inline]
+		 *
+		 * @param {array} children
+		 * @param {array} [parents] Null if there is no parents
+		 * @param {boolea} [insideInline] If inside an inline element
+		 * @param {array} [rootArr] Root array if there is one
+		 * @return {array}
+		 * @private
+		 */
+		function fixNesting(children, parents, insideInline, rootArr) {
+			var	token, i, parent, parentIndex, parentParentChildren, right;
+
+			var isInline = function (token) {
+				var bbcode = bbcodeHandlers[token.name];
+
+				return !bbcode || bbcode.isInline !== false;
+			};
+
+			parents = parents || [];
+			rootArr = rootArr || children;
+
+			// This must check the length each time as it can change when
+			// tokens are moved to fix the nesting.
+			for (i = 0; i < children.length; i++) {
+				if (!(token = children[i]) || token.type !== TOKEN_OPEN) {
+					continue;
+				}
+
+				if (insideInline && !isInline(token)) {
+					// if this is a blocklevel element inside an inline one then
+					// split the parent at the block level element
+					parent = last(parents);
+					right  = parent.splitAt(token);
+
+					parentParentChildren = parents.length > 1 ?
+						parents[parents.length - 2].children : rootArr;
+
+					// If parent inline is allowed inside this tag, clone it and
+					// wrap this tags children in it.
+					if (isChildAllowed(token, parent)) {
+						var clone = parent.clone();
+						clone.children = token.children;
+						token.children = [clone];
+					}
+
+					parentIndex = parentParentChildren.indexOf(parent);
+					if (parentIndex > -1) {
+						// remove the block level token from the right side of
+						// the split inline element
+						right.children.splice(0, 1);
+
+						// insert the block level token and the right side after
+						// the left side of the inline token
+						parentParentChildren.splice(
+							parentIndex + 1, 0, token, right
+						);
+
+						// If token is a block and is followed by a newline,
+						// then move the newline along with it to the new parent
+						var next = right.children[0];
+						if (next && next.type === TOKEN_NEWLINE) {
+							if (!isInline(token)) {
+								right.children.splice(0, 1);
+								parentParentChildren.splice(
+									parentIndex + 2, 0, next
+								);
+							}
+						}
+
+						// return to parents loop as the
+						// children have now increased
+						return;
+					}
+
+				}
+
+				parents.push(token);
+
+				fixNesting(
+					token.children,
+					parents,
+					insideInline || isInline(token),
+					rootArr
+				);
+
+				parents.pop();
+			}
+		}
+
+		/**
+		 * Removes any empty BBCodes which are not allowed to be empty.
+		 *
+		 * @param {array} tokens
+		 * @private
+		 */
+		function removeEmpty(tokens) {
+			var	token, bbcode;
+
+			/**
+			 * Checks if all children are whitespace or not
+			 * @private
+			 */
+			var isTokenWhiteSpace = function (children) {
+				var j = children.length;
+
+				while (j--) {
+					var type = children[j].type;
+
+					if (type === TOKEN_OPEN || type === TOKEN_CLOSE) {
+						return false;
+					}
+
+					if (type === TOKEN_CONTENT &&
+						/\S|\u00A0/.test(children[j].val)) {
+						return false;
+					}
+				}
+
+				return true;
+			};
+
+			var i = tokens.length;
+			while (i--) {
+				// So skip anything that isn't a tag since only tags can be
+				// empty, content can't
+				if (!(token = tokens[i]) || token.type !== TOKEN_OPEN) {
+					continue;
+				}
+
+				bbcode = bbcodeHandlers[token.name];
+
+				// Remove any empty children of this tag first so that if they
+				// are all removed this one doesn't think it's not empty.
+				removeEmpty(token.children);
+
+				if (isTokenWhiteSpace(token.children) && bbcode &&
+					!bbcode.isSelfClosing && !bbcode.allowsEmpty) {
+					tokens.splice.apply(tokens, [i, 1].concat(token.children));
+				}
+			}
+		}
+
+		/**
+		 * Converts a BBCode string to HTML
+		 *
+		 * @param {string} str
+		 * @param {boolean}   preserveNewLines If to preserve all new lines, not
+		 *                                  strip any based on the passed
+		 *                                  formatting options
+		 * @return {string}
+		 * @memberOf BBCodeParser.prototype
+		 */
+		base.toHTML = function (str, preserveNewLines) {
+			return convertToHTML(base.parse(str, preserveNewLines), true);
+		};
+
+		base.toHTMLFragment = function (str, preserveNewLines) {
+			return convertToHTML(base.parse(str, preserveNewLines), false);
+		};
+
+		/**
+		 * @private
+		 */
+		function convertToHTML(tokens, isRoot) {
+			var	undef, token, bbcode, content, html, needsBlockWrap,
+				blockWrapOpen, isInline, lastChild,
+				ret = '';
+
+			isInline = function (bbcode) {
+				return (!bbcode || (bbcode.isHtmlInline !== undef ?
+					bbcode.isHtmlInline : bbcode.isInline)) !== false;
+			};
+
+			while (tokens.length > 0) {
+				if (!(token = tokens.shift())) {
+					continue;
+				}
+
+				if (token.type === TOKEN_OPEN) {
+					lastChild = token.children[token.children.length - 1] || {};
+					bbcode = bbcodeHandlers[token.name];
+					needsBlockWrap = isRoot && isInline(bbcode);
+					content = convertToHTML(token.children, false);
+
+					if (bbcode && bbcode.html) {
+						// Only add a line break to the end if this is
+						// blocklevel and the last child wasn't block-level
+						if (!isInline(bbcode) &&
+							isInline(bbcodeHandlers[lastChild.name]) &&
+							!bbcode.isPreFormatted &&
+							!bbcode.skipLastLineBreak) {
+							// Add placeholder br to end of block level
+							// elements
+							content += '<br />';
+						}
+
+						if (!isFunction(bbcode.html)) {
+							token.attrs['0'] = content;
+							html = formatBBCodeString(
+								bbcode.html,
+								token.attrs
+							);
+						} else {
+							html = bbcode.html.call(
+								base,
+								token,
+								token.attrs,
+								content
+							);
+						}
+					} else {
+						html = token.val + content +
+							(token.closing ? token.closing.val : '');
+					}
+				} else if (token.type === TOKEN_NEWLINE) {
+					if (!isRoot) {
+						ret += '<br />';
+						continue;
+					}
+
+					// If not already in a block wrap then start a new block
+					if (!blockWrapOpen) {
+						ret += '<div>';
+					}
+
+					ret += '<br />';
+
+					// Normally the div acts as a line-break with by moving
+					// whatever comes after onto a new line.
+					// If this is the last token, add an extra line-break so it
+					// shows as there will be nothing after it.
+					if (!tokens.length) {
+						ret += '<br />';
+					}
+
+					ret += '</div>\n';
+					blockWrapOpen = false;
+					continue;
+				// content
+				} else {
+					needsBlockWrap = isRoot;
+					html           = escapeEntities(token.val, true);
+				}
+
+				if (needsBlockWrap && !blockWrapOpen) {
+					ret += '<div>';
+					blockWrapOpen = true;
+				} else if (!needsBlockWrap && blockWrapOpen) {
+					ret += '</div>\n';
+					blockWrapOpen = false;
+				}
+
+				ret += html;
+			}
+
+			if (blockWrapOpen) {
+				ret += '</div>\n';
+			}
+
+			return ret;
+		}
+
+		/**
+		 * Takes a BBCode string, parses it then converts it back to BBCode.
+		 *
+		 * This will auto fix the BBCode and format it with the specified
+		 * options.
+		 *
+		 * @param {string} str
+		 * @param {boolean} preserveNewLines If to preserve all new lines, not
+		 *                                strip any based on the passed
+		 *                                formatting options
+		 * @return {string}
+		 * @memberOf BBCodeParser.prototype
+		 */
+		base.toBBCode = function (str, preserveNewLines) {
+			return convertToBBCode(base.parse(str, preserveNewLines));
+		};
+
+		/**
+		 * Converts parsed tokens back into BBCode with the
+		 * formatting specified in the options and with any
+		 * fixes specified.
+		 *
+		 * @param  {array} toks Array of parsed tokens from base.parse()
+		 * @return {string}
+		 * @private
+		 */
+		function convertToBBCode(toks) {
+			var	token, attr, bbcode, isBlock, isSelfClosing, quoteType,
+				breakBefore, breakStart, breakEnd, breakAfter,
+				ret = '';
+
+			while (toks.length > 0) {
+				if (!(token = toks.shift())) {
+					continue;
+				}
+				// TODO: tidy this
+				bbcode        = bbcodeHandlers[token.name];
+				isBlock       = !(!bbcode || bbcode.isInline !== false);
+				isSelfClosing = bbcode && bbcode.isSelfClosing;
+
+				breakBefore = (isBlock && base.opts.breakBeforeBlock &&
+						bbcode.breakBefore !== false) ||
+					(bbcode && bbcode.breakBefore);
+
+				breakStart = (isBlock && !isSelfClosing &&
+						base.opts.breakStartBlock &&
+						bbcode.breakStart !== false) ||
+					(bbcode && bbcode.breakStart);
+
+				breakEnd = (isBlock && base.opts.breakEndBlock &&
+						bbcode.breakEnd !== false) ||
+					(bbcode && bbcode.breakEnd);
+
+				breakAfter = (isBlock && base.opts.breakAfterBlock &&
+						bbcode.breakAfter !== false) ||
+					(bbcode && bbcode.breakAfter);
+
+				quoteType = (bbcode ? bbcode.quoteType : null) ||
+					base.opts.quoteType || QuoteType.auto;
+
+				if (!bbcode && token.type === TOKEN_OPEN) {
+					ret += token.val;
+
+					if (token.children) {
+						ret += convertToBBCode(token.children);
+					}
+
+					if (token.closing) {
+						ret += token.closing.val;
+					}
+				} else if (token.type === TOKEN_OPEN) {
+					if (breakBefore) {
+						ret += '\n';
+					}
+
+					// Convert the tag and it's attributes to BBCode
+					ret += '[' + token.name;
+					if (token.attrs) {
+						if (token.attrs.defaultattr) {
+							ret += '=' + quote(
+								token.attrs.defaultattr,
+								quoteType,
+								'defaultattr'
+							);
+
+							delete token.attrs.defaultattr;
+						}
+
+						for (attr in token.attrs) {
+							if (token.attrs.hasOwnProperty(attr)) {
+								ret += ' ' + attr + '=' +
+									quote(token.attrs[attr], quoteType, attr);
+							}
+						}
+					}
+					ret += ']';
+
+					if (breakStart) {
+						ret += '\n';
+					}
+
+					// Convert the tags children to BBCode
+					if (token.children) {
+						ret += convertToBBCode(token.children);
+					}
+
+					// add closing tag if not self closing
+					if (!isSelfClosing && !bbcode.excludeClosing) {
+						if (breakEnd) {
+							ret += '\n';
+						}
+
+						ret += '[/' + token.name + ']';
+					}
+
+					if (breakAfter) {
+						ret += '\n';
+					}
+
+					// preserve whatever was recognized as the
+					// closing tag if it is a self closing tag
+					if (token.closing && isSelfClosing) {
+						ret += token.closing.val;
+					}
+				} else {
+					ret += token.val;
+				}
+			}
+
+			return ret;
+		}
+
+		/**
+		 * Quotes an attribute
+		 *
+		 * @param {string} str
+		 * @param {BBCodeParser.QuoteType} quoteType
+		 * @param {string} name
+		 * @return {string}
+		 * @private
+		 */
+		function quote(str, quoteType, name) {
+			var	needsQuotes = /\s|=/.test(str);
+
+			if (isFunction(quoteType)) {
+				return quoteType(str, name);
+			}
+
+			if (quoteType === QuoteType.never ||
+				(quoteType === QuoteType.auto && !needsQuotes)) {
+				return str;
+			}
+
+			return '"' + str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
+		}
+
+		/**
+		 * Returns the last element of an array or null
+		 *
+		 * @param {array} arr
+		 * @return {Object} Last element
+		 * @private
+		 */
+		function last(arr) {
+			if (arr.length) {
+				return arr[arr.length - 1];
+			}
+
+			return null;
+		}
+
+		/**
+		 * Converts a string to lowercase.
+		 *
+		 * @param {string} str
+		 * @return {string} Lowercase version of str
+		 * @private
+		 */
+		function lower(str) {
+			return str.toLowerCase();
+		}
+	};
+
+	/**
+	 * Quote type
+	 * @type {Object}
+	 * @class QuoteType
+	 * @name BBCodeParser.QuoteType
+	 * @since 1.4.0
+	 */
+	BBCodeParser.QuoteType = QuoteType;
+
+	/**
+	 * Default BBCode parser options
+	 * @type {Object}
+	 */
+	BBCodeParser.defaults = {
+		/**
+		 * If to add a new line before block level elements
+		 *
+		 * @type {Boolean}
+		 */
+		breakBeforeBlock: false,
+
+		/**
+		 * If to add a new line after the start of block level elements
+		 *
+		 * @type {Boolean}
+		 */
+		breakStartBlock: false,
+
+		/**
+		 * If to add a new line before the end of block level elements
+		 *
+		 * @type {Boolean}
+		 */
+		breakEndBlock: false,
+
+		/**
+		 * If to add a new line after block level elements
+		 *
+		 * @type {Boolean}
+		 */
+		breakAfterBlock: true,
+
+		/**
+		 * If to remove empty tags
+		 *
+		 * @type {Boolean}
+		 */
+		removeEmptyTags: true,
+
+		/**
+		 * If to fix invalid nesting,
+		 * i.e. block level elements inside inline elements.
+		 *
+		 * @type {Boolean}
+		 */
+		fixInvalidNesting: true,
+
+		/**
+		 * If to fix invalid children.
+		 * i.e. A tag which is inside a parent that doesn't
+		 * allow that type of tag.
+		 *
+		 * @type {Boolean}
+		 */
+		fixInvalidChildren: true,
+
+		/**
+		 * Attribute quote type
+		 *
+		 * @type {BBCodeParser.QuoteType}
+		 * @since 1.4.1
+		 */
+		quoteType: QuoteType.auto,
+
+		/**
+		 * Whether to use strict matching on attributes and styles.
+		 *
+		 * When true this will perform AND matching requiring all tag
+		 * attributes and styles to match.
+		 *
+		 * When false will perform OR matching and will match if any of
+		 * a tags attributes or styles match.
+		 *
+		 * @type {Boolean}
+		 * @since 3.1.0
+		 */
+		strictMatch: false
+	};
+
+	/**
+	 * Converts a number 0-255 to hex.
+	 *
+	 * Will return 00 if number is not a valid number.
+	 *
+	 * @param  {any} number
+	 * @return {string}
+	 * @private
+	 */
+	function toHex(number) {
+		number = parseInt(number, 10);
+
+		if (isNaN(number)) {
+			return '00';
+		}
+
+		number = Math.max(0, Math.min(number, 255)).toString(16);
+
+		return number.length < 2 ? '0' + number : number;
+	}
+
+	/**
+	 * Normalises a CSS colour to hex #xxxxxx format
+	 *
+	 * @param  {string} colorStr
+	 * @return {string}
+	 * @private
+	 */
+	function _normaliseColour(colorStr) {
+		var match;
+
+		colorStr = colorStr || '#000';
+
+		// rgb(n,n,n);
+		if ((match =
+			colorStr.match(/rgb\((\d{1,3}),\s*?(\d{1,3}),\s*?(\d{1,3})\)/i))) {
+			return '#' +
+				toHex(match[1]) +
+				toHex(match[2]) +
+				toHex(match[3]);
+		}
+
+		// expand shorthand
+		if ((match = colorStr.match(/#([0-9a-f])([0-9a-f])([0-9a-f])\s*?$/i))) {
+			return '#' +
+				match[1] + match[1] +
+				match[2] + match[2] +
+				match[3] + match[3];
+		}
+
+		return colorStr;
+	}
+
+	/**
+	 * SCEditor BBCode format
+	 * @since 2.0.0
+	 */
+	function bbcodeFormat() {
+		var base = this;
+
+		base.stripQuotes = _stripQuotes;
+
+		/**
+		 * cache of all the tags pointing to their bbcodes to enable
+		 * faster lookup of which bbcode a tag should have
+		 * @private
+		 */
+		var tagsToBBCodes = {};
+
+		/**
+		 * Allowed children of specific HTML tags. Empty array if no
+		 * children other than text nodes are allowed
+		 * @private
+		 */
+		var validChildren = {
+			ul: ['li', 'ol', 'ul'],
+			ol: ['li', 'ol', 'ul'],
+			table: ['tr'],
+			tr: ['td', 'th'],
+			code: ['br', 'p', 'div']
+		};
+
+		/**
+		 * Populates tagsToBBCodes and stylesToBBCodes for easier lookups
+		 *
+		 * @private
+		 */
+		function buildBbcodeCache() {
+			each(bbcodeHandlers, function (bbcode, handler) {
+				var
+					isBlock = handler.isInline === false,
+					tags   = bbcodeHandlers[bbcode].tags,
+					styles = bbcodeHandlers[bbcode].styles;
+
+				if (styles) {
+					tagsToBBCodes['*'] = tagsToBBCodes['*'] || {};
+					tagsToBBCodes['*'][isBlock] =
+						tagsToBBCodes['*'][isBlock] || {};
+					tagsToBBCodes['*'][isBlock][bbcode] = [
+						['style', Object.entries(styles)]
+					];
+				}
+
+				if (tags) {
+					each(tags, function (tag, values) {
+						if (values && values.style) {
+							values.style = Object.entries(values.style);
+						}
+
+						tagsToBBCodes[tag] = tagsToBBCodes[tag] || {};
+						tagsToBBCodes[tag][isBlock] =
+							tagsToBBCodes[tag][isBlock] || {};
+						tagsToBBCodes[tag][isBlock][bbcode] =
+							values && Object.entries(values);
+					});
+				}
+			});
+		};
+
+		/**
+		 * Handles adding newlines after block level elements
+		 *
+		 * @param {HTMLElement} element The element to convert
+		 * @param {string} content  The tags text content
+		 * @return {string}
+		 * @private
+		 */
+		function handleBlockNewlines(element, content) {
+			var	tag = element.nodeName.toLowerCase();
+			var isInline = dom.isInline;
+			if (!isInline(element, true) || tag === 'br') {
+				var	isLastBlockChild, parent, parentLastChild,
+					previousSibling = element.previousSibling;
+
+				// Skips selection makers and ignored elements
+				// Skip empty inline elements
+				while (previousSibling &&
+						previousSibling.nodeType === 1 &&
+						!is(previousSibling, 'br') &&
+						isInline(previousSibling, true) &&
+						!previousSibling.firstChild) {
+					previousSibling = previousSibling.previousSibling;
+				}
+
+				// If it's the last block of an inline that is the last
+				// child of a block then it shouldn't cause a line break
+				// <block><inline><br></inline></block>
+				do {
+					parent          = element.parentNode;
+					parentLastChild = parent && parent.lastChild;
+
+					isLastBlockChild = parentLastChild === element;
+					element = parent;
+				} while (parent && isLastBlockChild && isInline(parent, true));
+
+				// If this block is:
+				//	* Not the last child of a block level element
+				//	* Is a <li> tag (lists are blocks)
+				if (!isLastBlockChild || tag === 'li') {
+					content += '\n';
+				}
+
+				// Check for:
+				// <block>text<block>text</block></block>
+				//
+				// The second opening <block> opening tag should cause a
+				// line break because the previous sibing is inline.
+				if (tag !== 'br' && previousSibling &&
+					!is(previousSibling, 'br') &&
+					isInline(previousSibling, true)) {
+					content = '\n' + content;
+				}
+			}
+
+			return content;
+		}
+
+		/**
+		 * Handles a HTML tag and finds any matching BBCodes
+		 *
+		 * @param {HTMLElement} element The element to convert
+		 * @param {string} content  The Tags text content
+		 * @param {boolean} blockLevel
+		 * @return {string} Content with any matching BBCode tags
+		 *                  wrapped around it.
+		 * @private
+		 */
+		function handleTags(element, content, blockLevel) {
+			function isStyleMatch(style) {
+				var property = style[0];
+				var values = style[1];
+				var val = dom.getStyle(element, property);
+				var parent = element.parentNode;
+
+				// if the parent has the same style use that instead of this one
+				// so you don't end up with [i]parent[i]child[/i][/i]
+				if (!val || parent && dom.hasStyle(parent, property, val)) {
+					return false;
+				}
+
+				return !values || values.includes(val);
+			}
+
+			function createAttributeMatch(isStrict) {
+				return function (attribute) {
+					var name = attribute[0];
+					var value = attribute[1];
+
+					// code tags should skip most styles
+					if (name === 'style' && element.nodeName === 'CODE') {
+						return false;
+					}
+
+					if (name === 'style' && value) {
+						return value[isStrict ? 'every' : 'some'](isStyleMatch);
+					} else {
+						var val = attr(element, name);
+
+						return val && (!value || value.includes(val));
+					}
+				};
+			}
+
+			function handleTag(tag) {
+				if (!tagsToBBCodes[tag] || !tagsToBBCodes[tag][blockLevel]) {
+					return;
+				}
+
+				// loop all bbcodes for this tag
+				each(tagsToBBCodes[tag][blockLevel], function (bbcode, attrs) {
+					var fn, format,
+						isStrict = bbcodeHandlers[bbcode].strictMatch;
+
+					if (typeof isStrict === 'undefined') {
+						isStrict = base.opts.strictMatch;
+					}
+
+					// Skip if the element doesn't have the attribute or the
+					// attribute doesn't match one of the required values
+					fn = isStrict ? 'every' : 'some';
+					if (attrs && !attrs[fn](createAttributeMatch(isStrict))) {
+						return;
+					}
+
+					format = bbcodeHandlers[bbcode].format;
+					if (isFunction(format)) {
+						content = format.call(base, element, content);
+					} else {
+						content = _formatString(format, content);
+					}
+					return false;
+				});
+			}
+
+			handleTag('*');
+			handleTag(element.nodeName.toLowerCase());
+			return content;
+		}
+
+		/**
+		 * Converts a HTML dom element to BBCode starting from
+		 * the innermost element and working backwards
+		 *
+		 * @private
+		 * @param {HTMLElement}	element
+		 * @param {boolean}	hasCodeParent
+		 * @return {string} BBCode
+		 * @memberOf SCEditor.plugins.bbcode.prototype
+		 */
+		function elementToBbcode(element, hasCodeParent) {
+			var toBBCode = function (node, hasCodeParent, vChildren) {
+				var ret = '';
+
+				dom.traverse(node, function (node) {
+					var	content      = '',
+						nodeType     = node.nodeType,
+						tag          = node.nodeName.toLowerCase(),
+						isCodeTag    = tag === 'code',
+						isEmoticon   = tag === 'img' &&
+							!!attr(node, EMOTICON_DATA_ATTR),
+						vChild       = validChildren[tag],
+						firstChild   = node.firstChild,
+						isValidChild = true;
+
+					if (vChildren) {
+						isValidChild = vChildren.indexOf(tag) > -1;
+
+						// Emoticons should always be converted
+						if (isEmoticon) {
+							isValidChild = true;
+						}
+
+						// if this tag is one of the parents allowed children
+						// then set this tags allowed children to whatever it
+						// allows, otherwise set to what the parent allows
+						if (!isValidChild) {
+							vChild = vChildren;
+						}
+					}
+
+					// 1 = element
+					if (nodeType === 1) {
+						// skip empty nlf elements (new lines automatically
+						// added after block level elements like quotes)
+						if (is(node, '.sceditor-nlf') && !firstChild) {
+							return;
+						}
+
+						// don't convert iframe contents
+						if (tag !== 'iframe') {
+							content = toBBCode(node, hasCodeParent || isCodeTag,
+								vChild);
+						}
+
+						// TODO: isValidChild is no longer needed. Should use
+						// valid children bbcodes instead by creating BBCode
+						// tokens like the parser.
+						if (isValidChild) {
+							// Emoticons should be converted if they have found
+							// their way into a code tag
+							if (!hasCodeParent || isEmoticon) {
+								if (!isCodeTag) {
+									// Parse inline codes first so they don't
+									// contain block level codes
+									content = handleTags(node, content, false);
+								}
+
+								content = handleTags(node, content, true);
+							}
+							ret += handleBlockNewlines(node, content);
+						} else {
+							ret += content;
+						}
+					// 3 = text
+					} else if (nodeType === 3) {
+						ret += node.nodeValue;
+					}
+				}, false, true);
+
+				return ret;
+			};
+
+			return toBBCode(element, hasCodeParent);
+		};
+
+		/**
+		 * Initializer
+		 * @private
+		 */
+		base.init = function () {
+			base.opts = this.opts;
+			base.elementToBbcode = elementToBbcode;
+
+			// build the BBCode cache
+			buildBbcodeCache();
+
+			this.commands = extend(
+				true, {}, defaultCommandsOverrides, this.commands
+			);
+
+			// Add BBCode helper methods
+			this.toBBCode   = base.toSource;
+			this.fromBBCode = base.toHtml;
+		};
+
+		/**
+		 * Converts BBCode into HTML
+		 *
+		 * @param {boolean} asFragment
+		 * @param {string} source
+		 * @param {boolean} [legacyAsFragment] Used by fromBBCode() method
+		 */
+		function toHtml(asFragment, source, legacyAsFragment) {
+			var	parser = new BBCodeParser(base.opts.parserOptions);
+			var toHTML = (asFragment || legacyAsFragment) ?
+				parser.toHTMLFragment :
+				parser.toHTML;
+
+			return toHTML(base.opts.bbcodeTrim ? source.trim() : source);
+		}
+
+		/**
+		 * Converts HTML into BBCode
+		 *
+		 * @param {boolean} asFragment
+		 * @param {string}	html
+		 * @param {!Document} [context]
+		 * @param {!HTMLElement} [parent]
+		 * @return {string}
+		 * @private
+		 */
+		function toSource(asFragment, html, context, parent) {
+			context = context || document;
+
+			var	bbcode, elements;
+			var hasCodeParent = !!dom.closest(parent, 'code');
+			var containerParent = context.createElement('div');
+			var container = context.createElement('div');
+			var parser = new BBCodeParser(base.opts.parserOptions);
+
+			container.innerHTML = html;
+			css(containerParent, 'visibility', 'hidden');
+			containerParent.appendChild(container);
+			context.body.appendChild(containerParent);
+
+			if (asFragment) {
+				// Add text before and after so removeWhiteSpace doesn't remove
+				// leading and trailing whitespace
+				containerParent.insertBefore(
+					context.createTextNode('#'),
+					containerParent.firstChild
+				);
+				containerParent.appendChild(context.createTextNode('#'));
+			}
+
+			// Match parents white-space handling
+			if (parent) {
+				css(container, 'whiteSpace', css(parent, 'whiteSpace'));
+			}
+
+			// Remove all nodes with sceditor-ignore class
+			elements = container.getElementsByClassName('sceditor-ignore');
+			while (elements.length) {
+				elements[0].parentNode.removeChild(elements[0]);
+			}
+
+			dom.removeWhiteSpace(containerParent);
+
+			bbcode = elementToBbcode(container, hasCodeParent);
+
+			context.body.removeChild(containerParent);
+
+			bbcode = parser.toBBCode(bbcode, true);
+
+			if (base.opts.bbcodeTrim) {
+				bbcode = bbcode.trim();
+			}
+
+			return bbcode;
+		};
+
+		base.toHtml = toHtml.bind(null, false);
+		base.fragmentToHtml = toHtml.bind(null, true);
+		base.toSource = toSource.bind(null, false);
+		base.fragmentToSource = toSource.bind(null, true);
+	};
+
+	/**
+	 * Gets a BBCode
+	 *
+	 * @param {string} name
+	 * @return {Object|null}
+	 * @since 2.0.0
+	 */
+	bbcodeFormat.get = function (name) {
+		return bbcodeHandlers[name] || null;
+	};
+
+	/**
+	 * Adds a BBCode to the parser or updates an existing
+	 * BBCode if a BBCode with the specified name already exists.
+	 *
+	 * @param {string} name
+	 * @param {Object} bbcode
+	 * @return {this}
+	 * @since 2.0.0
+	 */
+	bbcodeFormat.set = function (name, bbcode) {
+		if (name && bbcode) {
+			// merge any existing command properties
+			bbcode = extend(bbcodeHandlers[name] || {}, bbcode);
+
+			bbcode.remove = function () {
+				delete bbcodeHandlers[name];
+			};
+
+			bbcodeHandlers[name] = bbcode;
+		}
+
+		return this;
+	};
+
+	/**
+	 * Renames a BBCode
+	 *
+	 * This does not change the format or HTML handling, those must be
+	 * changed manually.
+	 *
+	 * @param  {string} name    [description]
+	 * @param  {string} newName [description]
+	 * @return {this|false}
+	 * @since 2.0.0
+	 */
+	bbcodeFormat.rename = function (name, newName) {
+		if (name in bbcodeHandlers) {
+			bbcodeHandlers[newName] = bbcodeHandlers[name];
+
+			delete bbcodeHandlers[name];
+		}
+
+		return this;
+	};
+
+	/**
+	 * Removes a BBCode
+	 *
+	 * @param {string} name
+	 * @return {this}
+	 * @since 2.0.0
+	 */
+	bbcodeFormat.remove = function (name) {
+		if (name in bbcodeHandlers) {
+			delete bbcodeHandlers[name];
+		}
+
+		return this;
+	};
+
+	bbcodeFormat.formatBBCodeString = formatBBCodeString;
+
+	sceditor.formats.bbcode = bbcodeFormat;
+	sceditor.BBCodeParser = BBCodeParser;
+}(sceditor));

+ 1269 - 0
public/js/sc/formats/xhtml.js

@@ -0,0 +1,1269 @@
+/**
+ * SCEditor XHTML Plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ */
+(function (sceditor) {
+	'use strict';
+
+	var dom = sceditor.dom;
+	var utils = sceditor.utils;
+
+	var css = dom.css;
+	var attr = dom.attr;
+	var is = dom.is;
+	var removeAttr = dom.removeAttr;
+	var convertElement = dom.convertElement;
+	var extend = utils.extend;
+	var each = utils.each;
+	var isEmptyObject = utils.isEmptyObject;
+
+	var getEditorCommand = sceditor.command.get;
+
+	var defaultCommandsOverrides = {
+		bold: {
+			txtExec: ['<strong>', '</strong>']
+		},
+		italic: {
+			txtExec: ['<em>', '</em>']
+		},
+		underline: {
+			txtExec: ['<span style="text-decoration:underline;">', '</span>']
+		},
+		strike: {
+			txtExec: ['<span style="text-decoration:line-through;">', '</span>']
+		},
+		subscript: {
+			txtExec: ['<sub>', '</sub>']
+		},
+		superscript: {
+			txtExec: ['<sup>', '</sup>']
+		},
+		left: {
+			txtExec: ['<div style="text-align:left;">', '</div>']
+		},
+		center: {
+			txtExec: ['<div style="text-align:center;">', '</div>']
+		},
+		right: {
+			txtExec: ['<div style="text-align:right;">', '</div>']
+		},
+		justify: {
+			txtExec: ['<div style="text-align:justify;">', '</div>']
+		},
+		font: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('font')._dropDown(
+					editor,
+					caller,
+					function (font) {
+						editor.insertText('<span style="font-family:' +
+							font + ';">', '</span>');
+					}
+				);
+			}
+		},
+		size: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('size')._dropDown(
+					editor,
+					caller,
+					function (size) {
+						editor.insertText('<span style="font-size:' +
+							size + ';">', '</span>');
+					}
+				);
+			}
+		},
+		color: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('color')._dropDown(
+					editor,
+					caller,
+					function (color) {
+						editor.insertText('<span style="color:' +
+							color + ';">', '</span>');
+					}
+				);
+			}
+		},
+		bulletlist: {
+			txtExec: ['<ul><li>', '</li></ul>']
+		},
+		orderedlist: {
+			txtExec: ['<ol><li>', '</li></ol>']
+		},
+		table: {
+			txtExec: ['<table><tr><td>', '</td></tr></table>']
+		},
+		horizontalrule: {
+			txtExec: ['<hr />']
+		},
+		code: {
+			txtExec: ['<code>', '</code>']
+		},
+		image: {
+			txtExec: function (caller, selected) {
+				var	editor  = this;
+
+				getEditorCommand('image')._dropDown(
+					editor,
+					caller,
+					selected,
+					function (url, width, height) {
+						var attrs  = '';
+
+						if (width) {
+							attrs += ' width="' + width + '"';
+						}
+
+						if (height) {
+							attrs += ' height="' + height + '"';
+						}
+
+						editor.insertText(
+							'<img' + attrs + ' src="' + url + '" />'
+						);
+					}
+				);
+			}
+		},
+		email: {
+			txtExec: function (caller, selected) {
+				var	editor  = this;
+
+				getEditorCommand('email')._dropDown(
+					editor,
+					caller,
+					function (url, text) {
+						editor.insertText(
+							'<a href="mailto:' + url + '">' +
+								(text || selected || url) +
+							'</a>'
+						);
+					}
+				);
+			}
+		},
+		link: {
+			txtExec: function (caller, selected) {
+				var	editor  = this;
+
+				getEditorCommand('link')._dropDown(
+					editor,
+					caller,
+					function (url, text) {
+						editor.insertText(
+							'<a href="' + url + '">' +
+								(text || selected || url) +
+							'</a>'
+						);
+					}
+				);
+			}
+		},
+		quote: {
+			txtExec: ['<blockquote>', '</blockquote>']
+		},
+		youtube: {
+			txtExec: function (caller) {
+				var editor = this;
+
+				getEditorCommand('youtube')._dropDown(
+					editor,
+					caller,
+					function (id, time) {
+						editor.insertText(
+							'<iframe width="560" height="315" ' +
+							'src="https://www.youtube.com/embed/{id}?' +
+							'wmode=opaque&start=' + time + '" ' +
+							'data-youtube-id="' + id + '" ' +
+							'frameborder="0" allowfullscreen></iframe>'
+						);
+					}
+				);
+			}
+		},
+		rtl: {
+			txtExec: ['<div stlye="direction:rtl;">', '</div>']
+		},
+		ltr: {
+			txtExec: ['<div stlye="direction:ltr;">', '</div>']
+		}
+	};
+
+	/**
+	 * XHTMLSerializer part of the XHTML plugin.
+	 *
+	 * @class XHTMLSerializer
+	 * @name jQuery.sceditor.XHTMLSerializer
+	 * @since v1.4.1
+	 */
+	sceditor.XHTMLSerializer = function () {
+		var base = this;
+
+		var opts = {
+			indentStr: '\t'
+		};
+
+		/**
+		 * Array containing the output, used as it's faster
+		 * than string concatenation in slow browsers.
+		 * @type {Array}
+		 * @private
+		 */
+		var outputStringBuilder = [];
+
+		/**
+		 * Current indention level
+		 * @type {number}
+		 * @private
+		 */
+		var currentIndent = 0;
+
+		// TODO: use escape.entities
+		/**
+		 * Escapes XHTML entities
+		 *
+		 * @param  {string} str
+		 * @return {string}
+		 * @private
+		 */
+		function escapeEntities(str) {
+			var entities = {
+				'&': '&amp;',
+				'<': '&lt;',
+				'>': '&gt;',
+				'"': '&quot;',
+				'\xa0': '&nbsp;'
+			};
+
+			return !str ? '' : str.replace(/[&<>"\xa0]/g, function (entity) {
+				return entities[entity] || entity;
+			});
+		};
+
+		/**
+		 * Replace spaces including newlines with a single
+		 * space except for non-breaking spaces
+		 *
+		 * @param  {string} str
+		 * @return {string}
+		 * @private
+		 */
+		function trim(str) {
+			return str.replace(/[^\S\u00A0]+/g, ' ');
+		};
+
+		/**
+		 * Serializes a node to XHTML
+		 *
+		 * @param  {Node} node            Node to serialize
+		 * @param  {boolean} onlyChildren If to only serialize the nodes
+		 *                                children and not the node
+		 *                                itself
+		 * @return {string}               The serialized node
+		 * @name serialize
+		 * @memberOf jQuery.sceditor.XHTMLSerializer.prototype
+		 * @since v1.4.1
+		 */
+		base.serialize = function (node, onlyChildren) {
+			outputStringBuilder = [];
+
+			if (onlyChildren) {
+				node = node.firstChild;
+
+				while (node) {
+					serializeNode(node);
+					node = node.nextSibling;
+				}
+			} else {
+				serializeNode(node);
+			}
+
+			return outputStringBuilder.join('');
+		};
+
+		/**
+		 * Serializes a node to the outputStringBuilder
+		 *
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function serializeNode(node, parentIsPre) {
+			switch (node.nodeType) {
+				case 1: // element
+					handleElement(node, parentIsPre);
+					break;
+
+				case 3: // text
+					handleText(node, parentIsPre);
+					break;
+
+				case 4: // cdata section
+					handleCdata(node);
+					break;
+
+				case 8: // comment
+					handleComment(node);
+					break;
+
+				case 9: // document
+				case 11: // document fragment
+					handleDoc(node);
+					break;
+
+				// Ignored types
+				case 2: // attribute
+				case 5: // entity ref
+				case 6: // entity
+				case 7: // processing instruction
+				case 10: // document type
+				case 12: // notation
+					break;
+			}
+		};
+
+		/**
+		 * Handles doc node
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function handleDoc(node) {
+			var	child = node.firstChild;
+
+			while (child) {
+				serializeNode(child);
+				child = child.nextSibling;
+			}
+		};
+
+		/**
+		 * Handles element nodes
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function handleElement(node, parentIsPre) {
+			var	child, attr, attrValue,
+				tagName     = node.nodeName.toLowerCase(),
+				isIframe    = tagName === 'iframe',
+				attrIdx     = node.attributes.length,
+				firstChild  = node.firstChild,
+				// pre || pre-wrap with any vendor prefix
+				isPre       = parentIsPre ||
+					/pre(?:\-wrap)?$/i.test(css(node, 'whiteSpace')),
+				selfClosing = !node.firstChild && !dom.canHaveChildren(node) &&
+					!isIframe;
+
+			if (is(node, '.sceditor-ignore')) {
+				return;
+			}
+
+			output('<' + tagName, !parentIsPre && canIndent(node));
+			while (attrIdx--) {
+				attr = node.attributes[attrIdx];
+
+				attrValue = attr.value;
+
+				output(' ' + attr.name.toLowerCase() + '="' +
+					escapeEntities(attrValue) + '"', false);
+			}
+			output(selfClosing ? ' />' : '>', false);
+
+			if (!isIframe) {
+				child = firstChild;
+			}
+
+			while (child) {
+				currentIndent++;
+
+				serializeNode(child, isPre);
+				child = child.nextSibling;
+
+				currentIndent--;
+			}
+
+			if (!selfClosing) {
+				output(
+					'</' + tagName + '>',
+					!isPre && !isIframe && canIndent(node) &&
+						firstChild && canIndent(firstChild)
+				);
+			}
+		};
+
+		/**
+		 * Handles CDATA nodes
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function handleCdata(node) {
+			output('<![CDATA[' + escapeEntities(node.nodeValue) + ']]>');
+		};
+
+		/**
+		 * Handles comment nodes
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function handleComment(node) {
+			output('<!-- ' + escapeEntities(node.nodeValue) + ' -->');
+		};
+
+		/**
+		 * Handles text nodes
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function handleText(node, parentIsPre) {
+			var text = node.nodeValue;
+
+			if (!parentIsPre) {
+				text = trim(text);
+			}
+
+			if (text) {
+				output(escapeEntities(text), !parentIsPre && canIndent(node));
+			}
+		};
+
+		/**
+		 * Adds a string to the outputStringBuilder.
+		 *
+		 * The string will be indented unless indent is set to boolean false.
+		 * @param  {string} str
+		 * @param  {boolean} indent
+		 * @return {void}
+		 * @private
+		 */
+		function output(str, indent) {
+			var i = currentIndent;
+
+			if (indent !== false) {
+				// Don't add a new line if it's the first element
+				if (outputStringBuilder.length) {
+					outputStringBuilder.push('\n');
+				}
+
+				while (i--) {
+					outputStringBuilder.push(opts.indentStr);
+				}
+			}
+
+			outputStringBuilder.push(str);
+		};
+
+		/**
+		 * Checks if should indent the node or not
+		 * @param  {Node} node
+		 * @return {boolean}
+		 * @private
+		 */
+		function canIndent(node) {
+			var prev = node.previousSibling;
+
+			if (node.nodeType !== 1 && prev) {
+				return !dom.isInline(prev);
+			}
+
+			// first child of a block element
+			if (!prev && !dom.isInline(node.parentNode)) {
+				return true;
+			}
+
+			return !dom.isInline(node);
+		};
+	};
+
+	/**
+	 * SCEditor XHTML plugin
+	 * @class xhtml
+	 * @name jQuery.sceditor.plugins.xhtml
+	 * @since v1.4.1
+	 */
+	function xhtmlFormat() {
+		var base = this;
+
+		/**
+		 * Tag converters cache
+		 * @type {Object}
+		 * @private
+		 */
+		var tagConvertersCache = {};
+
+		/**
+		 * Attributes filter cache
+		 * @type {Object}
+		 * @private
+		 */
+		var attrsCache = {};
+
+		/**
+		 * Init
+		 * @return {void}
+		 */
+		base.init = function () {
+			if (!isEmptyObject(xhtmlFormat.converters || {})) {
+				each(
+					xhtmlFormat.converters,
+					function (idx, converter) {
+						each(converter.tags, function (tagname) {
+							if (!tagConvertersCache[tagname]) {
+								tagConvertersCache[tagname] = [];
+							}
+
+							tagConvertersCache[tagname].push(converter);
+						});
+					}
+				);
+			}
+
+			this.commands = extend(true,
+				{}, defaultCommandsOverrides, this.commands);
+		};
+
+		/**
+		 * Converts the WYSIWYG content to XHTML
+		 *
+		 * @param  {boolean} isFragment
+		 * @param  {string} html
+		 * @param  {Document} context
+		 * @param  {HTMLElement} [parent]
+		 * @return {string}
+		 * @memberOf jQuery.sceditor.plugins.xhtml.prototype
+		 */
+		function toSource(isFragment, html, context) {
+			var xhtml,
+				container = context.createElement('div');
+			container.innerHTML = html;
+
+			css(container, 'visibility', 'hidden');
+			context.body.appendChild(container);
+
+			convertTags(container);
+			removeTags(container);
+			removeAttribs(container);
+
+			if (!isFragment) {
+				wrapInlines(container);
+			}
+
+			xhtml = (new sceditor.XHTMLSerializer()).serialize(container, true);
+
+			context.body.removeChild(container);
+
+			return xhtml;
+		};
+
+		base.toSource = toSource.bind(null, false);
+
+		base.fragmentToSource = toSource.bind(null, true);;
+
+		/**
+		 * Runs all converters for the specified tagName
+		 * against the DOM node.
+		 * @param  {string} tagName
+		 * @return {Node} node
+		 * @private
+		 */
+		function convertNode(tagName, node) {
+			if (!tagConvertersCache[tagName]) {
+				return;
+			}
+
+			tagConvertersCache[tagName].forEach(function (converter) {
+				if (converter.tags[tagName]) {
+					each(converter.tags[tagName], function (attr, values) {
+						if (!node.getAttributeNode) {
+							return;
+						}
+
+						attr = node.getAttributeNode(attr);
+
+						if (!attr || values && values.indexOf(attr.value) < 0) {
+							return;
+						}
+
+						converter.conv.call(base, node);
+					});
+				} else if (converter.conv) {
+					converter.conv.call(base, node);
+				}
+			});
+		};
+
+		/**
+		 * Converts any tags/attributes to their XHTML equivalents
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function convertTags(node) {
+			dom.traverse(node, function (node) {
+				var	tagName = node.nodeName.toLowerCase();
+
+				convertNode('*', node);
+				convertNode(tagName, node);
+			}, true);
+		};
+
+		/**
+		 * Tests if a node is empty and can be removed.
+		 *
+		 * @param  {Node} node
+		 * @return {boolean}
+		 * @private
+		 */
+		function isEmpty(node, excludeBr) {
+			var	rect,
+				childNodes     = node.childNodes,
+				tagName        = node.nodeName.toLowerCase(),
+				nodeValue      = node.nodeValue,
+				childrenLength = childNodes.length,
+				allowedEmpty   = xhtmlFormat.allowedEmptyTags || [];
+
+			if (excludeBr && tagName === 'br') {
+				return true;
+			}
+
+			if (is(node, '.sceditor-ignore')) {
+				return true;
+			}
+
+			if (allowedEmpty.indexOf(tagName) > -1 || tagName === 'td' ||
+				!dom.canHaveChildren(node)) {
+
+				return false;
+			}
+
+			// \S|\u00A0 = any non space char
+			if (nodeValue && /\S|\u00A0/.test(nodeValue)) {
+				return false;
+			}
+
+			while (childrenLength--) {
+				if (!isEmpty(childNodes[childrenLength],
+					excludeBr && !node.previousSibling && !node.nextSibling)) {
+					return false;
+				}
+			}
+
+			// Treat tags with a width and height from CSS as not empty
+			if (node.getBoundingClientRect &&
+				(node.className || node.hasAttributes('style'))) {
+				rect = node.getBoundingClientRect();
+				return !rect.width || !rect.height;
+			}
+
+			return true;
+		};
+
+		/**
+		 * Removes any tags that are not white listed or if no
+		 * tags are white listed it will remove any tags that
+		 * are black listed.
+		 *
+		 * @param  {Node} rootNode
+		 * @return {void}
+		 * @private
+		 */
+		function removeTags(rootNode) {
+			dom.traverse(rootNode, function (node) {
+				var	remove,
+					tagName         = node.nodeName.toLowerCase(),
+					parentNode      = node.parentNode,
+					nodeType        = node.nodeType,
+					isBlock         = !dom.isInline(node),
+					previousSibling = node.previousSibling,
+					nextSibling     = node.nextSibling,
+					isTopLevel      = parentNode === rootNode,
+					noSiblings      = !previousSibling && !nextSibling,
+					empty           = tagName !== 'iframe' && isEmpty(node,
+						isTopLevel && noSiblings && tagName !== 'br'),
+					document        = node.ownerDocument,
+					allowedTags     = xhtmlFormat.allowedTags,
+					firstChild   	= node.firstChild,
+					disallowedTags  = xhtmlFormat.disallowedTags;
+
+				// 3 = text node
+				if (nodeType === 3) {
+					return;
+				}
+
+				if (nodeType === 4) {
+					tagName = '!cdata';
+				} else if (tagName === '!' || nodeType === 8) {
+					tagName = '!comment';
+				}
+
+				if (nodeType === 1) {
+					// skip empty nlf elements (new lines automatically
+					// added after block level elements like quotes)
+					if (is(node, '.sceditor-nlf')) {
+						if (!firstChild || (node.childNodes.length === 1 &&
+							/br/i.test(firstChild.nodeName))) {
+							// Mark as empty,it will be removed by the next code
+							empty = true;
+						} else {
+							node.classList.remove('sceditor-nlf');
+
+							if (!node.className) {
+								removeAttr(node, 'class');
+							}
+						}
+					}
+				}
+
+				if (empty) {
+					remove = true;
+				// 3 is text node which do not get filtered
+				} else if (allowedTags && allowedTags.length) {
+					remove = (allowedTags.indexOf(tagName) < 0);
+				} else if (disallowedTags && disallowedTags.length) {
+					remove = (disallowedTags.indexOf(tagName) > -1);
+				}
+
+				if (remove) {
+					if (!empty) {
+						if (isBlock && previousSibling &&
+							dom.isInline(previousSibling)) {
+							parentNode.insertBefore(
+								document.createTextNode(' '), node);
+						}
+
+						// Insert all the childen after node
+						while (node.firstChild) {
+							parentNode.insertBefore(node.firstChild,
+								nextSibling);
+						}
+
+						if (isBlock && nextSibling &&
+							dom.isInline(nextSibling)) {
+							parentNode.insertBefore(
+								document.createTextNode(' '), nextSibling);
+						}
+					}
+
+					parentNode.removeChild(node);
+				}
+			}, true);
+		};
+
+		/**
+		 * Merges two sets of attribute filters into one
+		 *
+		 * @param  {Object} filtersA
+		 * @param  {Object} filtersB
+		 * @return {Object}
+		 * @private
+		 */
+		function mergeAttribsFilters(filtersA, filtersB) {
+			var ret = {};
+
+			if (filtersA) {
+				ret = extend({}, ret, filtersA);
+			}
+
+			if (!filtersB) {
+				return ret;
+			}
+
+			each(filtersB, function (attrName, values) {
+				if (Array.isArray(values)) {
+					ret[attrName] = (ret[attrName] || []).concat(values);
+				} else if (!ret[attrName]) {
+					ret[attrName] = null;
+				}
+			});
+
+			return ret;
+		};
+
+		/**
+		 * Wraps adjacent inline child nodes of root
+		 * in paragraphs.
+		 *
+		 * @param {Node} root
+		 * @private
+		 */
+		function wrapInlines(root) {
+			// Strip empty text nodes so they don't get wrapped.
+			dom.removeWhiteSpace(root);
+
+			var wrapper;
+			var node = root.firstChild;
+			var next;
+			while (node) {
+				next = node.nextSibling;
+
+				if (dom.isInline(node) && !is(node, '.sceditor-ignore')) {
+					if (!wrapper) {
+						wrapper = root.ownerDocument.createElement('p');
+						node.parentNode.insertBefore(wrapper, node);
+					}
+
+					wrapper.appendChild(node);
+				} else {
+					wrapper = null;
+				}
+
+				node = next;
+			}
+		};
+
+		/**
+		 * Removes any attributes that are not white listed or
+		 * if no attributes are white listed it will remove
+		 * any attributes that are black listed.
+		 * @param  {Node} node
+		 * @return {void}
+		 * @private
+		 */
+		function removeAttribs(node) {
+			var	tagName, attr, attrName, attrsLength, validValues, remove,
+				allowedAttribs    = xhtmlFormat.allowedAttribs,
+				isAllowed         = allowedAttribs &&
+					!isEmptyObject(allowedAttribs),
+				disallowedAttribs = xhtmlFormat.disallowedAttribs,
+				isDisallowed      = disallowedAttribs &&
+					!isEmptyObject(disallowedAttribs);
+
+			attrsCache = {};
+
+			dom.traverse(node, function (node) {
+				if (!node.attributes) {
+					return;
+				}
+
+				tagName     = node.nodeName.toLowerCase();
+				attrsLength = node.attributes.length;
+
+				if (attrsLength) {
+					if (!attrsCache[tagName]) {
+						if (isAllowed) {
+							attrsCache[tagName] = mergeAttribsFilters(
+								allowedAttribs['*'],
+								allowedAttribs[tagName]
+							);
+						} else {
+							attrsCache[tagName] = mergeAttribsFilters(
+								disallowedAttribs['*'],
+								disallowedAttribs[tagName]
+							);
+						}
+					}
+
+					while (attrsLength--) {
+						attr        = node.attributes[attrsLength];
+						attrName    = attr.name;
+						validValues = attrsCache[tagName][attrName];
+						remove      = false;
+
+						if (isAllowed) {
+							remove = validValues !== null &&
+								(!Array.isArray(validValues) ||
+									validValues.indexOf(attr.value) < 0);
+						} else if (isDisallowed) {
+							remove = validValues === null ||
+								(Array.isArray(validValues) &&
+									validValues.indexOf(attr.value) > -1);
+						}
+
+						if (remove) {
+							node.removeAttribute(attrName);
+						}
+					}
+				}
+			});
+		};
+	};
+
+	/**
+	 * Tag conveters, a converter is applied to all
+	 * tags that match the criteria.
+	 * @type {Array}
+	 * @name jQuery.sceditor.plugins.xhtml.converters
+	 * @since v1.4.1
+	 */
+	xhtmlFormat.converters = [
+		{
+			tags: {
+				'*': {
+					width: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'width', attr(node, 'width'));
+				removeAttr(node, 'width');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					height: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'height', attr(node, 'height'));
+				removeAttr(node, 'height');
+			}
+		},
+		{
+			tags: {
+				'li': {
+					value: null
+				}
+			},
+			conv: function (node) {
+				removeAttr(node, 'value');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					text: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'color', attr(node, 'text'));
+				removeAttr(node, 'text');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					color: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'color', attr(node, 'color'));
+				removeAttr(node, 'color');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					face: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'fontFamily', attr(node, 'face'));
+				removeAttr(node, 'face');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					align: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'textAlign', attr(node, 'align'));
+				removeAttr(node, 'align');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					border: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'borderWidth', attr(node, 'border'));
+				removeAttr(node, 'border');
+			}
+		},
+		{
+			tags: {
+				applet: {
+					name: null
+				},
+				img: {
+					name: null
+				},
+				layer: {
+					name: null
+				},
+				map: {
+					name: null
+				},
+				object: {
+					name: null
+				},
+				param: {
+					name: null
+				}
+			},
+			conv: function (node) {
+				if (!attr(node, 'id')) {
+					attr(node, 'id', attr(node, 'name'));
+				}
+
+				removeAttr(node, 'name');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					vspace: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'marginTop', attr(node, 'vspace') - 0);
+				css(node, 'marginBottom', attr(node, 'vspace') - 0);
+				removeAttr(node, 'vspace');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					hspace: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'marginLeft', attr(node, 'hspace') - 0);
+				css(node, 'marginRight', attr(node, 'hspace') - 0);
+				removeAttr(node, 'hspace');
+			}
+		},
+		{
+			tags: {
+				'hr': {
+					noshade: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'borderStyle', 'solid');
+				removeAttr(node, 'noshade');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					nowrap: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'whiteSpace', 'nowrap');
+				removeAttr(node, 'nowrap');
+			}
+		},
+		{
+			tags: {
+				big: null
+			},
+			conv: function (node) {
+				css(convertElement(node, 'span'), 'fontSize', 'larger');
+			}
+		},
+		{
+			tags: {
+				small: null
+			},
+			conv: function (node) {
+				css(convertElement(node, 'span'), 'fontSize', 'smaller');
+			}
+		},
+		{
+			tags: {
+				b: null
+			},
+			conv: function (node) {
+				convertElement(node, 'strong');
+			}
+		},
+		{
+			tags: {
+				u: null
+			},
+			conv: function (node) {
+				css(convertElement(node, 'span'), 'textDecoration',
+					'underline');
+			}
+		},
+		{
+			tags: {
+				s: null,
+				strike: null
+			},
+			conv: function (node) {
+				css(convertElement(node, 'span'), 'textDecoration',
+					'line-through');
+			}
+		},
+		{
+			tags: {
+				dir: null
+			},
+			conv: function (node) {
+				convertElement(node, 'ul');
+			}
+		},
+		{
+			tags: {
+				center: null
+			},
+			conv: function (node) {
+				css(convertElement(node, 'div'), 'textAlign', 'center');
+			}
+		},
+		{
+			tags: {
+				font: {
+					size: null
+				}
+			},
+			conv: function (node) {
+				css(node, 'fontSize', css(node, 'fontSize'));
+				removeAttr(node, 'size');
+			}
+		},
+		{
+			tags: {
+				font: null
+			},
+			conv: function (node) {
+				// All it's attributes will be converted
+				// by the attribute converters
+				convertElement(node, 'span');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					type: ['_moz']
+				}
+			},
+			conv: function (node) {
+				removeAttr(node, 'type');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					'_moz_dirty': null
+				}
+			},
+			conv: function (node) {
+				removeAttr(node, '_moz_dirty');
+			}
+		},
+		{
+			tags: {
+				'*': {
+					'_moz_editor_bogus_node': null
+				}
+			},
+			conv: function (node) {
+				node.parentNode.removeChild(node);
+			}
+		},
+		{
+			tags: {
+				'*': {
+					'data-sce-target': null
+				}
+			},
+			conv: function (node) {
+				var rel = attr(node, 'rel') || '';
+				var target = attr(node, 'data-sce-target');
+
+				// Only allow the value _blank and only on links
+				if (target === '_blank' && is(node, 'a')) {
+					if (!/(^|\s)noopener(\s|$)/.test(rel)) {
+						attr(node, 'rel', 'noopener' + (rel ? ' ' + rel : ''));
+					}
+
+					attr(node, 'target', target);
+				}
+
+
+				removeAttr(node, 'data-sce-target');
+			}
+		},
+		{
+			tags: {
+				code: null
+			},
+			conv: function (node) {
+				var node, nodes = node.getElementsByTagName('div');
+				while ((node = nodes[0])) {
+					node.style.display = 'block';
+					convertElement(node, 'span');
+				}
+			}
+		}
+	];
+
+	/**
+	 * Allowed attributes map.
+	 *
+	 * To allow an attribute for all tags use * as the tag name.
+	 *
+	 * Leave empty or null to allow all attributes. (the disallow
+	 * list will be used to filter them instead)
+	 * @type {Object}
+	 * @name jQuery.sceditor.plugins.xhtml.allowedAttribs
+	 * @since v1.4.1
+	 */
+	xhtmlFormat.allowedAttribs = {};
+
+	/**
+	 * Attributes that are not allowed.
+	 *
+	 * Only used if allowed attributes is null or empty.
+	 * @type {Object}
+	 * @name jQuery.sceditor.plugins.xhtml.disallowedAttribs
+	 * @since v1.4.1
+	 */
+	xhtmlFormat.disallowedAttribs = {};
+
+	/**
+	 * Array containing all the allowed tags.
+	 *
+	 * If null or empty all tags will be allowed.
+	 * @type {Array}
+	 * @name jQuery.sceditor.plugins.xhtml.allowedTags
+	 * @since v1.4.1
+	 */
+	xhtmlFormat.allowedTags = [];
+
+	/**
+	 * Array containing all the disallowed tags.
+	 *
+	 * Only used if allowed tags is null or empty.
+	 * @type {Array}
+	 * @name jQuery.sceditor.plugins.xhtml.disallowedTags
+	 * @since v1.4.1
+	 */
+	xhtmlFormat.disallowedTags = [];
+
+	/**
+	 * Array containing tags which should not be removed when empty.
+	 *
+	 * @type {Array}
+	 * @name jQuery.sceditor.plugins.xhtml.allowedEmptyTags
+	 * @since v2.0.0
+	 */
+	xhtmlFormat.allowedEmptyTags = [];
+
+	sceditor.formats.xhtml = xhtmlFormat;
+}(sceditor));

+ 132 - 0
public/js/sc/icons/material.js

@@ -0,0 +1,132 @@
+/**
+ * SCEditor SVG material icons plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ */
+(function (document, sceditor) {
+	'use strict';
+
+	var dom = sceditor.dom;
+
+	/**
+	 * Material icons by Google (Apache license)
+	 * https://github.com/google/material-design-icons/blob/master/LICENSE
+	 *
+	 * Extra icons by materialdesignicons.com and contributors (MIT license)
+	 * https://github.com/Templarian/MaterialDesign-SVG/blob/master/LICENSE
+	 */
+	/* eslint max-len: off*/
+	var icons = {
+		'bold': '<path d="M13.5,15.5H10V12.5H13.5A1.5,1.5 0 0,1 15,14A1.5,1.5 0 0,1 13.5,15.5M10,6.5H13A1.5,1.5 0 0,1 14.5,8A1.5,1.5 0 0,1 13,9.5H10M15.6,10.79C16.57,10.11 17.25,9 17.25,8C17.25,5.74 15.5,4 13.25,4H7V18H14.04C16.14,18 17.75,16.3 17.75,14.21C17.75,12.69 16.89,11.39 15.6,10.79Z" />',
+		'bulletlist': '<path d="M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z" />',
+		'center': '<path d="M3,3H21V5H3V3M7,7H17V9H7V7M3,11H21V13H3V11M7,15H17V17H7V15M3,19H21V21H3V19Z" />',
+		// Cody @XT3000 - https://materialdesignicons.com/
+		'code': '<path d="M8,3A2,2 0 0,0 6,5V9A2,2 0 0,1 4,11H3V13H4A2,2 0 0,1 6,15V19A2,2 0 0,0 8,21H10V19H8V14A2,2 0 0,0 6,12A2,2 0 0,0 8,10V5H10V3M16,3A2,2 0 0,1 18,5V9A2,2 0 0,0 20,11H21V13H20A2,2 0 0,0 18,15V19A2,2 0 0,1 16,21H14V19H16V14A2,2 0 0,1 18,12A2,2 0 0,1 16,10V5H14V3H16Z" />',
+		'color': '<path d="M9.62,12L12,5.67L14.37,12M11,3L5.5,17H7.75L8.87,14H15.12L16.25,17H18.5L13,3H11Z" /><path class="sce-color" d="M0,24H24V20H0V24Z" />',
+		'copy': '<path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" />',
+		'cut': '<path d="M19,3L13,9L15,11L22,4V3M12,12.5A0.5,0.5 0 0,1 11.5,12A0.5,0.5 0 0,1 12,11.5A0.5,0.5 0 0,1 12.5,12A0.5,0.5 0 0,1 12,12.5M6,20A2,2 0 0,1 4,18C4,16.89 4.9,16 6,16A2,2 0 0,1 8,18C8,19.11 7.1,20 6,20M6,8A2,2 0 0,1 4,6C4,4.89 4.9,4 6,4A2,2 0 0,1 8,6C8,7.11 7.1,8 6,8M9.64,7.64C9.87,7.14 10,6.59 10,6A4,4 0 0,0 6,2A4,4 0 0,0 2,6A4,4 0 0,0 6,10C6.59,10 7.14,9.87 7.64,9.64L10,12L7.64,14.36C7.14,14.13 6.59,14 6,14A4,4 0 0,0 2,18A4,4 0 0,0 6,22A4,4 0 0,0 10,18C10,17.41 9.87,16.86 9.64,16.36L12,14L19,21H22V20L9.64,7.64Z" />',
+		'date': '<path d="M7,10H12V15H7M19,19H5V8H19M19,3H18V1H16V3H8V1H6V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />',
+		'email': '<path d="M20,8L12,13L4,8V6L12,11L20,6M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" />',
+		'emoticon': '<path d="M12,17.5C14.33,17.5 16.3,16.04 17.11,14H6.89C7.69,16.04 9.67,17.5 12,17.5M8.5,11A1.5,1.5 0 0,0 10,9.5A1.5,1.5 0 0,0 8.5,8A1.5,1.5 0 0,0 7,9.5A1.5,1.5 0 0,0 8.5,11M15.5,11A1.5,1.5 0 0,0 17,9.5A1.5,1.5 0 0,0 15.5,8A1.5,1.5 0 0,0 14,9.5A1.5,1.5 0 0,0 15.5,11M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />',
+		// JapanYoshi @japanyoshilol - https://materialdesignicons.com/
+		'font': '<path d="M17,8H20V20H21V21H17V20H18V17H14L12.5,20H14V21H10V20H11L17,8M18,9L14.5,16H18V9M5,3H10C11.11,3 12,3.89 12,5V16H9V11H6V16H3V5C3,3.89 3.89,3 5,3M6,5V9H9V5H6Z" />',
+		'format': '<path d="M18,4V3A1,1 0 0,0 17,2H5A1,1 0 0,0 4,3V7A1,1 0 0,0 5,8H17A1,1 0 0,0 18,7V6H19V10H9V21A1,1 0 0,0 10,22H12A1,1 0 0,0 13,21V12H21V4H18Z" />',
+		// Austin Andrews @Templarian - https://materialdesignicons.com/
+		'grip': '<path d="M22,22H20V20H22V22M22,18H20V16H22V18M18,22H16V20H18V22M18,18H16V16H18V18M14,22H12V20H14V22M22,14H20V12H22V14Z" />',
+		// Sam Clarke @samclarke
+		'horizontalrule': '<path d="M 3,3 21,3 21,5 3,5 3,3 M 3,7 15,7 15,9 3,9 3,7 m 0,4 18,0 0,4 -18,0 0,-4" />',
+		'image': '<path d="M8.5,13.5L11,16.5L14.5,12L19,18H5M21,19V5C21,3.89 20.1,3 19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19Z" />',
+		'indent': '<path d="M11,13H21V11H11M11,9H21V7H11M3,3V5H21V3M11,17H21V15H11M3,8V16L7,12M3,21H21V19H3V21Z" />',
+		'italic': '<path d="M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z" />',
+		'justify': '<path d="M3,3H21V5H3V3M3,7H21V9H3V7M3,11H21V13H3V11M3,15H21V17H3V15M3,19H21V21H3V19Z" />		',
+		'left': '<path d="M3,3H21V5H3V3M3,7H15V9H3V7M3,11H21V13H3V11M3,15H15V17H3V15M3,19H21V21H3V19Z" />		',
+		'link': '<path d="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0 0,1 16,16.1H13V18H16A6,6 0 0,0 22,12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0 0,0 2,12A6,6 0 0,0 8,18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z" />',
+		'ltr': '<path d="M21,18L17,14V17H5V19H17V22M9,10V15H11V4H13V15H15V4H17V2H9A4,4 0 0,0 5,6A4,4 0 0,0 9,10Z" />',
+		// Austin Andrews @Templarian - https://materialdesignicons.com/
+		'maximize': '<path d="M9.5,13.09L10.91,14.5L6.41,19H10V21H3V14H5V17.59L9.5,13.09M10.91,9.5L9.5,10.91L5,6.41V10H3V3H10V5H6.41L10.91,9.5M14.5,13.09L19,17.59V14H21V21H14V19H17.59L13.09,14.5L14.5,13.09M13.09,9.5L17.59,5H14V3H21V10H19V6.41L14.5,10.91L13.09,9.5Z" />',
+		'orderedlist': '<path d="M7,13H21V11H7M7,19H21V17H7M7,7H21V5H7M2,11H3.8L2,13.1V14H5V13H3.2L5,10.9V10H2M3,8H4V4H2V5H3M2,17H4V17.5H3V18.5H4V19H2V20H5V16H2V17Z" />',
+		'outdent': '<path d="M11,13H21V11H11M11,9H21V7H11M3,3V5H21V3M3,21H21V19H3M3,12L7,16V8M11,17H21V15H11V17Z" />',
+		'paste': '<path d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" />',
+		'pastetext': '<path d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" />',
+		'print': '<path d="M18,3H6V7H18M19,12A1,1 0 0,1 18,11A1,1 0 0,1 19,10A1,1 0 0,1 20,11A1,1 0 0,1 19,12M16,19H8V14H16M19,8H5A3,3 0 0,0 2,11V17H6V21H18V17H22V11A3,3 0 0,0 19,8Z" />',
+		'quote': '<path d="M14,17H17L19,13V7H13V13H16M6,17H9L11,13V7H5V13H8L6,17Z" />',
+		'redo': '<path d="M18.4,10.6C16.55,9 14.15,8 11.5,8C6.85,8 2.92,11.03 1.54,15.22L3.9,16C4.95,12.81 7.95,10.5 11.5,10.5C13.45,10.5 15.23,11.22 16.62,12.38L13,16H22V7L18.4,10.6Z" />',
+		'removeformat': '<path d="M6,5V5.18L8.82,8H11.22L10.5,9.68L12.6,11.78L14.21,8H20V5H6M3.27,5L2,6.27L8.97,13.24L6.5,19H9.5L11.07,15.34L16.73,21L18,19.73L3.55,5.27L3.27,5Z" />',
+		'right': '<path d="M3,3H21V5H3V3M9,7H21V9H9V7M3,11H21V13H3V11M9,15H21V17H9V15M3,19H21V21H3V19Z" />',
+		'rtl': '<path d="M8,17V14L4,18L8,22V19H20V17M10,10V15H12V4H14V15H16V4H18V2H10A4,4 0 0,0 6,6A4,4 0 0,0 10,10Z" />',
+		'size': '<path d="M3,12H6V19H9V12H12V9H3M9,4V7H14V19H17V7H22V4H9Z" />',
+		'source': '<path d="M14.6,16.6L19.2,12L14.6,7.4L16,6L22,12L16,18L14.6,16.6M9.4,16.6L4.8,12L9.4,7.4L8,6L2,12L8,18L9.4,16.6Z" />',
+		'strike': '<path d="M3,14H21V12H3M5,4V7H10V10H14V7H19V4M10,19H14V16H10V19Z" />',
+		// Austin Andrews @Templarian - https://materialdesignicons.com/
+		'subscript': '<path d="M16,7.41L11.41,12L16,16.59L14.59,18L10,13.41L5.41,18L4,16.59L8.59,12L4,7.41L5.41,6L10,10.59L14.59,6L16,7.41M21.85,21.03H16.97V20.03L17.86,19.23C18.62,18.58 19.18,18.04 19.56,17.6C19.93,17.16 20.12,16.75 20.13,16.36C20.14,16.08 20.05,15.85 19.86,15.66C19.68,15.5 19.39,15.38 19,15.38C18.69,15.38 18.42,15.44 18.16,15.56L17.5,15.94L17.05,14.77C17.32,14.56 17.64,14.38 18.03,14.24C18.42,14.1 18.85,14 19.32,14C20.1,14.04 20.7,14.25 21.1,14.66C21.5,15.07 21.72,15.59 21.72,16.23C21.71,16.79 21.53,17.31 21.18,17.78C20.84,18.25 20.42,18.7 19.91,19.14L19.27,19.66V19.68H21.85V21.03Z" />',
+		// Austin Andrews @Templarian - https://materialdesignicons.com/
+		'superscript': '<path d="M16,7.41L11.41,12L16,16.59L14.59,18L10,13.41L5.41,18L4,16.59L8.59,12L4,7.41L5.41,6L10,10.59L14.59,6L16,7.41M21.85,9H16.97V8L17.86,7.18C18.62,6.54 19.18,6 19.56,5.55C19.93,5.11 20.12,4.7 20.13,4.32C20.14,4.04 20.05,3.8 19.86,3.62C19.68,3.43 19.39,3.34 19,3.33C18.69,3.34 18.42,3.4 18.16,3.5L17.5,3.89L17.05,2.72C17.32,2.5 17.64,2.33 18.03,2.19C18.42,2.05 18.85,2 19.32,2C20.1,2 20.7,2.2 21.1,2.61C21.5,3 21.72,3.54 21.72,4.18C21.71,4.74 21.53,5.26 21.18,5.73C20.84,6.21 20.42,6.66 19.91,7.09L19.27,7.61V7.63H21.85V9Z" />',
+		// Austin Andrews @Templarian - https://materialdesignicons.com/
+		'table': '<path d="M5,4H19A2,2 0 0,1 21,6V18A2,2 0 0,1 19,20H5A2,2 0 0,1 3,18V6A2,2 0 0,1 5,4M5,8V12H11V8H5M13,8V12H19V8H13M5,14V18H11V14H5M13,14V18H19V14H13Z" />',
+		'time': '<path d="M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />',
+		'underline': '<path d="M5,21H19V19H5V21M12,17A6,6 0 0,0 18,11V3H15.5V11A3.5,3.5 0 0,1 12,14.5A3.5,3.5 0 0,1 8.5,11V3H6V11A6,6 0 0,0 12,17Z" />',
+		'undo': '<path d="M12.5,8C9.85,8 7.45,9 5.6,10.6L2,7V16H11L7.38,12.38C8.77,11.22 10.54,10.5 12.5,10.5C16.04,10.5 19.05,12.81 20.1,16L22.47,15.22C21.08,11.03 17.15,8 12.5,8Z" />',
+		// Austin Andrews @Templarian - https://materialdesignicons.com/
+		'unlink': '<path d="M2,5.27L3.28,4L20,20.72L18.73,22L14.73,18H13V16.27L9.73,13H8V11.27L5.5,8.76C4.5,9.5 3.9,10.68 3.9,12C3.9,14.26 5.74,16.1 8,16.1H11V18H8A6,6 0 0,1 2,12C2,10.16 2.83,8.5 4.14,7.41L2,5.27M16,6A6,6 0 0,1 22,12C22,14.21 20.8,16.15 19,17.19L17.6,15.77C19.07,15.15 20.1,13.7 20.1,12C20.1,9.73 18.26,7.9 16,7.9H13V6H16M8,6H11V7.9H9.72L7.82,6H8M16,11V13H14.82L12.82,11H16Z" />',
+		'youtube': '<path d="M10,16.5V7.5L16,12M20,4.4C19.4,4.2 15.7,4 12,4C8.3,4 4.6,4.19 4,4.38C2.44,4.9 2,8.4 2,12C2,15.59 2.44,19.1 4,19.61C4.6,19.81 8.3,20 12,20C15.7,20 19.4,19.81 20,19.61C21.56,19.1 22,15.59 22,12C22,8.4 21.56,4.91 20,4.4Z" />'
+	};
+
+	sceditor.icons.material = function () {
+		var nodes = {};
+
+		var colorPath;
+
+		return {
+			create: function (command) {
+				if (command in icons) {
+					// Using viewbox="1 1 22 22" to trim off the 1 unit border
+					// around the SVG icons.
+					// Default is viewbox="0 0 24 24"
+					nodes[command] = sceditor.dom.parseHTML(
+						'<svg xmlns="http://www.w3.org/2000/svg" ' +
+							'viewbox="1 1 22 22" unselectable="on">' +
+								icons[command] +
+						'</svg>'
+					).firstChild;
+
+					if (command === 'color') {
+						colorPath = nodes[command].querySelector('.sce-color');
+					}
+				}
+
+				return nodes[command];
+			},
+			update: function (isSourceMode, currentNode) {
+				if (colorPath) {
+					var color = 'inherit';
+
+					if (!isSourceMode && currentNode) {
+						color = currentNode.ownerDocument
+							.queryCommandValue('forecolor');
+					}
+
+					dom.css(colorPath, 'fill', color);
+				}
+			},
+			rtl: function (isRtl) {
+				var gripNode = nodes.grip;
+
+				if (gripNode) {
+					var transform = isRtl ? 'scaleX(-1)' : '';
+
+					dom.css(gripNode, 'transform', transform);
+					dom.css(gripNode, 'msTransform', transform);
+					dom.css(gripNode, 'webkitTransform', transform);
+				}
+			}
+		};
+	};
+
+	sceditor.icons.material.icons = icons;
+})(document, sceditor);

+ 112 - 0
public/js/sc/icons/monocons.js

@@ -0,0 +1,112 @@
+/**
+ * SCEditor SVG monocons plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ */
+(function (document, sceditor) {
+	'use strict';
+
+	var dom = sceditor.dom;
+
+	/* eslint max-len: off*/
+	var icons = {
+		'bold': '<text x="50%" y="50%" text-anchor="middle" dy=".5ex" font-family="Dejavu Sans, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold">B</text>',
+		'bulletlist': '<path d="M6 2h9v2H6zm0 5h9v2H6zm0 5h9v2H6z"/><circle cx="3" cy="3" r="1.75"/><circle cx="3" cy="8" r="1.75"/><circle cx="3" cy="13" r="1.75"/>',
+		'center': '<path d="M1 1h14v2H1zm2 4h10v2H3zM1 9h14v2H1zm2 4h10v2H3z"/>',
+		'code': '<path d="M7 6L4 9l3 3v-1.5L5.5 9 7 7.5zm2 0v1.5L10.5 9 9 10.5V12l3-3zM2.406 1A.517.517 0 0 0 2 1.5v13c0 .262.238.5.5.5h11a.52.52 0 0 0 .5-.5V4.375c.002-.102-.13-.193-.156-.219l-3-3A.506.506 0 0 0 10.5 1zM3 2h7v2.5c0 .262.238.5.5.5H13v9H3zm8 .688L12.313 4H11z"/>',
+		'color': '<text x="50%" y="8" text-anchor="middle" dy=".5ex" font-family="Dejavu Sans, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold">A</text><path class="sce-color" d="M2 13h12v2H2z"/>',
+		'copy': '<path d="M6.404 5.002a.5.5 0 0 0-.406.5v10a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V8.596a.492.492 0 0 0 0-.094.662.662 0 0 0 0-.063v-.063l-.031-.063v-.031a.557.557 0 0 0-.094-.094l-.031-.031-2.875-2.844a.498.498 0 0 0-.125-.156.5.5 0 0 0-.344-.156h-5a.59.59 0 0 0-.094.001c-.239.046.031-.003 0 0zm.594 1h4v2.5a.5.5 0 0 0 .5.5h2.5v6h-7v-9zm5 .687l1.313 1.313h-1.313V6.689zM1.406.002a.517.517 0 0 0-.406.5v10c0 .262.238.5.5.5H7V6l3-.063V3.596a.492.492 0 0 0 0-.094.331.331 0 0 0 0-.063v-.063c-.009-.021-.02-.041-.031-.062v-.031a.597.597 0 0 0-.094-.094l-.031-.031L6.969.314a.484.484 0 0 0-.125-.156A.506.506 0 0 0 6.5.002h-5a.492.492 0 0 0-.094 0c-.229.044.032-.003 0 0zm.594 1h4v2.5c0 .262.238.5.5.5H9v1.029L7 5 6 6v4l-4 .002v-9zm5 .687l1.313 1.313H7V1.689z"/>',
+		'cut': '<path d="M3 .5c0 2.936 3.774 7.73 3.938 7.938l-1.813 2.844A2.46 2.46 0 0 0 4 11c-1.375 0-2.5 1.125-2.5 2.5S2.625 16 4 16s2.5-1.125 2.5-2.5c0-.444-.138-.856-.344-1.22L8 9.845l1.844 2.438A2.473 2.473 0 0 0 9.5 13.5c0 1.375 1.125 2.5 2.5 2.5s2.5-1.125 2.5-2.5S13.375 11 12 11a2.46 2.46 0 0 0-1.125.28L9.062 8.439C9.226 8.232 13 3.437 13 .5h-1L8 6.78 4 .5H3zM4 12c.834 0 1.5.666 1.5 1.5S4.834 15 4 15s-1.5-.666-1.5-1.5S3.166 12 4 12zm8 0c.834 0 1.5.666 1.5 1.5S12.834 15 12 15s-1.5-.666-1.5-1.5.666-1.5 1.5-1.5z"/>',
+		'date': '<path d="M8.1 7v1h2.7v1H8.094v3H11.7v-1H9v-1h2.7V7zM4.5 7v1h.8v3h-.8v1h2.7v-1h-.9V7zM.9 1v14h14.4V1h-1.8v2h-2.7V1H5.4v2H2.7V1zm.9 4h12.6v9H1.8z"/>',
+		'email': '<path d="M1 4.5v8c0 .262.238.5.5.5h13a.52.52 0 0 0 .5-.5V4.594C15 4 15 4 14.5 4H1.563C1 4 1 4 1 4.5zM2 5h12v7H2V5zm-.187-.906l-.625.812 6.5 5 .312.219.313-.219 6.5-5-.625-.813L8 8.844l-6.187-4.75z"/>',
+		'emoticon': '<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 1a6 6 0 1 1 0 12A6 6 0 0 1 8 2zM6 5c-.546 0-1 .454-1 1s.454 1 1 1 1-.454 1-1-.454-1-1-1zm4 0c-.547 0-1 .454-1 1s.453 1 1 1c.547 0 1-.454 1-1s-.453-1-1-1zM4.5 9.5s-.002.652.469 1.281C5.44 11.409 6.389 12 8 12c1.611 0 2.561-.591 3.031-1.219.47-.629.469-1.281.469-1.281h-1s-.002.314-.281.688c-.279.374-.83.813-2.219.813-1.389 0-1.94-.44-2.219-.813C5.502 9.814 5.5 9.5 5.5 9.5z"/>',
+		'font': '<path d="M7.953 9.75h-4.06l-.395 1.141c-.132.381-.254.752-.368 1.109H.7c.391-1.119.762-2.154 1.113-3.105a104.642 104.642 0 0 1 2.024-5.079 52.23 52.23 0 0 1 1.016-2.212h2.218a80.63 80.63 0 0 1 2.011 4.605c.337.84.105.338.458 1.288s-1.455 2.63-1.587 2.253zM5.912 3.959c-.052.151-.129.357-.229.616-.1.26-.215.56-.343.901-.129.341-.273.716-.431 1.125-.159.409-.32.839-.484 1.288h2.972c-.159-.45-.312-.882-.461-1.292a46.81 46.81 0 0 0-.425-1.127c-.135-.34-.252-.641-.354-.9-.1-.26-.182-.463-.245-.611zm6.949 10.042a36.325 36.325 0 0 0-.35-1.037l-.371-1.063H8.352l-.368 1.064A41.69 41.69 0 0 0 7.64 14H5.373c.365-1.045.711-2.01 1.039-2.896.328-.886.648-1.723.962-2.506.313-.786.623-1.53.927-2.235.305-.705.62-1.393.948-2.065h2.069c.318.672.634 1.36.941 2.065.311.705.621 1.449.936 2.235.314.783.636 1.619.964 2.506.327.888.676 1.853 1.041 2.896l-2.339.001zm-2.625-7.504c-.049.141-.118.333-.213.576-.094.242-.2.521-.319.84-.121.317-.254.668-.402 1.051-.147.382-.299.783-.45 1.201h2.772c-.147-.42-.291-.822-.433-1.205a43.073 43.073 0 0 0-.396-1.053c-.125-.317-.233-.598-.33-.84a13.884 13.884 0 0 0-.229-.57z"/>',
+		'format': '<path d="M10.5 2v1.5H12c.235 0 .401-.009.5 0 .008.088 0 .279 0 .5v2H14V3.437c0-.237-.01-.409-.031-.593-.022-.185-.067-.42-.25-.594s-.407-.2-.594-.219A5.693 5.693 0 0 0 12.5 2zm0-2L7.187 2.5 10.5 5zm.5 5.187L13.5 8.5 16 5.187zm-.958-.339h-2.03l-3.234 8.456c-.154.392-.336.994-.854 1.022v.518h2.744v-.518c-.644-.168-.658-.462-.434-1.036l.784-2.086h3.43l.854 2.086c.238.574.308.924-.406 1.036v.518h3.276v-.518c-.434-.056-.546-.364-.686-.728l-3.444-8.75M7.424 10l1.26-3.318L10 10H7.424M4.912.975h-1.63L.686 7.764c-.124.314-.27.798-.686.82V9h2.203v-.416c-.517-.135-.528-.37-.348-.832l.629-1.674h2.754l.685 1.674c.192.461.248.742-.325.832V9c1.73.137 1.837-.002 2.079-1L4.912.975M2.81 5.11l1.012-2.664L4.878 5.11H2.81"/>',
+		'grip': '<path d="M14.656 5.156l-10 10 .688.688 10-10-.688-.688zm0 3l-7 7 .688.688 7-7-.688-.688zm0 3l-4 4 .688.688 4-4-.688-.688z"/>',
+		'horizontalrule': '<path d="M2 2v1h12V2H2zm0 2v1h9V4H2zm0 2v1h12V6H2zm0 2v2h12V8H2z"/>',
+		'image': '<path d="M.5 2.5v11h15v-11H.5zm1 1h13v9h-13v-9z"/><circle cx="4" cy="6" r="1.25"/><path d="M1 11h14v2H1z"/><path d="M5 12l2-4 2 4z"/><path d="M7 12l4-7 4 7z"/>',
+		'indent': '<path d="M1 1h14v2H1zm5 4h9v2H6zm0 4h9v2H6zm-5 4h14v2H1zm4-5L1 5v6z"/>',
+		'italic': '<text x="50%" y="50%" text-anchor="middle" dy=".5ex" font-family="Dejavu Sans, Helvetica, Arial, sans-serif" font-weight="bold" font-size="15" font-style="italic">i</text>',
+		'justify': '<path d="M1 1h14v2H1zm0 4h14v2H1zm0 4h14v2H1zm0 4h14v2H1z"/>',
+		'left': '<path d="M1 1h14v2H1zm0 4h10v2H1zm0 4h14v2H1zm0 4h10v2H1z"/>',
+		'link': '<path d="M2 4c-.625 0-1.009.438-1.188.75s-.269.63-.344.969c-.15.677-.219 1.476-.219 2.28s.068 1.605.219 2.282c.075.339.165.625.344.938s.563.78 1.188.78h4v-2H2.469c-.022-.065-.042-.06-.063-.155-.1-.447-.156-1.15-.156-1.844s.057-1.396.156-1.844c.02-.088.042-.092.063-.156H6V4H2zm8 0v2h3.531c.021.064.043.068.063.156.1.448.156 1.149.156 1.844s-.057 1.396-.156 1.844c-.021.096-.041.09-.063.156H10v2h4c.625 0 1.009-.47 1.188-.781s.269-.6.344-.938c.15-.678.219-1.476.219-2.281s-.068-1.604-.219-2.281c-.075-.34-.165-.656-.344-.97S14.625 4 14 4h-4zM5.719 7c-.523.074-.949.602-.875 1.125S5.477 9.074 6 9h4c.528.01 1-.472 1-1s-.472-1.007-1-1H6a.593.593 0 0 0-.188 0h-.093z"/>',
+		'ltr': '<path d="M10.313 1.937c-.98 0-1.752.284-2.344.813-.592.529-.906 1.228-.906 2.094 0 .811.275 1.467.781 1.969.506.497 1.227.792 2.156.906V14h2V3h1v11h1V1.939zM2 4v8l4-4z"/>',
+		'maximize': '<path d="M2 7l1.75-1.75-2-2L0 5V0h5L3.25 1.75l2 2L7 2v5H2zm9 9l1.75-1.75-2-2L9 14V9h5l-1.75 1.75 2 2L16 11v5h-5zm-6 0l-1.75-1.75 2-2L7 14V9H2l1.75 1.75-2 2L0 11v5h5zm6-16l1.75 1.75-2 2L9 2v5h5l-1.75-1.75 2-2L16 5V0h-5z"/>',
+		'orderedlist': '<path d="M6 2h9v2H6zm0 5h9v2H6zm0 5h9v2H6zm-2.799.846q.392.1.594.352.205.25.205.636 0 .576-.441.877-.441.298-1.287.298-.298 0-.599-.05-.298-.046-.591-.142v-.77q.28.14.555.212.277.07.545.07.396 0 .607-.137.212-.138.212-.394 0-.265-.218-.4-.215-.137-.638-.137h-.4v-.644h.421q.376 0 .56-.116.185-.12.185-.36 0-.224-.18-.346-.178-.122-.505-.122-.242 0-.488.055-.246.054-.49.16v-.731q.295-.083.586-.125.29-.041.57-.041.756 0 1.13.249.375.246.375.744 0 .34-.179.558-.179.215-.529.304zm-.905-3.609H4v.734H1.186v-.734L2.599 7.99q.19-.172.28-.335.091-.163.091-.34 0-.272-.184-.438-.182-.166-.485-.166-.234 0-.511.101-.278.099-.594.296v-.851q.337-.112.667-.169.329-.06.645-.06.696 0 1.08.307.386.306.386.853 0 .317-.163.592-.164.272-.688.731l-.827.726zM1.228 4.276h.903V1.714l-.927.19V1.21l.922-.191h.971v3.258H4v.706H1.228v-.706z"/>',
+		'outdent': '<path d="M1 1h14v2H1zm0 4h9v2H1zm0 4h9v2H1zm0 4h14v2H1zm10-5l4-3v6z"/>',
+		'paste': '<path d="M4.406 0A.5.5 0 0 0 4 .5V1H1.5a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5H6v2.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V7.594a.492.492 0 0 0 0-.094.436.436 0 0 0 0-.125.916.916 0 0 0-.031-.063v-.031a.749.749 0 0 0-.063-.063.749.749 0 0 0-.063-.063l-2.875-2.844a.498.498 0 0 0-.125-.156A.498.498 0 0 0 11.5 4H10V1.5a.5.5 0 0 0-.5-.5H7V.5a.5.5 0 0 0-.5-.5h-2a.492.492 0 0 0-.094 0c-.239.045.032-.003 0 0zM2 2h1v.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5V2h1v2H6.5a.64.64 0 0 0-.062 0 .493.493 0 0 0-.094.031.474.474 0 0 0-.125.063l-.031.031-.031.031a.916.916 0 0 0-.063.031.47.47 0 0 0-.031.094l-.031.031A.506.506 0 0 0 6 4.5V11H2V2zm5 3h4v2.5a.5.5 0 0 0 .5.5H14v6H7v-2.406a.492.492 0 0 0 0-.094V5zm5 .688L13.313 7H12V5.688zM4.406 0A.5.5 0 0 0 4 .5V1H1.5a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5h5a.5.5 0 0 0 .5-.5V5h2.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5H7V.5a.5.5 0 0 0-.5-.5h-2a.492.492 0 0 0-.094 0c-.239.045.032-.003 0 0zM2 2h1v.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5V2h1v2H6.5a.5.5 0 0 0-.5.5V11H2V2zm4.406 2A.5.5 0 0 0 6 4.5v10a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V7.594a.492.492 0 0 0 0-.094.331.331 0 0 0 0-.063v-.063a.916.916 0 0 0-.031-.063V7.28a.523.523 0 0 0-.094-.094l-.031-.031-2.875-2.844a.498.498 0 0 0-.125-.156A.503.503 0 0 0 11.5 4h-5a.492.492 0 0 0-.094 0c-.239.045.032-.003 0 0zM7 5h4v2.5a.5.5 0 0 0 .5.5H14v6H7V5zm5 .688L13.313 7H12V5.688zM8 12h5v1H8v-1zm0-2h5v1H8v-1zm0-2h5v1H8V8zm0-2h3v1H8V6z"/>',
+		'pastetext': '<path d="M4.406 0A.5.5 0 0 0 4 .5V1H1.5a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5H6v2.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V7.594a.492.492 0 0 0 0-.094.436.436 0 0 0 0-.125.916.916 0 0 0-.031-.063v-.031a.749.749 0 0 0-.063-.063.749.749 0 0 0-.063-.063l-2.875-2.844a.498.498 0 0 0-.125-.156A.498.498 0 0 0 11.5 4H10V1.5a.5.5 0 0 0-.5-.5H7V.5a.5.5 0 0 0-.5-.5h-2a.492.492 0 0 0-.094 0zM2 2h1v.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5V2h1v2H6.5a.64.64 0 0 0-.062 0 .493.493 0 0 0-.094.031.474.474 0 0 0-.125.063l-.031.031-.031.031a.916.916 0 0 0-.063.031.47.47 0 0 0-.031.094l-.031.031A.506.506 0 0 0 6 4.5V11H2V2zm5 3h4v2.5a.5.5 0 0 0 .5.5H14v6H7v-2.406a.492.492 0 0 0 0-.094V5zm5 .688L13.313 7H12V5.688zM4.406 0A.5.5 0 0 0 4 .5V1H1.5a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5h5a.5.5 0 0 0 .5-.5V5h2.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5H7V.5a.5.5 0 0 0-.5-.5h-2a.492.492 0 0 0-.094 0zM2 2h1v.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5V2h1v2H6.5a.5.5 0 0 0-.5.5V11H2V2zm4.406 2A.5.5 0 0 0 6 4.5v10a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V7.594a.492.492 0 0 0 0-.094.331.331 0 0 0 0-.063v-.062a.916.916 0 0 0-.031-.063v-.031a.523.523 0 0 0-.094-.094l-.031-.031-2.875-2.844a.498.498 0 0 0-.125-.156A.5.5 0 0 0 11.5 4h-5a.492.492 0 0 0-.094 0zM7 5h4v2.5a.5.5 0 0 0 .5.5H14v6H7V5zm5 .688L13.313 7H12V5.688z"/>',
+		'print': '<path d="M4 1v3H1v8h2V6h10v6h2V4h-3V1zm1 1h6v2H5zM4 7v8h8V7zm1 1h6v6H5zm1 1v1h4V9zm0 2v1h4v-1z"/>',
+		'quote': '<path d="M8 2.013c-1.998 0-3.818.382-5.188 1.125S.499 5.054.499 6.513c0 1.237.926 2.345 2.281 3.156s3.197 1.344 5.219 1.344c.344 0 .563.019.906 0l5.875 2.938c.377.18.854-.32.656-.688l-1.813-3.656c1.242-.79 1.875-2.014 1.875-3.094 0-1.46-.943-2.632-2.313-3.375S9.998 2.013 8 2.013z"/>',
+		'redo': '<path d="M9 7l5-5v5z"/><path d="M9.553 2.205c1 .268 1.932.796 2.69 1.553l.706.707-1.414 1.414-.707-.707a3.995 3.995 0 0 0-3.863-1.035 3.995 3.995 0 0 0-2.828 2.828 3.995 3.995 0 0 0 1.035 3.863l.707.707-1.414 1.414-.707-.707a6.003 6.003 0 0 1-1.553-5.795 6.003 6.003 0 0 1 7.348-4.242z"/>',
+		'removeformat': '<path d="M8.781 2l-.125.125L3.781 7l-.125.125-3 3-.313.313.25.344 3 4 .156.219h2.47l.125-.156 3-3 .313-.313 4.688-4.688.313-.313-.25-.344-3-4-.156-.188H8.781zm.407 1h.594l-4 4h-.594l4-4zm1.75.25l2.406 3.188-4.281 4.28-2.406-3.187 4.281-4.281z"/>',
+		'right': '<path d="M1 1h14v2H1zm4 4h10v2H5zM1 9h14v2H1zm4 4h10v2H5z"/>',
+		'rtl': '<path d="M5.344 2.001c-.98 0-1.783.284-2.375.813-.592.529-.875 1.227-.875 2.093 0 .811.244 1.467.75 1.969.506.497 1.227.792 2.156.906V14h2V3.001L8 3v11h1V2zM14 4l-4 4 4 4z"/>',
+		'size': '<path d="M12.5.656L10 4h5L12.5.656zM4.594 4.5a49.476 49.476 0 0 0-.875 1.906c-.277.65-.581 1.334-.875 2.063-.286.729-.572 1.52-.875 2.344S1.338 12.53 1 13.5h2.094c.095-.313.2-.64.313-.97.121-.328.262-.64.375-.968h3.5c.113.329.231.64.344.969.121.329.217.656.313.969h2.188c-.338-.971-.666-1.864-.969-2.688s-.611-1.615-.906-2.344a56.045 56.045 0 0 0-.844-2.063c-.286-.66-.581-1.282-.875-1.906H4.594zM10 6l2.5 3.313L15 6h-5zm-4.5.53c.052.13.132.307.219.532.086.225.2.486.313.78.121.296.245.614.375.97s.268.734.406 1.125H4.25c.139-.391.245-.77.375-1.125.139-.355.293-.674.406-.97s.194-.555.281-.78c.087-.224.145-.401.188-.531z"/>',
+		'source': '<path d="M4.937 3.939L1 8.499l3.937 4.564L6 12 3 8.499 6 5zm6.126 0L10 5.002l3 3.503-3 3.497 1.063 1.063L15 8.505z"/>',
+		'strike': '<text x="50%" y="50%" text-anchor="middle" dy=".5ex" font-family="Dejavu Sans, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold">S</text><path d="M1 7v1h14V7H1z"/>',
+		'subscript': '<path d="M11 10v1h3v1h-3v3h4v-1h-3v-1h3v-3zM1 3l3 5-3 5h2l3-5H4l3 5h2L6 8l3-5H7L4 8h2L3 3z"/>',
+		'superscript': '<path d="M11 1v1h3v1h-3v3h4V5h-3V4h3V1zM1 3l3 5-3 5h2l3-5H4l3 5h2L6 8l3-5H7L4 8h2L3 3z"/>',
+		'table': '<path d="M1 2h14v2H1zm0 2v10h14V4H1zm1 1h3.5v2H2V5zm4.5 0h3v2h-3V5zm4 0H14v2h-3.5V5zM2 8h3.5v2H2V8zm4.5 0h3v2h-3V8zm4 0H14v2h-3.5V8zM2 11h3.5v2H2v-2zm4.5 0h3v2h-3v-2zm4 0H14v2h-3.5v-2z"/>',
+		'time': '<path d="M8 0C3 0 0 4 0 8s3 8 8 8 8-4 8-8-3-8-8-8zm0 2c3.461 0 6 2.539 6 6s-2.539 6-6 6c-3.46 0-6-2.539-6-6s2.54-6 6-6zM7 3v6l2.5 2L11 9.5 9 8V3z"/>',
+		'underline': '<text x="50%" y="50%" text-anchor="middle" dy=".5ex" font-family="Dejavu Sans, Helvetica, Arial, sans-serif" font-weight="bold" font-size="15" text-decoration="underline">U</text>',
+		'undo': '<path d="M2 7h5L2 2z"/><path d="M6.447 2.205c-1 .268-1.932.796-2.69 1.553l-.706.707 1.414 1.414.707-.707a3.995 3.995 0 0 1 3.863-1.035 3.995 3.995 0 0 1 2.828 2.828 3.995 3.995 0 0 1-1.035 3.863l-.707.707 1.414 1.414.707-.707a6.003 6.003 0 0 0 1.553-5.795 6.003 6.003 0 0 0-7.348-4.242z"/>',
+		'unlink': '<path d="M2 4c-.625 0-1.009.438-1.188.75s-.269.63-.344.969c-.15.677-.219 1.476-.219 2.28s.068 1.605.219 2.282c.075.339.165.625.344.938s.563.78 1.188.78h4v-2H2.469c-.022-.065-.042-.06-.063-.155-.1-.447-.156-1.15-.156-1.844s.057-1.396.156-1.844c.02-.088.042-.092.063-.156H6V4H2zm8 0v2h3.531c.021.064.043.068.063.156.1.448.156 1.149.156 1.844s-.057 1.396-.156 1.844c-.021.095-.041.09-.063.156H10v2h4c.625 0 1.009-.47 1.188-.781s.269-.6.344-.938c.15-.678.219-1.476.219-2.281s-.068-1.604-.219-2.281c-.075-.34-.165-.656-.344-.97S14.625 4 14 4h-4z"/>',
+		'youtube': '<path d="M2 2C1 2 0 3 0 4v8c0 1 1 2 2 2h12c1 0 2-1 2-2V4c0-1-1-2-2-2H2zm4 3l6 3-6 3V5z"/>'
+	};
+
+	sceditor.icons.monocons = function () {
+		var nodes = {};
+		var colorPath;
+
+		return {
+			create: function (command) {
+				if (command in icons) {
+					nodes[command] = sceditor.dom.parseHTML(
+						'<svg xmlns="http://www.w3.org/2000/svg" ' +
+							'viewbox="0 0 16 16" unselectable="on">' +
+								icons[command] +
+						'</svg>'
+					).firstChild;
+
+					if (command === 'color') {
+						colorPath = nodes[command].querySelector('.sce-color');
+					}
+				}
+
+				return nodes[command];
+			},
+			update: function (isSourceMode, currentNode) {
+				if (colorPath) {
+					var color = 'inherit';
+
+					if (!isSourceMode && currentNode) {
+						color = currentNode.ownerDocument
+							.queryCommandValue('forecolor');
+					}
+
+					dom.css(colorPath, 'fill', color);
+				}
+			},
+			rtl: function (isRtl) {
+				var gripNode = nodes.grip;
+
+				if (gripNode) {
+					var transform = isRtl ? 'scaleX(-1)' : '';
+
+					dom.css(gripNode, 'transform', transform);
+					dom.css(gripNode, 'msTransform', transform);
+					dom.css(gripNode, 'webkitTransform', transform);
+				}
+			}
+		};
+	};
+
+	sceditor.icons.monocons.icons = icons;
+})(document, sceditor);

+ 68 - 0
public/js/sc/languages/ar.js

@@ -0,0 +1,68 @@
+/**
+ * @author Atramez_Zeton http://onyx-sy.net
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['ar'] = {
+		'Bold': 'عريض',
+		'Italic': 'مائل',
+		'Underline': 'خط من الأسفل',
+		'Strikethrough': 'خط في المنتصف',
+		'Subscript': 'حرف منخفض',
+		'Superscript': 'حرف مرتفع',
+		'Align left': 'انحياز إلى اليسار',
+		'Center': 'توسط',
+		'Align right': 'انحياز إالى اليمين',
+		'Justify': 'ملأ السطر',
+		'Font Name': 'نوع الخط',
+		'Font Size': 'حجم الخط',
+		'Font Color': 'لون الخط',
+		'Remove Formatting': 'ازالة التعديلات',
+		'Cut': 'قص',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ctrl/Cmd-X متصفحك لا يدعم اوامر القص الرجاء استخدام اختصارات لوحة التحكم',
+		'Copy': 'نسخ',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ctrl/Cmd-C متصفحك لا يدعم اوامر النسخ الرجاء استخدام اختصارات لوحة التحكم',
+		'Paste': 'لصق',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ctrl/Cmd-V متصفحك لا يدعم اوامر اللصق الرجاء استخدام اختصارات لوحة التحكم',
+		'Paste your text inside the following box:': 'قم بلصق نصّك في المربع',
+		'Paste Text': 'الصق النص',
+		'Bullet list': 'قائمة نقطية',
+		'Numbered list': 'قائمة مرقمة',
+		'Undo': 'تراجع',
+		'Redo': 'تقدم',
+		'Rows:': 'اسطر',
+		'Cols:': 'اعمدة',
+		'Insert a table': 'ادرج جدول',
+		'Insert a horizontal rule': 'ادرج مسطرة افقية',
+		'Code': 'كود',
+		'Width (optional):': 'عرض (اختياري)',
+		'Height (optional):': 'ارتفاع (اختياري)',
+		'Insert an image': 'ادرج صورة',
+		'E-mail:': 'بريد الكتروني',
+		'Insert an email': 'ادرج بريدا الكترونيا',
+		'URL:': 'وصلة موقع',
+		'Insert a link': 'ادرج وصلة لموقع',
+		'Unlink': 'ازالة الوصلة',
+		'More': 'المزيد',
+		'Insert an emoticon': 'ادرج وجها',
+		'Video URL:': 'وصلة فيديو',
+		'Insert': 'ادرج',
+		'Insert a YouTube video': 'ادرج وصلة فيديو يوتيوب',
+		'Insert current date': 'ادرج التاريخ الحالي',
+		'Insert current time': 'ادرج الوقت الحالي',
+		'Print': 'اطبع',
+		'View source': 'اظهر المصدر',
+		'Description (optional):': 'الوصف (اختياري)',
+		'Enter the image URL:': 'ضع وصلة الصورة',
+		'Enter the e-mail address:': 'ضع عنوان البريد الإلكتروني',
+		'Enter the displayed text:': 'ضع النص الذي تريد اظهاره',
+		'Enter URL:': 'ضع وصلة موقع',
+		'Enter the YouTube video URL or ID:': 'ضع وصلة فيديو يوتيوب او رقم الفيديو',
+		'Insert a Quote': 'ادرج اقتباسا',
+		'Invalid YouTube video': 'هذا الفيديو غير صالح',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 68 - 0
public/js/sc/languages/ca.js

@@ -0,0 +1,68 @@
+/**
+ * @author Fran Sobrino
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['ca'] = {
+		'Bold': 'Negrita',
+		'Italic': 'Cursiva',
+		'Underline': 'Subratlla',
+		'Strikethrough': 'Ratllar',
+		'Subscript': 'Sub\u00edndice',
+		'Superscript': 'Super\u00edndice',
+		'Align left': 'Alinear a l\'Esquerra',
+		'Center': 'Centrar',
+		'Align right': 'Alinear a la dreta',
+		'Justify': 'Justificar',
+		'Font Name': 'Tipus de Lletra',
+		'Font Size': 'Mida de Lletra',
+		'Font Color': 'Color de Font',
+		'Remove Formatting': 'Treure Formats',
+		'Cut': 'Tallar',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-X',
+		'Copy': 'Copiar',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-C',
+		'Paste': 'Pegar',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Pega o texto dentro do seguinte recadro',
+		'Paste Text': 'Pegar Texto',
+		'Bullet list': 'Llista d\'Vinyetes',
+		'Numbered list': 'Llista numerada',
+		'Undo': 'Desfer',
+		'Redo': 'Refer',
+		'Rows:': 'Files',
+		'Cols:': 'Columnes',
+		'Insert a table': 'Inserir una taula',
+		'Insert a horizontal rule': 'Insereix una Regla horitzontal',
+		'Code': 'C\u00f3digo',
+		'Width (optional):': 'Ample (Opcional)',
+		'Height (optional):': 'Alçada (Opcional)',
+		'Insert an image': 'Insereix una imatge',
+		'E-mail:': 'Correu electrònic',
+		'Insert an email': 'Insereix un Email',
+		'URL:': 'URL',
+		'Insert a link': 'Inserir un enllaç',
+		'Unlink': 'Treure un enllaç',
+		'More': 'Més',
+		'Insert an emoticon': 'Inserir un emoticon',
+		'Video URL:': 'URL del V\u00eddeo',
+		'Insert': 'Insereix',
+		'Insert a YouTube video': 'Insereix un v\u00eddeo de YouTube',
+		'Insert current date': 'Insereix data actual',
+		'Insert current time': 'Insereix hora actual',
+		'Print': 'Imprimir',
+		'View source': 'Veure C\u00f3digo',
+		'Description (optional):': 'Descripci\u00f3 (Opcional):',
+		'Enter the image URL:': 'Ingressar la URL de la imatge:',
+		'Enter the e-mail address:': 'Ingressar el correu electr\u00f3nico:',
+		'Enter the displayed text:': 'Ingressar el texto mostrat:',
+		'Enter URL:': 'Entrada URL:',
+		'Enter the YouTube video URL or ID:': 'Entrada URL ou ID de YouTube',
+		'Insert a Quote': 'v Insereix',
+		'Invalid YouTube video': 'V\u00eddeo de YouTube Inv\u00e1lido',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 68 - 0
public/js/sc/languages/cn.js

@@ -0,0 +1,68 @@
+/**
+ * @author <Your Name> <Your e-mail/Website if you would like>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['cn'] = {
+		'Bold': '粗体',
+		'Italic': '斜体',
+		'Underline': '下划线',
+		'Strikethrough': '删除线',
+		'Subscript': '下标',
+		'Superscript': '上标',
+		'Align left': '靠左对齐',
+		'Center': '置中',
+		'Align right': '靠右对齐',
+		'Justify': '两端对齐',
+		'Font Name': '字体',
+		'Font Size': '字号',
+		'Font Color': '字色',
+		'Remove Formatting': '格式清除',
+		'Cut': '剪切',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': '您的浏览器不支持剪切命令,请使用快捷键 Ctrl/Cmd-X',
+		'Copy': '拷贝',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': '您的浏览器不支持拷贝命令,请使用快捷键 Ctrl/Cmd-C',
+		'Paste': '粘贴',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': '您的浏览器不支持粘贴命令,请使用快捷键 Ctrl/Cmd-V',
+		'Paste your text inside the following box:': '请在下面贴入您的文本',
+		'Paste Text': '粘贴纯文本',
+		'Bullet list': '符号列表',
+		'Numbered list': '编号列表',
+		'Undo': '恢复',
+		'Redo': '撤消',
+		'Rows:': '行数',
+		'Cols:': '列数',
+		'Insert a table': '插入表格',
+		'Insert a horizontal rule': '插入分隔符',
+		'Code': '代码',
+		'Width (optional):': '宽度(选填)',
+		'Height (optional):': '高度(选填)',
+		'Insert an image': '插入图片',
+		'E-mail:': 'Email地址',
+		'Insert an email': '插入Email地址',
+		'URL:': '网址',
+		'Insert a link': '插入链接',
+		'Unlink': '取消链接',
+		'More': '更多',
+		'Insert an emoticon': '插入表情符号',
+		'Video URL:': '视频地址',
+		'Insert': '插入',
+		'Insert a YouTube video': '插入YouTube视频',
+		'Insert current date': '插入当前日期',
+		'Insert current time': '插入当前时间',
+		'Print': '打印',
+		'View source': '查看代码',
+		'Description (optional):': '描述(选填)',
+		'Enter the image URL:': '输入图片地址',
+		'Enter the e-mail address:': '输入email地址',
+		'Enter the displayed text:': '输入显示文字',
+		'Enter URL:': '输入网址',
+		'Enter the YouTube video URL or ID:': '输入YouTube地址或编号',
+		'Insert a Quote': '插入引用',
+		'Invalid YouTube video': '无效的YouTube视频',
+
+		dateFormat: 'year-month-day'
+	};
+})();

+ 71 - 0
public/js/sc/languages/cs.js

@@ -0,0 +1,71 @@
+/**
+ * @author Daniel Vítek danielvitek1@gmail.com danvitek.cz
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['cs'] = {
+		'Bold': 'Tučné',
+		'Italic': 'Kurzíva',
+		'Underline': 'Podtržené',
+		'Strikethrough': 'Přeškrtnuté',
+		'Subscript': 'Dolní index',
+		'Superscript': 'Horní index',
+		'Align left': 'Zarovnat vlevo',
+		'Center': 'Zarovnat na střed',
+		'Align right': 'Zarovnat vpravo',
+		'Justify': 'Zarovnat do bloku',
+		'Font Name': 'Výběr písma',
+		'Font Size': 'Velikost písma',
+		'Font Color': 'Barva písma',
+		'Remove Formatting': 'Vymazat formátování',
+		'Cut': 'Vyjmout',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Váš prohlížeč nepodporuje tento příkaz, použijte CTRL+X',
+		'Copy': 'Kopírovat',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Váš prohlížeč nepodporuje tento příkaz, použijte CTRL+C',
+		'Paste': 'Vložit',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Váš prohlížeč nepodporuje tento příkaz, použijte CTRL+V',
+		'Paste your text inside the following box:': 'Vložte Váš text do následujícího pole',
+		'Paste Text': 'Vložit text',
+		'Bullet list': 'Seznam',
+		'Numbered list': 'Číslovaný seznam',
+		'Undo': 'Zpět',
+		'Redo': 'Vpřed',
+		'Rows:': 'Řádků',
+		'Cols:': 'Buněk',
+		'Insert a table': 'Vložit tabulku',
+		'Insert a horizontal rule': 'Vložit vodorovnou čáru',
+		'Code': 'Vložit kód',
+		'Width (optional):': 'Šířka (volitelné)',
+		'Height (optional):': 'Výška (volitelné)',
+		'Insert an image': 'Vložit obrázek',
+		'E-mail:': 'E-mailová adresa',
+		'Insert an email': 'Vložit e-mail',
+		'URL:': 'Adresa',
+		'Insert a link': 'Vložit odkaz',
+		'Unlink': 'Zrušit odkaz',
+		'More': 'Více',
+		'Insert an emoticon': 'Vložit smajlíka',
+		'Video URL:': 'Adresa videa',
+		'Insert': 'Vložit',
+		'Insert a YouTube video': 'Vložte video z YouTube',
+		'Insert current date': 'Vložte aktuální datum',
+		'Insert current time': 'Vložte aktuální čas',
+		'Print': 'Vytisknout',
+		'View source': 'Zobrazit zdroj',
+		'Description (optional):': 'Popis (volitelné)',
+		'Enter the image URL:': 'Vložte adresu obrázku',
+		'Enter the e-mail address:': 'Vložte e-mailovou adresu',
+		'Enter the displayed text:': 'Vložte zobrazovaný text',
+		'Enter URL:': 'Vložte adresu',
+		'Enter the YouTube video URL or ID:': 'Vložte adresu YouTube videa nebo ID videa',
+		'Insert a Quote': 'Vložit citát',
+		'Invalid YouTube video': 'Neplatné YouTube video',
+		'Add indent': 'Posunout na další úroveň',
+		'Remove one indent': 'Posunout na předchozí úroveň',
+		'Maximize': 'Zobrazit přes celou obrazovku',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 61 - 0
public/js/sc/languages/de.js

@@ -0,0 +1,61 @@
+(function () {
+	'use strict';
+
+	sceditor.locale['de'] = {
+		'Bold': 'Fett',
+		'Italic': 'Kursiv',
+		'Underline': 'Unterstrichen',
+		'Strikethrough': 'Durchgestrichen',
+		'Subscript': 'Tiefgestellt',
+		'Superscript': 'Hochgestellt',
+		'Align left': 'Linksbündig ausrichten',
+		'Center': 'Zentrieren',
+		'Align right': 'Rechtsbündig ausrichten',
+		'Justify': 'Blocksatz',
+		'Font Name': 'Schriftname',
+		'Font Size': 'Schriftgröße',
+		'Font Color': 'Schriftfarbe',
+		'Remove Formatting': 'Formatierung entfernen',
+		'Cut': 'Ausschneiden',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ihr Browser erlaubt das Ausschneiden von Text nicht, bitte Nutzen Sie das Tastenkürzel Strg / Cmd-X',
+		'Copy': 'Kopieren',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ihr Browser erlaubt das Kopieren von Text nicht, bitte Nutzen Sie das Tastenkürzel Strg / Cmd-C',
+		'Paste': 'Einfügen',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ihr Browser erlaubt das Einfügen von Text nicht, bitte Nutzen Sie das Tastenkürzel Strg / Cmd-V',
+		'Paste your text inside the following box:': 'Fügen Sie Ihren Text in die folgende Box ein',
+		'Paste Text': 'Text einfügen',
+		'Bullet list': 'Aufzählungsliste',
+		'Numbered list': 'Nummerierte Liste',
+		'Add indent': 'Ebene hinzufügen',
+		'Remove one indent': 'Eine Ebene entfernen',
+		'Undo': 'Rückgängig machen',
+		'Redo': 'Wiederherstellen',
+		'Rows:': 'Zeilen',
+		'Cols:': 'Spalten',
+		'Insert a table': 'Tabelle einfügen',
+		'Insert a horizontal rule': 'Horizontale Linie einfügen',
+		'Code': 'Code',
+		'Insert a Quote': 'Zitat einfügen',
+		'Width (optional):': 'Breite (Optional)',
+		'Height (optional):': 'Höhe (Optional)',
+		'Insert an image': 'Ein Bild einfügen',
+		'E-mail:': 'E-Mail',
+		'Insert an email': 'E-Mail einfügen',
+		'URL:': 'URL',
+		'Insert a link': 'Link einfügen',
+		'Unlink': 'Link entfernen',
+		'More': 'Mehr',
+		'Left-to-Right': 'Links nach rechts',
+		'Right-to-Left': 'Rechts nach links',
+		'Insert an emoticon': 'Emoticon einfügen',
+		'Video URL:': 'Video URL',
+		'Insert': 'Einfügen',
+		'Insert a YouTube video': 'YouTube Video einfügen',
+		'Insert current date': 'Aktuelles Datum einfügen',
+		'Insert current time': 'Aktuelle Uhrzeit einfügen',
+		'Print': 'Drucken',
+		'Maximize': 'Maximieren',
+		'View source': 'Quelltext ansehen',
+		dateFormat: 'day.month.year'
+	};
+})();

+ 68 - 0
public/js/sc/languages/el.js

@@ -0,0 +1,68 @@
+/**
+ * @author Nikos Aggelis nikosaggelis@hotmail.gr
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['el'] = {
+		'Bold': 'Έντονα',
+		'Italic': 'Πλάγια',
+		'Underline': 'Υπογραμμισμένα',
+		'Strikethrough': 'Διαγραμμισμένα',
+		'Subscript': 'Δείκτης',
+		'Superscript': 'Εκθέτης',
+		'Align left': 'Αριστερή στοίχιση',
+		'Center': 'Κεντραρισμένα',
+		'Align right': 'Δεξιά στοίχιση',
+		'Justify': 'Πλήρης στοίχιση',
+		'Font Name': 'Γραμματοσειρά',
+		'Font Size': 'Μέγεθος',
+		'Font Color': 'Χρώμα',
+		'Remove Formatting': 'Αφαίρεση μορφοποίησης',
+		'Cut': 'Αποκοπή',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ο περιηγητής σας δεν επιτρέπει την εντολή αποκοπής. Παρακαλούμε χρησιμοποιήστε τη συντόμευση πληκτρολογίου Ctrl/Cmd-X',
+		'Copy': 'Αντιγραφή',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ο περιηγητής σας δεν επιτρέπει την εντολή αντιγραφής. Παρακαλούμε χρησιμοποιήστε τη συντόμευση πληκτρολογίου Ctrl/Cmd-C',
+		'Paste': 'Επικόλληση',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ο περιηγητής σας δεν επιτρέπει την εντολή επικόλλησης. Παρακαλούμε χρησιμοποιήστε τη συντόμευση πληκτρολογίου Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Επικολλήστε το κείμενό σας μέσα στο ακόλουθο πλαίσιο:',
+		'Paste Text': 'Επικόλληση κειμένου',
+		'Bullet list': 'Λίστα με κουκίδες',
+		'Numbered list': 'Λίστα με αρίθμηση',
+		'Undo': 'Αναίρεση',
+		'Redo': 'Επανάληψη',
+		'Rows:': 'Γραμμές',
+		'Cols:': 'Στήλες',
+		'Insert a table': 'Εισαγωγή πίνακα',
+		'Insert a horizontal rule': 'Εισαγωγή οριζόντιας γραμμής',
+		'Code': 'Κώδικας',
+		'Width (optional):': 'Πλάτος (Προαιρετικό)',
+		'Height (optional):': 'Ύψος (Προαιρετικό)',
+		'Insert an image': 'Εισαγωγή εικόνας',
+		'E-mail:': 'Ηλεκτρονικό ταχυδρομείο',
+		'Insert an email': 'Εισαγωγή email',
+		'URL:': 'Ηλεκτρονική διεύθυνση',
+		'Insert a link': 'Εισαγωγή συνδέσμου',
+		'Unlink': 'Κατάργηση σύνδεσης',
+		'More': 'Περισσότερα',
+		'Insert an emoticon': 'Εισαγωγή φατσούλας',
+		'Video URL:': 'Διεύθυνση βίντεο',
+		'Insert': 'Εισαγωγή',
+		'Insert a YouTube video': 'Εισαγωγή βίντεο YouTube',
+		'Insert current date': 'Εισαγωγή τρέχουσας ημερομηνίας',
+		'Insert current time': 'Εισαγωγή τρέχουσας ώρας',
+		'Print': 'Εκτύπωση',
+		'Maximize': 'Μεγιστοποίηση',
+		'View source': 'Προβολή πηγαίου κώδικα',
+		'Description (optional):': 'Περιγραφή (προαιρετικό)',
+		'Enter the image URL:': 'Εισάγετε τη διεύθυνση εικόνας',
+		'Enter the e-mail address:': 'Εισάγετε τη διεύθυνση e-mail',
+		'Enter the displayed text:': 'Εισάγετε το εμφανιζόμενο κείμενο',
+		'Enter URL:': 'Εισάγετε διεύθυνση',
+		'Enter the YouTube video URL or ID:': 'Εισάγετε τη διεύθυνση του βίντεο YouTube ή το ID',
+		'Insert a Quote': 'Εισαγωγή παράθεσης',
+		'Invalid YouTube video': 'Μη έγκυρο βίντεο YouTube',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 7 - 0
public/js/sc/languages/en-US.js

@@ -0,0 +1,7 @@
+(function () {
+	'use strict';
+
+	sceditor.locale['en-US'] = {
+		dateFormat: 'month/day/year'
+	};
+})();

+ 12 - 0
public/js/sc/languages/en.js

@@ -0,0 +1,12 @@
+(function () {
+	'use strict';
+
+	sceditor.locale['en-GB'] = {
+		'Font Color': 'Font Colour',
+		'Center': 'Centre',
+		dateFormat: 'day/month/year'
+	};
+
+	// set this as the default English locale
+	sceditor.locale['en'] = sceditor.locale['en-GB'];
+})();

+ 68 - 0
public/js/sc/languages/es.js

@@ -0,0 +1,68 @@
+/**
+ * @author <Maxpower> <maxpowerid@gmail.com/www.identi.li>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['es'] = {
+		'Bold': 'Negrita',
+		'Italic': 'Cursiva',
+		'Underline': 'Subrayar',
+		'Strikethrough': 'Tachar',
+		'Subscript': 'Sub\u00edndice',
+		'Superscript': 'Super\u00edndice',
+		'Align left': 'Alinear a la Izquierda',
+		'Center': 'Centrar',
+		'Align right': 'Alinear a la Derecha',
+		'Justify': 'Justificar',
+		'Font Name': 'Tipo de Letra',
+		'Font Size': 'Tama\u00f1o de Letra',
+		'Font Color': 'Color de Fuente',
+		'Remove Formatting': 'Quitar Formatos',
+		'Cut': 'Cortar',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Su navegador no acepta el comando cortar. Por favor, use la combinaci\u00f3n Ctrl/Cmd-X',
+		'Copy': 'Copiar',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Su navegador no acepta el comando copiar. Por favor, use la combinaci\u00f3n Ctrl/Cmd-C',
+		'Paste': 'Pegar',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Su navegador no acepta el comando pegar. Por favor, use la combinaci\u00f3n Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Pega el texto dentro del siguiente recuadro',
+		'Paste Text': 'Pegar Texto',
+		'Bullet list': 'Lista de Vi\u00f1etas',
+		'Numbered list': 'Lista Numerada',
+		'Undo': 'Deshacer',
+		'Redo': 'Rehacer',
+		'Rows:': 'Filas',
+		'Cols:': 'Columnas',
+		'Insert a table': 'Insertar una Tabla',
+		'Insert a horizontal rule': 'Insertar una Regla Horizontal',
+		'Code': 'C\u00f3digo',
+		'Width (optional):': 'Ancho (Opcional)',
+		'Height (optional):': 'Altura (Opcional)',
+		'Insert an image': 'Insertar una Imagen',
+		'E-mail:': 'E-mail',
+		'Insert an email': 'Insertar un Email',
+		'URL:': 'URL',
+		'Insert a link': 'Insertar un V\u00ednculo',
+		'Unlink': 'Quitar V\u00ednculo',
+		'More': 'M\u00e1s',
+		'Insert an emoticon': 'Insertar un emoticon',
+		'Video URL:': 'URL del V\u00eddeo',
+		'Insert': 'Insertar',
+		'Insert a YouTube video': 'Insertar un v\u00eddeo de YouTube',
+		'Insert current date': 'Insertar fecha actual',
+		'Insert current time': 'Insertar hora actual',
+		'Print': 'Imprimir',
+		'View source': 'Ver C\u00f3digo',
+		'Description (optional):': 'Descripci\u00f3n (Opcional):',
+		'Enter the image URL:': 'Ingresar la URL de la imagen:',
+		'Enter the e-mail address:': 'Ingresar el correo electr\u00f3nico:',
+		'Enter the displayed text:': 'Ingresar el texto mostrado:',
+		'Enter URL:': 'Ingresar URL:',
+		'Enter the YouTube video URL or ID:': 'Ingresar URL o ID de YouTube',
+		'Insert a Quote': 'Insertar Cita',
+		'Invalid YouTube video': 'Video de YouTube Inv\u00e1lido',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 57 - 0
public/js/sc/languages/et.js

@@ -0,0 +1,57 @@
+(function () {
+	'use strict';
+
+	sceditor.locale['et'] = {
+		'Bold': 'Rasvane',
+		'Italic': 'Kaldkiri',
+		'Underline': 'Allajoonitud',
+		'Strikethrough': 'Läbijoonitud',
+		'Subscript': 'Allindeks',
+		'Superscript': 'Ülaindeks',
+		'Align left': 'Joonad vasakule',
+		'Center': 'Joonda keskele',
+		'Align right': 'Joonda paremale',
+		'Justify': 'Joondus mõlemale poole',
+		'Font Name': 'Fondi nimi',
+		'Font Size': 'Fondi suurus',
+		'Font Color': 'Fondi värv',
+		'Remove Formatting': 'Eemalda vormindus',
+		'Cut': 'Lõika',
+		'Sinu veebilehitseja ei luba lõikamise käsu kasutamist. Palun kasuta kiirklahvi Ctrl/Cmd-X': '... Ctrl / Cmd-X',
+		'Copy': 'Kopeeri',
+		'Sinu veebilehitseja ei luba kopeerimise käsu kasutamist. Palun kasuta kiirklahvi Ctrl/Cmd-C': '... Ctrl / Cmd-C',
+		'Paste': 'Aseta',
+		'Sinu veebilehitseja ei luba asetamise käsu kasutamist. Palun kasuta kiirklahvi Ctrl/Cmd-V': '... Ctrl / Cmd-V',
+		'Paste your text inside the following box:': 'Aseta oma tekst järgneva tekstikasti sisse',
+		'Paste Text': 'Aseta tekstina',
+		'Bullet list': 'Nimekiri',
+		'Numbered list': 'Nummerdatud nimekiri',
+		'Undo': 'Samm tagasi',
+		'Redo': 'Samm edasi',
+		'Rows:': 'Read',
+		'Cols:': 'Veerud',
+		'Insert a table': 'Sisesta tabel',
+		'Insert a horizontal rule': 'Sisesta horisontaalne joon',
+		'Code': 'Kood',
+		'Insert a Quote': 'Sisesta tsitaat',
+		'Width (optional):': 'Laius (Valikuline)',
+		'Height (optional):': 'Kõrgus (Valikuline)',
+		'Insert an image': 'Sisesta pilt',
+		'E-mail:': 'E-post',
+		'Insert an email': 'Sisesta e-posti aadress',
+		'URL:': 'Link',
+		'Insert a link': 'Sisesta link',
+		'Unlink': 'Eemalda link',
+		'More': 'Veel',
+		'Insert an emoticon': 'Sisesta emotikon',
+		'Video URL:': 'Video link',
+		'Insert': 'Sisesta',
+		'Insert a YouTube video': 'Sisesta YouTube video',
+		'Insert current date': 'Sisesta praegune kuupäev',
+		'Insert current time': 'Sisesta praegune kellaaeg',
+		'Print': 'Prindi',
+		'View source': 'Vaata lähtekoodi',
+
+		dateFormat: 'day.month.year'
+	};
+})();

+ 69 - 0
public/js/sc/languages/fa.js

@@ -0,0 +1,69 @@
+// add locale:'fa', to your config options.
+// Translated By Ebad Ghafoory [info@ghafoory.com]
+// 2013/05/01
+(function () {
+	'use strict';
+
+	sceditor.locale['fa'] = {
+		'Bold': 'تیره',
+		'Italic': 'مورب',
+		'Underline': 'زیرخط',
+		'Strikethrough': 'خط خورده',
+		'Subscript': 'زیرنویس',
+		'Superscript': 'بالانویس',
+		'Align left': 'چپ چین',
+		'Center': 'وسط چین',
+		'Align right': 'راست چین',
+		'Justify': 'همخط',
+		'Font Name': 'نام قلم',
+		'Font Size': 'اندازه\u200cی نوشته',
+		'Font Color': 'رنگ نوشته',
+		'Remove Formatting': 'پاکسازی فرمت نوشته',
+		'Cut': 'برش',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'مرورگر شما اجازه برش توسط نرم\u200cافزار را نمی\u200cدهد. لطفا از دکمه\u200cهای ترکیبی Ctrl / Cmd-X استفاده کنید',
+		'Copy': 'کپی',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'مرورگر شما اجازه کپی کردن توسط نرم\u200cافزار را نمی\u200cدهد. لطفا از دکمه\u200cهای ترکیبی  Ctrl / Cmd-C استفاده کنید',
+		'Paste': 'چسباندن',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'مرورگر شما اجازه چسباندن توسط نرم\u200cافزار را نمی\u200cدهد. لطفا از دکمه\u200cهای ترکیبی  Ctrl / Cmd-V استفاده کنید',
+		'Paste your text inside the following box:': 'متن خود را در داخل کادر زیر بچسبانید',
+		'Paste Text': 'چسباندن متن',
+		'Bullet list': 'لیست',
+		'Numbered list': 'لیست عددی',
+		'Undo': 'حرکت قبل',
+		'Redo': 'حرکت بعد',
+		'Rows:': 'تعداد ردیف',
+		'Cols:': 'تعداد ستون',
+		'Insert a table': 'افزودن جدول',
+		'Insert a horizontal rule': 'افزودن خط افقی',
+		'Code': 'کد',
+		'Insert a Quote': 'افزودن نقل قول',
+		'Width (optional):': 'پهنا (دلخواه):',
+		'Height (optional):': 'ارتفاع (دلخواه):',
+		'Insert an image': 'افزودن عکس',
+		'E-mail:': 'ایمیل',
+		'Insert an email': 'افزودن ایمیل',
+		'URL:': 'آدرس اینترنتی',
+		'Insert a link': 'افزودن لینک',
+		'Unlink': 'حذف لینک',
+		'More': 'بیشتر',
+		'Insert an emoticon': 'افزودن شکلک',
+		'Video URL:': 'آدرس اینترنتی ویدیو',
+		'Insert': 'افزودن',
+		'Insert a YouTube video': 'افزودن فیلم از یوتوب',
+		'Insert current date': 'افزودن تاریخ اکنون',
+		'Insert current time': 'افزودن زمان اکنون',
+		'Print': 'چاپ',
+		'View source': 'مشاهده سورس',
+		'Description (optional):': 'توضیحات (دلخواه):',
+		'Enter the image URL:': 'آدرس اینترنتی عکس را وارد کنید:',
+		'Enter the e-mail address:': 'آدرس ایمیل را وارد کنید:',
+		'Enter the displayed text:': 'متن نمایش\u200cدهنده را وارد کنید:',
+		'Enter URL:': 'آدرس اینترنتی را وارد کنید:',
+		'Enter the YouTube video URL or ID:': 'آدرس اینترنتی فیلم یوتوب یا شناسه ویدیو را وارد کنید:',
+		'Invalid YouTube video': 'فیلم یوتوب غیر معتبر است',
+		'Right-to-Left': 'راست به چپ',
+		'Left-to-Right': 'چپ به راست',
+
+		dateFormat: 'year.month.day'
+	};
+})();

+ 70 - 0
public/js/sc/languages/fi.js

@@ -0,0 +1,70 @@
+/**
+ * @author Juho Räsänen https://github.com/RJuho
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['fi'] = {
+
+		'Bold': 'Lihavoitu',
+		'Italic': 'Kursivoitu',
+		'Underline': 'Alleviivattu',
+		'Strikethrough': 'Yliviivattu',
+		'Subscript': 'Alaindeksi',
+		'Superscript': 'Yläindeksi',
+		'Align left': 'Tasaa Vasemmalle',
+		'Center': 'Tasaa Keskelle',
+		'Align right': 'Tasaa Oikealle',
+		'Justify': 'Tasaa',
+		'Font Name': 'Fontti',
+		'Font Size': 'Tekstin Koko',
+		'Font Color': 'Tekstin Väri',
+		'Remove Formatting': 'Poista Muotoilu',
+		'Cut': 'Leikkaa',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Selaimesi ei salli leikkaus komentoa. Voit käyttää pikanäppäintä Ctrl/Cmd-X',
+		'Copy': 'Kopioi',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Selaimesi ei salli kopiointi komentoa. Voit käyttää pikanäppäintä Ctrl/Cmd-C',
+		'Paste': 'Liitä',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Selaimesi ei salli liittämis komentoa. Voit käyttää pikanäppäintä Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Liitä teksti laatikon sisään:',
+		'Paste Text': 'Liitä Teksti',
+		'Bullet list': 'Lista',
+		'Numbered list': 'Numeroitu Lista',
+		'Undo': 'Kumoa',
+		'Redo': 'Tee Uudelleen',
+		'Rows:': 'Rivit:',
+		'Cols:': 'Sarakkeet:',
+		'Insert a table': 'Lisää taulukko',
+		'Insert a horizontal rule': 'Lisää vaakasuuntainen sääntö',
+		'Code': 'Koodi',
+		'Width (optional):': 'Leveys (valinnainen):',
+		'Height (optional):': 'Korkeus (valinnainen):',
+		'Insert an image': 'Lisää kuva',
+		'E-mail:': 'Sähköpostiosoite:',
+		'Insert an email': 'Syötä sähköpostiosoite',
+		'URL:': 'URL:',
+		'Insert a link': 'Syötä linkki',
+		'Unlink': 'Poista linkitys',
+		'More': 'Lisää',
+		'Insert an emoticon': 'Lisää hymiö',
+		'Video URL:': 'Videon osoite:',
+		'Insert': 'Lisää',
+		'Insert a YouTube video': 'Lisää YouTube-video',
+		'Insert current date': 'Lisää nykyinen päivämäärä',
+		'Insert current time': 'Lisää nykyinen aika',
+		'Print': 'Tulosta',
+		'View source': 'Näytä lähdekoodi',
+		'Description (optional):': 'Kuvaus (valinnainen):',
+		'Enter the image URL:': 'Syötä kuvan URL-osoite:',
+		'Enter the e-mail address:': 'Syötä sähköpostiosoite:',
+		'Enter the displayed text:': 'Syötä näytettävä teksti:',
+		'Enter URL:': 'Anna URL-osoite:',
+		'Enter the YouTube video URL or ID:': 'Anna YouTube-videon URL-osoite tai ID:',
+		'Insert a Quote': 'Lisää lainaus',
+		'Invalid YouTube video': 'Virheellinen YouTube-video',
+		'Drop files here': 'Pudota tiedostot tähän',
+
+		dateFormat: 'day.month.year'
+	};
+})();

+ 70 - 0
public/js/sc/languages/fr.js

@@ -0,0 +1,70 @@
+
+// add locale:'fr', to your config options.
+
+(function () {
+	'use strict';
+
+	sceditor.locale['fr-FR'] = {
+		'Bold': 'Gras',
+		'Italic': 'Italique',
+		'Underline': 'Souligné',
+		'Strikethrough': 'Barré',
+		'Subscript': 'Indice',
+		'Superscript': 'Exposant',
+		'Align left': 'Aligner à gauche',
+		'Center': 'Centrer',
+		'Align right': 'Aligner à droite',
+		'Justify': 'Justifier',
+		'Font Name': 'Police',
+		'Font Size': 'Taille de police',
+		'Font Color': 'Couleur de police',
+		'Remove Formatting': 'Enlever le formatage',
+		'Cut': 'Couper',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Votre navigateur n\'autorise pas la commande \'Couper\'. Merci d\'utiliser le raccourcis clavier Ctrl/Cmd+X',
+		'Copy': 'Copier',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Votre navigateur n\'autorise pas la commande \'Copier\'. Merci d\'utiliser le raccourcis clavier Ctrl/Cmd+C',
+		'Paste': 'Coller',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Votre navigateur n\'autorise pas la commande \'Coller\'. Merci d\'utiliser le raccourcis clavier Ctrl/Cmd+V',
+		'Paste your text inside the following box:': 'Collez votre texte à l\'intérieur de ce bloc',
+		'Paste Text': 'Texte collé',
+		'Bullet list': 'Liste à puce',
+		'Numbered list': 'Liste numérotée',
+		'Undo': 'Annuler',
+		'Redo': 'Rétablir',
+		'Rows:': 'Lignes',
+		'Cols:': 'Colonnes',
+		'Insert a table': 'Insérer un tableau',
+		'Insert a horizontal rule': 'Insérer une ligne horizontale',
+		'Code': 'Code',
+		'Insert a Quote': 'Insérer une citation',
+		'Width (optional):': 'Largeur (Optionnelle)',
+		'Height (optional):': 'Hauteur (Optionnelle)',
+		'Insert an image': 'Insérer une image',
+		'E-mail:': 'Courriel',
+		'Insert an email': 'Insérer un courriel',
+		'URL:': 'URL',
+		'Insert a link': 'Insérer un lien',
+		'Unlink': 'Supprimer un lien',
+		'More': 'Plus',
+		'Insert an emoticon': 'Insérer une émoticône',
+		'Video URL:': 'URL Vidéo',
+		'Insert': 'Insérer',
+		'Insert a YouTube video': 'Insérer une vidéo YouTube',
+		'Insert current date': 'Insérer la date actuelle',
+		'Insert current time': 'Insérer l\'heure actuelle',
+		'Print': 'Imprimer',
+		'View source': 'Afficher le texte brut',
+		'Description (optional):': 'Description (Optionnelle)',
+		'Enter the image URL:': 'Entrez l\'URL de l\'image:',
+		'Enter the e-mail address:': 'Entrez le courriel:',
+		'Enter the displayed text:': 'Entrez le texte affiché:',
+		'Enter URL:': 'Entrez une URL:',
+		'Enter the YouTube video URL or ID:': 'Entrez l\'URL ou l\'ID de la vidéo YouTube:',
+		'Invalid YouTube video': 'Vidéo YouTube invalide',
+		'Right-to-Left': 'De droite à gauche',
+		'Left-to-Right': 'De gauche à droite',
+
+		dateFormat: 'day/month/year'
+	};
+	sceditor.locale['fr'] = sceditor.locale['fr-FR'];
+})();

+ 68 - 0
public/js/sc/languages/gl.js

@@ -0,0 +1,68 @@
+/**
+ * @author Fran Sobrino
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['gl'] = {
+		'Bold': 'Negrita',
+		'Italic': 'Cursiva',
+		'Underline': 'Subrayar',
+		'Strikethrough': 'Riscar',
+		'Subscript': 'Sub\u00edndice',
+		'Superscript': 'Super\u00edndice',
+		'Align left': 'Alinear á Esquerda',
+		'Center': 'Centrar',
+		'Align right': 'Alinear á Dereita',
+		'Justify': 'Xustificar',
+		'Font Name': 'Tipo de Letra',
+		'Font Size': 'Tama\u00f1o de Letra',
+		'Font Color': 'Cor de Fonte',
+		'Remove Formatting': 'Quitar Formatos',
+		'Cut': 'Cortar',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-X',
+		'Copy': 'Copiar',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-C',
+		'Paste': 'Pegar',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Pega o texto dentro do seguinte recadro',
+		'Paste Text': 'Pegar Texto',
+		'Bullet list': 'Lista de Vi\u00f1etas',
+		'Numbered list': 'Lista Numerada',
+		'Undo': 'Desfacer',
+		'Redo': 'Refacer',
+		'Rows:': 'Ringleiras',
+		'Cols:': 'Columnas',
+		'Insert a table': 'Engadir unha Tabla',
+		'Insert a horizontal rule': 'Engadir unha Regla Horizontal',
+		'Code': 'C\u00f3digo',
+		'Width (optional):': 'Ancho (Opcional)',
+		'Height (optional):': 'Altura (Opcional)',
+		'Insert an image': 'Engadir unha Imaxen',
+		'E-mail:': 'E-mail',
+		'Insert an email': 'Engadir un Email',
+		'URL:': 'URL',
+		'Insert a link': 'Engadir un V\u00ednculo',
+		'Unlink': 'Quitar V\u00ednculo',
+		'More': 'M\u00e1is',
+		'Insert an emoticon': 'Engadir un emoticon',
+		'Video URL:': 'URL do V\u00eddeo',
+		'Insert': 'Engadir',
+		'Insert a YouTube video': 'Engadir un v\u00eddeo de YouTube',
+		'Insert current date': 'Engadir data actual',
+		'Insert current time': 'Engadir hora actual',
+		'Print': 'Imprimir',
+		'View source': 'Ver C\u00f3digo',
+		'Description (optional):': 'Descripci\u00f3n (Opcional):',
+		'Enter the image URL:': 'Ingresar a URL da imaxen:',
+		'Enter the e-mail address:': 'Ingresar o correo electr\u00f3nico:',
+		'Enter the displayed text:': 'Ingresar o texto mostrado:',
+		'Enter URL:': 'Ingresar URL:',
+		'Enter the YouTube video URL or ID:': 'Ingresar URL ou ID de YouTube',
+		'Insert a Quote': 'Engadir Cita',
+		'Invalid YouTube video': 'V\u00eddeo de YouTube Inv\u00e1lido',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 69 - 0
public/js/sc/languages/hu.js

@@ -0,0 +1,69 @@
+/**
+ * @author Ángyán László <lacavale55@gmail.com>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ * @date 2013-08-11
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['hu'] = {
+		'Bold': 'Félkövér',
+		'Italic': 'Dőlt',
+		'Underline': 'Aláhúzva',
+		'Strikethrough': 'Áthúzva',
+		'Subscript': 'Alsó index',
+		'Superscript': 'Felső index',
+		'Align left': 'Balra zárt',
+		'Center': 'Középre zárt',
+		'Align right': 'Jobbra zárt',
+		'Justify': 'Sorkizárt',
+		'Font Name': 'Betűtípus',
+		'Font Size': 'Betű méret',
+		'Font Color': 'Betű szín',
+		'Remove Formatting': 'Formázás törlése',
+		'Cut': 'Kivágás',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'A böngésző biztonsági beállításai nem engedik a kivágást. Használd a Ctrl/Cmd+X billetyűket.',
+		'Copy': 'Másolás',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'A böngésző biztonsági beállításai nem engedik a másolást. Használd a Ctrl/Cmd+C billetyűket.',
+		'Paste': 'Beillesztés',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'A böngésző biztonsági beállításai nem engedik a beillesztést. Használd a Ctrl/Cmd+V billetyűket.',
+		'Paste your text inside the following box:': 'Illeszd be a szöveget a dobozba:',
+		'Paste Text': 'Szöveg beszúrása',
+		'Bullet list': 'Felsorolás',
+		'Numbered list': 'Sorszámozott felsorolás',
+		'Undo': 'Vissza',
+		'Redo': 'Mégis',
+		'Rows:': 'Sorok',
+		'Cols:': 'Oszlopok',
+		'Insert a table': 'Táblázat beszúrása',
+		'Insert a horizontal rule': 'Vízszintes vonal beszúrása',
+		'Code': 'Kód',
+		'Width (optional):': 'Szélesség (nem kötelező):',
+		'Height (optional):': 'Magasság (nem kötelező):',
+		'Insert an image': 'Illessz be egy képet',
+		'E-mail:': 'Email:',
+		'Insert an email': 'Illessz be egy email címet.',
+		'URL:': 'Honlap',
+		'Insert a link': 'Hivatkozás létrehozása',
+		'Unlink': 'Hivatkozás megszüntetése',
+		'More': 'Több',
+		'Insert an emoticon': 'Smiley beszúrása',
+		'Video URL:': 'Video link:',
+		'Insert': 'Beszúrás',
+		'Insert a YouTube video': 'Youtube video beszúrása',
+		'Insert current date': 'Szúrd be az aktuális dátumot',
+		'Insert current time': 'Szúrd be a jelenlegi időt',
+		'Print': 'Nyomtatás',
+		'View source': 'Forrás',
+		'Description (optional):': 'Hivatkozás szövege (nem kötelező)',
+		'Enter the image URL:': 'Kép URL beillesztése:',
+		'Enter the e-mail address:': 'Írd be az email címet:',
+		'Enter the displayed text:': 'Írd be a megjelenítendő szöveget:',
+		'Enter URL:': 'Írd be a linket:',
+		'Enter the YouTube video URL or ID:': 'Írd be a Youtube video URL-jét vagy azonosítóját',
+		'Insert a Quote': 'Idézet beszúrása',
+		'Invalid YouTube video': 'Érvénytelen Youtube link',
+
+		dateFormat: 'year.month.day.'
+	};
+})();

+ 68 - 0
public/js/sc/languages/id.js

@@ -0,0 +1,68 @@
+/**
+ * @author Sandy Irawan (sndbkct@gmail.com)
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['id'] = {
+		'Bold': 'Tebal',
+		'Italic': 'Miring',
+		'Underline': 'Garis Bawah',
+		'Strikethrough': 'Coret',
+		'Subscript': 'Tulisan dibawah garis',
+		'Superscript': 'Tulisan diatas garis',
+		'Align left': 'Rata Kiri',
+		'Center': 'Rata Tengah',
+		'Align right': 'Rata Kanan',
+		'Justify': 'Rata Kanan-Kiri',
+		'Font Name': 'Nama Fon',
+		'Font Size': 'Ukuran Fon',
+		'Font Color': 'Warna Fon',
+		'Remove Formatting': 'Hapus Format',
+		'Cut': 'Potong',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Browser Anda tidak memungkinkan perintah cut. Silakan gunakan shortcut keyboard Ctrl / Cmd-X ',
+		'Copy': 'Salin',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Browser Anda tidak memungkinkan perintah copy. Silakan gunakan shortcut keyboard Ctrl / Cmd-C ',
+		'Paste': 'Rekatkan',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Browser Anda tidak memungkinkan perintah paste. Silakan gunakan shortcut keyboard Ctrl / Cmd-V ',
+		'Paste your text inside the following box:': 'Rekatkan teks Anda dalam kotak berikut:',
+		'Paste Text': 'Rekatkan Teks',
+		'Bullet list': 'Daftar Bullet',
+		'Numbered list': 'Daftar Nomor',
+		'Undo': 'Kembalikan',
+		'Redo': 'Ulangi',
+		'Rows:': 'Baris',
+		'Cols:': 'Kolom',
+		'Insert a table': 'Sisipkan sebuah tabel',
+		'Insert a horizontal rule': 'Sisipkan aturan horisontal',
+		'Code': 'Kode',
+		'Width (optional):': 'Lebar (opsional)',
+		'Height (optional):': 'Tinggi (opsional)',
+		'Insert an image': 'Sisipkan Gambar',
+		'E-mail:': 'Surel',
+		'Insert an email': 'Sisipkan surel',
+		'URL:': 'URL',
+		'Insert a link': 'Sisipkan link',
+		'Unlink': 'Buang Link',
+		'More': 'Lainnya',
+		'Insert an emoticon': 'Sisipkan emotikon',
+		'Video URL:': 'URL Video',
+		'Insert': 'Sisipkan',
+		'Insert a YouTube video': 'Sisipkan video Youtube',
+		'Insert current date': 'Sisipkan tanggal sekarang',
+		'Insert current time': 'Sisipkan waktu sekarang',
+		'Print': 'Print',
+		'View source': 'Lihat sumber',
+		'Description (optional):': 'Deskripsi (opsional)',
+		'Enter the image URL:': 'Masukkan URL gambar',
+		'Enter the e-mail address:': 'Masukkan alamat surel',
+		'Enter the displayed text:': 'Masukkan teks yang ditampilkan',
+		'Enter URL:': 'Masukkan URL',
+		'Enter the YouTube video URL or ID:': 'Masukkan URL video YouTube atau ID',
+		'Insert a Quote': 'Sisipkan kutipan',
+		'Invalid YouTube video': 'Video YouTube yang tidak valid',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 72 - 0
public/js/sc/languages/it.js

@@ -0,0 +1,72 @@
+/**
+ * @author <Tropico> <www.mangiaconsapevole.com>
+ * @author Gianluca Guazzo
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['it-IT'] = {
+		'Bold': 'Grassetto',
+		'Italic': 'Corsivo',
+		'Underline': 'Sottolineato',
+		'Strikethrough': 'Barrato',
+		'Subscript': 'Pedice',
+		'Superscript': 'Apice',
+		'Align left': 'Allinea a sinistra',
+		'Center': 'Centrato',
+		'Align right': 'Allinea a destra',
+		'Justify': 'Giustificato',
+		'Font Name': 'Nome carattere',
+		'Font Size': 'Dimensione carattere',
+		'Font Color': 'Colore carattere',
+		'Remove Formatting': 'Rimuovi formattazione',
+		'Cut': 'Taglia',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Il tuo browser non permette il comando Taglia. Usa per favore la scorciatoia da tastiera Ctrl/Cmd-X',
+		'Copy': 'Copia',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Il tuo browser non permette il comando Copia. Usa per favore la scorciatoia da tastiera Ctrl/Cmd-C',
+		'Paste': 'Incolla',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Il tuo browser non permette il comando Incolla. Usa per favore la scorciatoia da tastiera Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Incolla il tuo testo dentro il seguente riquadro',
+		'Paste Text': 'Incolla Testo',
+		'Bullet list': 'Elenco puntato',
+		'Numbered list': 'Elenco numerato',
+		'Undo': 'Annulla',
+		'Redo': 'Ripeti',
+		'Rows:': 'Righe:',
+		'Cols:': 'Colonne:',
+		'Insert a table': 'Inserisci una tabella',
+		'Insert a horizontal rule': 'Inserisci riga orizzontale',
+		'Code': 'Codice',
+		'Width (optional):': 'Larghezza(opzionale):',
+		'Height (optional):': 'Altezza(opzionale):',
+		'Insert an image': 'Inserisci un\'immagine',
+		'E-mail:': 'E-mail:',
+		'Insert an email': 'Inserisci una email',
+		'URL:': 'URL:',
+		'Insert a link': 'Inserisci collegamento(link):',
+		'Unlink': 'Togli collegamento(link):',
+		'More': 'Di più',
+		'Insert an emoticon': 'Inserisci una emoticon',
+		'Video URL:': 'URL del video',
+		'Insert': 'Inserisci',
+		'Insert a YouTube video': 'Inserisci un video YouTube',
+		'Insert current date': 'Inserisci data corrente',
+		'Insert current time': 'Inserisci ora corrente',
+		'Print': 'Stampa',
+		'View source': 'Vedi codice sorgente',
+		'Description (optional):': 'Descrizione (opzionale):',
+		'Enter the image URL:': 'Inserisci URL dell\'immagine',
+		'Enter the e-mail address:': 'Inserisci indirizzo email',
+		'Enter the displayed text:': 'Inserisci testo visualizzato',
+		'Enter URL:': 'Inserisci URL',
+		'Enter the YouTube video URL or ID:': 'Inserisci URL o ID video di YouTube',
+		'Insert a Quote': 'Inserisci una citazione',
+		'Invalid YouTube video': 'Video YouTube invalido',
+
+		dateFormat: 'day-month-year'
+	};
+
+	// Set as the default Italian locale
+	sceditor.locale['it'] = sceditor.locale['it-IT'];
+})();

+ 71 - 0
public/js/sc/languages/ja.js

@@ -0,0 +1,71 @@
+/**
+ * @author <Yoshihiro Misawa> <myoshi321go@gmail.com>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['ja'] = {
+		'Bold': '太字',
+		'Italic': '斜字',
+		'Underline': '下線',
+		'Strikethrough': '取り消し線',
+		'Subscript': '下付き文字',
+		'Superscript': '上付き文字',
+		'Align left': '左揃え',
+		'Center': '中央揃え',
+		'Align right': '右揃え',
+		'Justify': '均等揃え',
+		'Font Name': 'フォント名',
+		'Font Size': 'フォントサイズ',
+		'Font Color': 'フォントの色',
+		'Remove Formatting': '書式解除',
+		'Cut': '切り取り',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'お使いのブラウザではカットコマンドを許可されていません。 キーボードショートカットの Ctrl/Cmd-X をお使いください。',
+		'Copy': 'コピー',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'お使いのブラウザではコピーコマンドを許可されていません。 キーボードショートカットの Ctrl/Cmd-C をお使いください。',
+		'Paste': '貼り付け',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'お使いのブラウザでは貼り付けコマンドを許可されていません。 キーボードショートカットの Ctrl/Cmd-V をお使いください。',
+		'Paste your text inside the following box:': '以下にテキストを貼り付けてください。',
+		'Paste Text': 'テキストを貼り付け',
+		'Bullet list': '箇条書き',
+		'Numbered list': '段落番号',
+		'Undo': '元に戻す',
+		'Redo': 'やり直す',
+		'Rows:': '行数',
+		'Cols:': '列数',
+		'Insert a table': '表を挿入',
+		'Insert a horizontal rule': '水平線を挿入',
+		'Code': 'コード',
+		'Width (optional):': '幅 (オプション)',
+		'Height (optional):': '高さ (オプション)',
+		'Insert an image': '画像を挿入',
+		'E-mail:': 'メールアドレス',
+		'Insert an email': 'メールアドレスを挿入',
+		//'URL:': ',
+		'Insert a link': 'リンクを挿入',
+		'Unlink': 'リンクを解除',
+		//'More': ',
+		'Insert an emoticon': '顔文字を挿入',
+		'Video URL:': '動画URL',
+		'Insert': '挿入',
+		'Insert a YouTube video': 'Youtubeを挿入',
+		'Insert current date': '現在の日付を挿入',
+		'Insert current time': '現在の時間を挿入',
+		'Print': '印刷',
+		'View source': 'ソースを表示',
+		'Description (optional):': '説明 (オプション)',
+		'Enter the image URL:': '画像URLを入力してください。',
+		'Enter the e-mail address:': 'メールアドレスを入力してください。',
+		'Enter the displayed text:': '表示テキストを入力してください。',
+		'Enter URL:': 'URLを入力してください。',
+		'Enter the YouTube video URL or ID:': 'Youtubeの動画URLまたはIDを入力してください。',
+		'Insert a Quote': '引用を挿入',
+		'Invalid YouTube video': '不正なYoutube動画',
+		'Left-to-Right': '左から右へ',
+		'Right-to-Left': '右から左へ',
+		'Maximize': '最大化',
+
+		dateFormat: 'year-month-day'
+	};
+})();

+ 68 - 0
public/js/sc/languages/lt.js

@@ -0,0 +1,68 @@
+/**
+ * @author Team from www.klaustukai.lt
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['lt'] = {
+		'Bold': 'Paryškintas',
+		'Italic': 'Pasvirasis',
+		'Underline': 'Pabraukti',
+		'Strikethrough': 'Perbraukti',
+		'Subscript': 'Parašyti sumažintas raides po žodžio',
+		'Superscript': 'Parašyti sumažintas raides virš žodžio',
+		'Align left': 'Kairysis lygiavimas',
+		'Center': 'Centrinis lygiavimas',
+		'Align right': 'Dešinysis lygiavimas',
+		'Justify': 'Išlygintas tekstas',
+		'Font Name': 'Šrifto pavadinimas',
+		'Font Size': 'Šrifto dydis',
+		'Font Color': 'Šrifto spalva',
+		'Remove Formatting': 'Panaikinti teksto formatavimą',
+		'Cut': 'Iškirpti',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Jūsų paieškos sistema neleidžia atlikti šios funkcijos. Norėdami iškirpti spauskite Ctrl/Cmd-x',
+		'Copy': 'Kopijuoti',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Jūsų paieškos sistema neleidžia atlikti šios komandos. Norėdami nukopijuoti spauskite Ctrl/Cmd - C',
+		'Paste': 'Įklijuoti',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Jūsų paieškos sistema neleidžia atlikti šios komandos. Norėdami įklijuoti spauskite Ctrl/Cmd - V',
+		'Paste your text inside the following box:': 'Įklijuokite tekstą nurodytoje vietoje',
+		'Paste Text': 'Įklijuoti tekstą',
+		'Bullet list': 'Sugrupuotas sąrašas',
+		'Numbered list': 'Sunumeruotas sąrašas',
+		'Undo': 'panaikinti',
+		'Redo': 'atitaisyti',
+		'Rows:': 'Eilutės',
+		'Cols:': 'Stulpeliai',
+		'Insert a table': 'Įterpti lentelę',
+		'Insert a horizontal rule': 'Įterpti horizontalią liniją',
+		'Code': 'Šalies kodas',
+		'Width (optional):': 'plotis (laisvai pasirenkamas)',
+		'Height (optional):': 'aukštis (laisvai pasirenkamas)',
+		'Insert an image': 'Įterpti nuotrauką',
+		'E-mail:': 'Elektroninis paštas',
+		'Insert an email': 'Įterpti elktroninio pašto nuorodą',
+		'URL:': 'Internetinės svetainės adresas:',
+		'Insert a link': 'Įterpti nuorodą',
+		'Unlink': 'Atjungti',
+		'More': 'Daugiau',
+		'Insert an emoticon': 'Įterpti šypsenėlę',
+		'Video URL:': 'Vaizdo klipo nuoroda',
+		'Insert': 'Įterpti',
+		'Insert a YouTube video': 'Įterpti Youtube vaizdo klipą',
+		'Insert current date': 'Įterpti esamą datą (diena-mėnuo-metai)',
+		'Insert current time': 'Įterpti esamą laiką',
+		'Print': 'Atspausdinti',
+		'View source': 'Peržiūrėti šaltinį',
+		'Description (optional):': 'Aprašymas (laisvai pasirenkamas)',
+		'Enter the image URL:': 'Įterpti nuotraukos adresą',
+		'Enter the e-mail address:': 'Įterpti elektroninio pašto adresą',
+		'Enter the displayed text:': 'Įvesti pavaizduotą tekstą',
+		'Enter URL:': 'Įvesti internetinį adresą',
+		'Enter the YouTube video URL or ID:': 'Įrašykite Youtube vaizdo klipo nuorodą ar ID',
+		'Insert a Quote': 'Įterpti citatą',
+		'Invalid YouTube video': 'YouTube vaizdo įrašas neveikia',
+
+		dateFormat: 'year-month-day'
+	};
+})();

+ 70 - 0
public/js/sc/languages/nb.js

@@ -0,0 +1,70 @@
+/**
+ * @author Katrine
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['nb-NO'] = {
+		'Bold': 'Fet',
+		'Italic': 'Kursiv',
+		'Underline': 'Understrek',
+		'Strikethrough': 'Gjennomstrek',
+		'Subscript': 'Senket',
+		'Superscript': 'Hevet',
+		'Align left': 'Sidestill til venstre',
+		'Center': 'Midstill',
+		'Align right': 'Sidestill til høyre',
+		'Justify': 'Normalt oppstilt',
+		'Font Name': 'Skriftype',
+		'Font Size': 'Skriftstørrelse',
+		'Font Color': 'skriftfarge',
+		'Remove Formatting': 'Fjern formatering',
+		'Cut': 'Klipp',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Nettleseren din kan ikke utføre klippe kommandoen. Vennligst bruk hurtigtasten Ctrl / Cmd-X',
+		'Copy': 'Kopier',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Nettleseren din kan ikke utføre kopier kommandoen. Vennligst bruk hurtigtasten Ctrl / Cmd-C',
+		'Paste': 'Lim',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Nettleseren din kan ikke utføre lime kommandoen. Vennligst bruk hurtigtasten Ctrl / Cmd-V',
+		'Paste your text inside the following box:': 'Lim inn teksten i den følgende boksen:',
+		'Paste Text': 'Lim inn tekst',
+		'Bullet list': 'Bullet liste',
+		'Numbered list': 'Nummerert liste',
+		'Undo': 'Angre',
+		'Redo': 'Gjør på nytt',
+		'Rows:': 'Rader',
+		'Cols:': 'Kolonner',
+		'Insert a table': 'Sett inn en tabell',
+		'Insert a horizontal rule': 'Sett en horisontal regel',
+		'Code': 'Kode',
+		'Width (optional):': 'Bredde (valgfritt):',
+		'Height (optional):': 'Høyde (valgfritt):',
+		'Insert an image': 'Sett inn et bilde',
+		'E-mail:': 'E-post',
+		'Insert an email': 'Sett inn en e-post',
+		'URL:': 'URL:',
+		'Insert a link': 'Sett inn en lenke',
+		'Unlink': 'Oppheve tilknytningen',
+		'More': 'Mer',
+		'Insert an emoticon': 'Sett inn et uttrykksikon',
+		'Video URL:': 'Video URL',
+		'Insert': 'Sett inn',
+		'Insert a YouTube video': 'Sett inn en YouTube-video',
+		'Insert current date': 'Sett inn gjeldende dato',
+		'Insert current time': 'Sett inn gjeldende klokkeslett',
+		'Print': 'Skriv ut',
+		'View source': 'Vis kildekode',
+		'Description (optional):': 'Beskrivelse (valgfritt):',
+		'Enter the image URL:': 'Skriv inn bildet\'s URL:',
+		'Enter the e-mail address:': 'Skriv inn e-postadresse:',
+		'Enter the displayed text:': 'Skriv inn teksten som vises:',
+		'Enter URL:': 'Skriv inn URL adresse:',
+		'Enter the YouTube video URL or ID:': 'Angi YouTube video link eller ID:',
+		'Insert a Quote': 'Sett inn sitat',
+		'Invalid YouTube video': 'Ugyldig Youtube video',
+
+		dateFormat: 'day.month.year'
+	};
+
+	sceditor.locale['nb'] = sceditor.locale['nb-NO'];
+})();

+ 72 - 0
public/js/sc/languages/nl.js

@@ -0,0 +1,72 @@
+/**
+ * @author Pieterjan Deneys (NekoJonez) - https://arpegi.wordpress.com
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+
+(function () {
+	'use strict';
+
+	sceditor.locale['nl'] = {
+		'Bold': 'Vet',
+		'Italic': 'Cursief',
+		'Underline': 'Onderlijnd',
+		'Strikethrough': 'Doorhalen',
+		'Subscript': 'Subscript',
+		'Superscript': 'Superscript',
+		'Align left': 'Links uitlijnen',
+		'Center': 'Centreren',
+		'Align right': 'Rechts uitlijnen',
+		'Justify': 'Uitvullen',
+		'Font Name': 'Lettertype naam',
+		'Font Size': 'Lettergrootte',
+		'Font Color': 'Lettertype kleur',
+		'Remove Formatting': 'Verwijder opmaak',
+		'Cut': 'Knippen',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Je browser staat het knippen commando niet toe. Gebruik de toetsenbord sneltoets Ctrl/Cmd-X',
+		'Copy': 'Kopiëren',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Je browser staat het kopieer commando niet toe. Gebruik de toetsenbord sneltoets Ctrl/Cmd-C',
+		'Paste': 'Plakken',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Je browser staat het plakken commando niet toe. Gebruik de toetsenbord sneltoets Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Plak je tekst in het volgende vak:',
+		'Paste Text': 'Tekst plakken',
+		'Bullet list': 'Opsomming',
+		'Numbered list': 'Genummerde lijst',
+		'Undo': 'Ongedaan maken',
+		'Redo': 'Opnieuw uitvoeren',
+		'Rows:': 'Rijen',
+		'Cols:': 'Kolommen',
+		'Insert a table': 'Tabel',
+		'Insert a horizontal rule': 'Horizontale regel invoegen',
+		'Code': 'Code',
+		'Width (optional):': 'Breedte (optioneel):',
+		'Height (optional):': 'Hoogte (optioneel):',
+		'Insert an image': 'Afbeelding invoegen',
+		'E-mail:': 'E-mail',
+		'Insert an email': 'E-mail invoegen',
+		'URL:': 'URL:',
+		'Insert a link': 'Link invoegen',
+		'Unlink': 'Link verwijderen',
+		'More': 'Meer',
+		'Insert an emoticon': 'Emoticon invoegen',
+		'Video URL:': 'Video URL',
+		'Insert': 'Invoegen',
+		'Insert a YouTube video': 'YouTube video invoegen',
+		'Insert current date': 'Huidige datum invoegen',
+		'Insert current time': 'Huidige tijd invoegen',
+		'Print': 'Print',
+		'View source': 'Bron bekijken',
+		'Description (optional):': 'Beschrijving (optioneel):',
+		'Enter the image URL:': 'Voer de afbeelding URL in:',
+		'Enter the e-mail address:': 'Voer het e-mailadres in:',
+		'Enter the displayed text:': 'Voer de getoonde tekst in:',
+		'Enter URL:': 'Voer de URL in:',
+		'Enter the YouTube video URL or ID:': 'Voer de YouTube video URL of ID in:',
+		'Insert a Quote': 'Citaat toevoegen',
+		'Invalid YouTube video': 'Ongeldige YouTube video',
+		'Drop files here': 'Plaats bestanden hier',
+
+		// month format, replace - with the date format separator and order in the
+		// order used
+		dateFormat: 'day-month-year'
+	};
+})();

+ 68 - 0
public/js/sc/languages/pl.js

@@ -0,0 +1,68 @@
+/**
+ * @author <Mirosław Dróżdż> <miroslaw.drozdz@vert.pl/www.vert.info.pl>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['pl'] = {
+		'Bold': 'Pogrubienie',
+		'Italic': 'Kursywa',
+		'Underline': 'Podkreślenie',
+		'Strikethrough': 'Przekreślenie',
+		'Subscript': 'Indeks dolny',
+		'Superscript': 'Indeks górny',
+		'Align left': 'Do lewej',
+		'Center': 'Do środka',
+		'Align right': 'Do prawej',
+		'Justify': 'Wyjustowanie',
+		'Font Name': 'Krój czcionki',
+		'Font Size': 'Rozmiar czcionki',
+		'Font Color': 'Kolor czcionki',
+		'Remove Formatting': 'Usuń formatowanie',
+		'Cut': 'Wytnij',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Twoja przeglądarka nie obsługuje opcji wycinania. Użyj skrótu klawiszowego Cmd/Ctrl + X',
+		'Copy': 'Skopiuj',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Twoja przeglądarka nie obsługuje opcji kopiowania. Użyj skrótu klawiszowego Cmd/Ctrl + C',
+		'Paste': 'Wklej',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Twoja przeglądarka nie obsługuje opcji wklejania. Użyj skrótu klawiszowego Cmd/Ctrl + V',
+		'Paste your text inside the following box:': 'Wklej swój tekst do tego pola:',
+		'Paste Text': 'Wklej tekst',
+		'Bullet list': 'Wypunktowanie',
+		'Numbered list': 'Lista numerowana',
+		'Undo': 'Cofnij',
+		'Redo': 'Powtórz',
+		'Rows:': 'Wiersze:',
+		'Cols:': 'Kolumny:',
+		'Insert a table': 'Wstaw tabelę',
+		'Insert a horizontal rule': 'Wstaw linię poziomą',
+		'Code': 'Kod',
+		'Width (optional):': 'Szerokość (opcjonalnie)',
+		'Height (optional):': 'Wysokość (opcjonalnie)',
+		'Insert an image': 'Wstaw obrazek',
+		'E-mail:': 'E-mail',
+		'Insert an email': 'Wstaw e-mail',
+		'URL:': 'URL',
+		'Insert a link': 'Wstaw odnośnik',
+		'Unlink': 'Usuń odnośnik',
+		'More': 'Więcej',
+		'Insert an emoticon': 'Wstaw emotikonę',
+		'Video URL:': 'URL do filmu',
+		'Insert': 'Wstaw',
+		'Insert a YouTube video': 'Wstaw film YouTube',
+		'Insert current date': 'Wstaw aktualną datę',
+		'Insert current time': 'Wstaw aktualny czas',
+		'Print': 'Drukuj',
+		'View source': 'Pokaż źródło',
+		'Description (optional):': 'Opis (opcjonalny)',
+		'Enter the image URL:': 'Wstaw URL do obrazka',
+		'Enter the e-mail address:': 'Wpisz adres e-mail',
+		'Enter the displayed text:': 'Wpisz wyświetlony tekst',
+		'Enter URL:': 'Wpisz adres URL',
+		'Enter the YouTube video URL or ID:': 'Wpisz adres URL lub ID filmu na YouTube',
+		'Insert a Quote': 'Wstaw cytat',
+		'Invalid YouTube video': 'Nieprawidłowy film YouTube',
+
+		dateFormat: 'day-month-year'
+	};
+})();

+ 67 - 0
public/js/sc/languages/pt-BR.js

@@ -0,0 +1,67 @@
+/**
+* @author martec
+* @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+*/
+(function () {
+	'use strict';
+
+	sceditor.locale['pt-BR'] = {
+		'Bold': 'Negrito',
+		'Italic': 'Itálico',
+		'Underline': 'Sublinhado',
+		'Strikethrough': 'Rasurado',
+		'Subscript': 'Subscrito',
+		'Superscript': 'Sobrescrito ',
+		'Align left': 'Alinhar à esquerda',
+		'Center': 'Centralizar',
+		'Align right': 'Alinhar à direita',
+		'Justify': 'Justificar',
+		'Font Name': 'Nome da fonte',
+		'Font Size': 'Tamanho da fonte',
+		'Font Color': 'Cor da fonte',
+		'Remove Formatting': 'Remover a formatação',
+		'Cut': 'Recortar',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Seu navegador não permite o comando recortar. Favor use o atalho Ctrl/Cmd-X',
+		'Copy': 'Copiar',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Seu navegador não permite o comando copiar. Favor use o atalho Ctrl/Cmd-C',
+		'Paste': 'Colar',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Seu navegador não permite o comando colar. Favor use o atalho Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Cole o seu texto dentro da caixa de texto a seguir:',
+		'Paste Text': 'Colar o texto',
+		'Bullet list': 'Lista com marcadores',
+		'Numbered list': 'Lista numérica',
+		'Undo': 'Desfazer',
+		'Redo': 'Refazer',
+		'Rows:': 'Linhas:',
+		'Cols:': 'Colunas:',
+		'Insert a table': 'Inserir uma tabela',
+		'Insert a horizontal rule': 'Inserir uma linha horizontal',
+		'Code': 'Código',
+		'Width (optional):': 'Largura (opcional):',
+		'Height (optional):': 'Altura (opcional):',
+		'Insert an image': 'Inserir uma imagem',
+		'E-mail:': 'E-mail:',
+		'Insert an email': 'Inserir um e-mail',
+		'URL:': 'URL:',
+		'Insert a link': 'Inserir um hiperlink',
+		'Unlink': 'Remover o hiperlink',
+		'More': 'Mais',
+		'Insert an emoticon': 'Inserir um emoticon',
+		'Video URL:': 'Video URL:',
+		'Insert': 'Inserir',
+		'Insert a YouTube video': 'Inserir YouTube video',
+		'Insert current date': 'Inserir a data atual',
+		'Insert current time': 'Inserir a hora atual',
+		'Print': 'Imprimir',
+		'View source': 'Fonte',
+		'Description (optional):': 'Descrição (opcional):',
+		'Enter the image URL:': 'Informe o endereço URL da imagem:',
+		'Enter the e-mail address:': 'Informe o endereço de e-mail:',
+		'Enter the displayed text:': 'Digite o texto exibido:',
+		'Enter URL:': 'Informe o endereço URL:',
+		'Enter the YouTube video URL or ID:': 'Informe o endereço URL ou ID do YouTube:',
+		'Insert a Quote': 'Inserir uma citação',
+		'Invalid YouTube video': 'Vídeo do YouTube inválido',
+		dateFormat: 'day-month-year'
+	};
+})();

+ 69 - 0
public/js/sc/languages/pt.js

@@ -0,0 +1,69 @@
+/**
+* @author brunoais
+* @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+*/
+(function () {
+	'use strict';
+
+	sceditor.locale['pt-PT'] = {
+		'Bold': 'Negrito',
+		'Italic': 'Itálico',
+		'Underline': 'Sublinhado',
+		'Strikethrough': 'Rasurado',
+		'Subscript': 'Subscrito',
+		'Superscript': 'Sobrescrito ',
+		'Align left': 'Alinhar à esquerda',
+		'Center': 'Centrar',
+		'Align right': 'Alinhar à direita',
+		'Justify': 'Justificar',
+		'Font Name': 'Nome da fonte',
+		'Font Size': 'Tamanho da fonte',
+		'Font Color': 'Cor da fonte',
+		'Remove Formatting': 'Remover a formatação',
+		'Cut': 'Cortar',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Seu navegador não permite o comando cortar. Por favor use o atalho Ctrl/Cmd-X',
+		'Copy': 'Copiar',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Seu navegador não permite o comando copiar. Por favor  use o atalho Ctrl/Cmd-C',
+		'Paste': 'Colar',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Seu navegador não permite o comando colar. Por favor  use o atalho Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Cole o seu texto dentro da caixa de texto a seguir:',
+		'Paste Text': 'Colar o texto',
+		'Bullet list': 'Lista com marcadores',
+		'Numbered list': 'Lista numérica',
+		'Undo': 'Desfazer',
+		'Redo': 'Refazer',
+		'Rows:': 'Linhas:',
+		'Cols:': 'Colunas:',
+		'Insert a table': 'Inserir uma tabela',
+		'Insert a horizontal rule': 'Inserir uma linha horizontal',
+		'Code': 'Código',
+		'Width (optional):': 'Largura (opcional):',
+		'Height (optional):': 'Altura (opcional):',
+		'Insert an image': 'Inserir uma imagem',
+		'E-mail:': 'E-mail:',
+		'Insert an email': 'Inserir um e-mail',
+		'URL:': 'URL:',
+		'Insert a link': 'Inserir um hiperlink',
+		'Unlink': 'Remover o hiperlink',
+		'More': 'Mais',
+		'Insert an emoticon': 'Inserir um emoticon',
+		'Video URL:': 'Video URL:',
+		'Insert': 'Inserir',
+		'Insert a YouTube video': 'Inserir YouTube video',
+		'Insert current date': 'Inserir a data atual',
+		'Insert current time': 'Inserir a hora atual',
+		'Print': 'Imprimir',
+		'View source': 'Código fonte',
+		'Description (optional):': 'Descrição (opcional):',
+		'Enter the image URL:': 'Introduza o endereço URL da imagem:',
+		'Enter the e-mail address:': 'Introduza o endereço de e-mail:',
+		'Enter the displayed text:': 'Indique o texto exibido:',
+		'Enter URL:': 'Introduza o endereço URL:',
+		'Enter the YouTube video URL or ID:': 'Introduza o endereço URL ou o ID do video do YouTube:',
+		'Insert a Quote': 'Inserir uma citação',
+		dateFormat: 'day/month/year'
+	};
+
+	// Set as the default Portuguese locale
+	sceditor.locale['pt'] = sceditor.locale['pt-PT'];
+})();

+ 60 - 0
public/js/sc/languages/ru.js

@@ -0,0 +1,60 @@
+(function () {
+	'use strict';
+
+	sceditor.locale['ru'] = {
+		'Bold': 'Жирный',
+		'Italic': 'Курсив',
+		'Underline': 'Подчёркнутый',
+		'Strikethrough': 'Зачёркнутый',
+		'Subscript': 'Нижний индекс',
+		'Superscript': 'Верхний индекс',
+		'Align left': 'Выравнивание по левому краю',
+		'Center': 'Выравнивание по центру',
+		'Align right': 'Выравнивание по правому краю',
+		'Justify': 'Выравнивание по обоим краям',
+		'Font Name': 'Шрифт',
+		'Font Size': 'Размер шрифта',
+		'Font Color': 'Цвет шрифта',
+		'Remove Formatting': 'Удалить форматирование',
+		'Cut': 'Вырезать',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ваш браузер не позволяет выполнять эту команду. Пожалуйста, используйте сочетание клавиш Ctrl / Cmd-X',
+		'Copy': 'Копировать',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ваш браузер не позволяет выполнять эту команду. Пожалуйста, используйте сочетание клавиш Ctrl / Cmd-C',
+		'Paste': 'Выставить',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ваш браузер не позволяет выполнять эту команду. Пожалуйста, используйте сочетание клавиш Ctrl / Cmd-V',
+		'Paste your text inside the following box:': 'Вставьте текст в следующее окно:',
+		'Paste Text': 'Вставить текст',
+		'Bullet list': 'Маркированный список',
+		'Numbered list': 'Нумерованный список',
+		'Undo': 'Отменить',
+		'Redo': 'Повторить',
+		'Rows:': 'Строки',
+		'Cols:': 'Столбцы',
+		'Insert a table': 'Таблица',
+		'Insert a horizontal rule': 'Горизонтальная линия',
+		'Code': 'Код',
+		'Insert a Quote': 'Цитата',
+		'Width (optional):': 'Ширина (необязательно):',
+		'Height (optional):': 'Высота (необязательно):',
+		'Insert an image': 'Изображение',
+		'E-mail:': 'E-mail',
+		'Insert an email': 'E-mail',
+		'URL:': 'URL:',
+		'Insert a link': 'Ссылка',
+		'Unlink': 'Удалить ссылку',
+		'More': 'Больше',
+		'Insert an emoticon': 'Смайлы',
+		'Video URL:': 'Видео URL',
+		'Insert': 'Вставить',
+		'Insert a YouTube video': 'YouTube-видео',
+		'Insert current date': 'Текущая дата',
+		'Insert current time': 'Текущее время',
+		'Print': 'Распечатать',
+		'View source': 'Показать код',
+		'Maximize': 'Развернуть',
+		'Left-to-Right': 'Слева направо',
+		'Right-to-Left': 'Справа налево',
+		'Description (optional):': 'Описание (необязательно):',
+		dateFormat: 'day.month.year'
+	};
+})();

+ 70 - 0
public/js/sc/languages/sk.js

@@ -0,0 +1,70 @@
+/**
+ * @author Kevo <me@kevo.link>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+	sceditor.locale['sk'] = {
+		'Bold': 'Tučné',
+		'Italic': 'Kurzíva',
+		'Underline': 'Podčiarknuté',
+		'Strikethrough': 'Prečiarknuté',
+		'Subscript': 'Dolný index',
+		'Superscript': 'Horný index',
+		'Align left': 'Zarovnať vľavo',
+		'Center': 'Zarovnať na stred',
+		'Align right': 'Zarovnať vpravo',
+		'Justify': 'Zarovnať do bloku',
+		'Font Name': 'Typ písma',
+		'Font Size': 'Veľkosť písma',
+		'Font Color': 'Farba písma',
+		'Remove Formatting': 'Odstrániť formátovanie',
+		'Cut': 'Vystrihnúť',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Váš prehliadač nepodporuje príkaz pre vystrihnutie. Prosím, použite klávesovú skratku Ctrl/Cmd-X',
+		'Copy': '',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Váš prehliadač nepodporuje príkaz pre skopírovanie. Prosím, použite klávesovú skratku Ctrl/Cmd-C',
+		'Paste': '',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Váš prehliadač nepodporuje príkaz pre vloženie. Prosím, použite klávesovú skratku Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Vložte Váš text do nasledujúceho poľa',
+		'Paste Text': 'Vložiť text',
+		'Bullet list': 'Zoznam',
+		'Numbered list': 'Číslovaný zoznam',
+		'Undo': 'Krok späť',
+		'Redo': 'Krok vpred',
+		'Rows:': 'Riadkov:',
+		'Cols:': 'Stĺpcov:',
+		'Insert a table': 'Vložiť tabuľku',
+		'Insert a horizontal rule': 'Vložiť vodorovnú čiaru',
+		'Code': 'Vložiť kód',
+		'Width (optional):': 'Šírka (voliteľné):',
+		'Height (optional):': 'Výška (voliteľné):',
+		'Insert an image': 'Vložiť obrázok',
+		'E-mail:': 'E-mail:',
+		'Insert an email': 'Vložiť E-mail',
+		'URL:': 'URL adresa:',
+		'Insert a link': 'Vložiť odkaz',
+		'Unlink': 'Zrušiť odkaz',
+		'More': 'Viac',
+		'Insert an emoticon': 'Vložiť smajlíka',
+		'Video URL:': 'URL adresa videa:',
+		'Insert': 'Vložiť',
+		'Insert a YouTube video': 'Vložiť YouTube video',
+		'Insert current date': 'Vložiť dnešný dátum',
+		'Insert current time': 'Vložiť aktuálny čas',
+		'Print': 'Vytlačiť',
+		'View source': 'Zobraziť zdrojový kód',
+		'Description (optional):': 'Popis (voliteľné):',
+		'Enter the image URL:': 'Vložte URL adresu obrázka:',
+		'Enter the e-mail address:': 'Vložte E-mailovú adresu:',
+		'Enter the displayed text:': 'Vložte zobrazovaný text:',
+		'Enter URL:': 'Vložte URL adresu:',
+		'Enter the YouTube video URL or ID:': 'Vložte URL adresu YouTube videa alebo jeho ID:',
+		'Insert a Quote': 'Vložiť citát',
+		'Invalid YouTube video': 'Neplatné YouTube video',
+		'Left-to-Right': 'Zľava doprava',
+		'Right-to-Left': 'Zprava doľava',
+		'Drop files here': 'Presuňte súbory sem',
+		'Maximize': 'Maximalizovať',
+		dateFormat: 'day. month. year'
+	};
+})();

+ 58 - 0
public/js/sc/languages/sv.js

@@ -0,0 +1,58 @@
+(function () {
+	'use strict';
+
+	sceditor.locale['sv-SE'] = {
+		'Bold': 'Fet',
+		'Italic': 'Kursiv',
+		'Underline': 'Understruken',
+		'Strikethrough': 'Genomstruken',
+		'Subscript': 'Nersänkt',
+		'Superscript': 'Upphöjt',
+		'Align left': 'Vänsterställ',
+		'Center': 'Centrera',
+		'Align right': 'Högerställ',
+		'Justify': 'Normalt oppstilt',
+		'Font Name': 'Teckensnitt',
+		'Font Size': 'Teckenstorlek',
+		'Font Color': 'Teckenfärg',
+		'Remove Formatting': 'Ta bort formatering',
+		'Cut': 'Klipp ut',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Din webbläsare kan inte utföra kommandot. Vänligen använd kortkommando Ctrl / Cmd-X',
+		'Copy': 'Kopiera',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Din webbläsare kan inte utföra kommandot. Vänligen använd kortkommando Ctrl / Cmd-C',
+		'Paste': 'Klista in',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Din webbläsare kan inte utföra kommandot. Vänligen använd kortkommando Ctrl / Cmd-V',
+		'Paste your text inside the following box:': 'Klistra in din text i rutan:',
+		'Paste Text': 'Klistra in text',
+		'Bullet list': 'Lista',
+		'Numbered list': 'Numrerad lista',
+		'Undo': 'Ångra',
+		'Redo': 'Gör om',
+		'Rows:': 'Rader',
+		'Cols:': 'Kolumner',
+		'Insert a table': 'Infoga tabell',
+		'Insert a horizontal rule': 'Infoga skiljestreck',
+		'Code': 'Kod',
+		'Width (optional):': 'Bredd (valfritt):',
+		'Height (optional):': 'Höjd (valfritt):',
+		'Insert an image': 'Infoga bild',
+		'E-mail:': 'E-post',
+		'Insert an email': 'Infoga e-post',
+		'URL:': 'URL:',
+		'Insert a link': 'Infoga länk',
+		'Unlink': 'Ta bort länk',
+		'More': 'Mer',
+		'Insert an emoticon': 'Infoga smiley',
+		'Video URL:': 'Video URL',
+		'Insert': 'Infoga',
+		'Insert a YouTube video': 'Infoga YouTube-video',
+		'Insert current date': 'Infoga dagens datum',
+		'Insert current time': 'Infoga nuvarande tid',
+		'Print': 'Skriv ut',
+		'View source': 'Visa källkod',
+		'Description (optional):': 'Beskrivning (valfritt):',
+		dateFormat: 'year-month-day'
+	};
+
+	sceditor.locale['sv'] = sceditor.locale['sv-SE'];
+})();

+ 80 - 0
public/js/sc/languages/template.js

@@ -0,0 +1,80 @@
+/**
+ * @author <Your Name> <Your e-mail/Website if you would like>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	// Replace <code> with the language code, e.g. no, fr, en, ect.
+	sceditor.locale['<code>'] = {
+
+		// Original string is on the left, place the translation between
+		// the quotes on the right
+		'Bold': '',
+		'Italic': '',
+		'Underline': '',
+		'Strikethrough': '',
+		'Subscript': '',
+		'Superscript': '',
+		'Align left': '',
+		'Center': '',
+		'Align right': '',
+		'Justify': '',
+		'Font Name': '',
+		'Font Size': '',
+		'Font Color': '',
+		'Remove Formatting': '',
+		'Cut': '',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': '',
+		'Copy': '',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': '',
+		'Paste': '',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': '',
+		'Paste your text inside the following box:': '',
+		'Paste Text': '',
+		'Bullet list': '',
+		'Numbered list': '',
+		'Add indent': '',
+		'Remove one indent': '',
+		'Undo': '',
+		'Redo': '',
+		'Rows:': '',
+		'Cols:': '',
+		'Insert a table': '',
+		'Insert a horizontal rule': '',
+		'Code': '',
+		'Width (optional):': '',
+		'Height (optional):': '',
+		'Insert an image': '',
+		'E-mail:': '',
+		'Insert an email': '',
+		'URL:': '',
+		'Insert a link': '',
+		'Unlink': '',
+		'More': '',
+		'Insert an emoticon': '',
+		'Video URL:': '',
+		'Insert': '',
+		'Insert a YouTube video': '',
+		'Insert current date': '',
+		'Insert current time': '',
+		'Print': '',
+		'Maximize': '',
+		'View source': '',
+		'Description (optional):': '',
+		'Enter the image URL:': '',
+		'Enter the e-mail address:': '',
+		'Enter the displayed text:': '',
+		'Enter URL:': '',
+		'Enter the YouTube video URL or ID:': '',
+		'Insert a Quote': '',
+		'Invalid YouTube video': '',
+		'Drop files here': '',
+		'Left-to-Right': '',
+		'Right-to-Left': '',
+
+		// month format, replace - with the date format separator and order in the
+		// order used
+		dateFormat: 'day-month-year'
+	};
+})();

+ 66 - 0
public/js/sc/languages/tr.js

@@ -0,0 +1,66 @@
+/**
+ * @author Mahmut Yaman - iletisim@/m-yaman.com
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+	sceditor.locale['tr'] = {
+		'Bold': 'Kalın',
+		'Italic': 'İtalik',
+		'Underline': 'Altı çizgili',
+		'Strikethrough': 'Üstü çizgili',
+		'Subscript': 'Simge',
+		'Superscript': 'Üstsimge',
+		'Align left': 'Sola yasla',
+		'Center': 'Ortala',
+		'Align right': 'Sağa yasla',
+		'Justify': 'Satır uzunluğuna ayarla',
+		'Font Name': 'Yazı tipi',
+		'Font Size': 'Yazı boyutu',
+		'Font Color': 'Yazı rengi',
+		'Remove Formatting': 'Biçimlendirmeyi temizle',
+		'Cut': 'Kes',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Tarayıcınız kesme komutuna izin vermiyor. Lütfen Ctrl/Cmd-X klavye kısayolunu kullanın.',
+		'Copy': 'Kopyala',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Tarayıcınız kopyalama komutuna izin vermiyor. Lütfen Ctrl/Cmd-C klavye kısayolunu kullanın.',
+		'Paste': 'Yapıştır',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Tarayıcınız yapıştırma komutuna izin vermiyor. Lütfen Ctrl/Cmd-V klavye kısayolunu kullanın.',
+		'Paste your text inside the following box:': 'Yazınızı bu kutucuğa yapıştırın:',
+		'Paste Text': 'Metin Yapıştır',
+		'Bullet list': 'Madde işaretli liste',
+		'Numbered list': 'Numaralı liste',
+		'Undo': 'Geri al',
+		'Redo': 'Yinele',
+		'Rows:': 'Sütun:',
+		'Cols:': 'Kolon:',
+		'Insert a table': 'Tablo ekle',
+		'Insert a horizontal rule': 'Yatay ayraç ekle',
+		'Code': 'Kod',
+		'Width (optional):': 'Genişlik (opsiyonel):',
+		'Height (optional):': 'Yükseklik (opsiyonel):',
+		'Insert an image': 'Resim ekle',
+		'E-mail:': 'E-posta:',
+		'Insert an email': 'E-posta ekle',
+		'URL:': 'URL:',
+		'Insert a link': 'Bağlantı ekle',
+		'Unlink': 'Bağlantıyı kaldır',
+		'More': 'Daha fazla',
+		'Insert an emoticon': 'Yüz ifadesi ekle',
+		'Video URL:': 'Video URL:',
+		'Insert': 'Ekle',
+		'Insert a YouTube video': 'YouTube videosu ekle',
+		'Insert current date': 'Şuanki tarihi ekle',
+		'Insert current time': 'Şuanki saati ekle',
+		'Print': 'Yazdır',
+		'View source': 'Kaynağı görüntüle',
+		'Description (optional):': 'Açıklama (opsiyonel):',
+		'Enter the image URL:': 'Resim URL\'sini girin:',
+		'Enter the e-mail address:': 'E-posta adresini girin:',
+		'Enter the displayed text:': 'Görünecek yazıyı girin:',
+		'Enter URL:': 'URL\'yi girin:',
+		'Enter the YouTube video URL or ID:': 'YouTube video URL\'sini yada ID\'sini girin:',
+		'Insert a Quote': 'Alıntı ekle',
+		'Invalid YouTube video': 'Geçersiz YouTube videosu',
+		dateFormat: 'day-month-year'
+	};
+})();

+ 68 - 0
public/js/sc/languages/tw.js

@@ -0,0 +1,68 @@
+/**
+ * @author <Your Name> <Your e-mail/Website if you would like>
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['tw'] = {
+		'Bold': '粗體',
+		'Italic': '斜體',
+		'Underline': '底線',
+		'Strikethrough': '删除線',
+		'Subscript': '下標',
+		'Superscript': '上標',
+		'Align left': '靠左對齊',
+		'Center': '置中',
+		'Align right': '靠右對齊',
+		'Justify': '兩端對齊',
+		'Font Name': '字形',
+		'Font Size': '字體大小',
+		'Font Color': '文字顏色',
+		'Remove Formatting': '清除格式',
+		'Cut': '剪下',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': '您的瀏覽器不支持剪下命令,請使用快速键 Ctrl/Cmd-X',
+		'Copy': '拷貝',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': '您的瀏覽器不支持拷貝命令,請使用快速键 Ctrl/Cmd-C',
+		'Paste': '貼上',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': '您的瀏覽器不支持貼上命令,請使用快速键 Ctrl/Cmd-V',
+		'Paste your text inside the following box:': '請在下面貼上您的文字',
+		'Paste Text': '貼上纯文字',
+		'Bullet list': '符號列表',
+		'Numbered list': '编號列表',
+		'Undo': '復原',
+		'Redo': '重做',
+		'Rows:': '行數',
+		'Cols:': '列數',
+		'Insert a table': '插入表格',
+		'Insert a horizontal rule': '插入分隔線',
+		'Code': '原始碼',
+		'Width (optional):': '寬度(選填)',
+		'Height (optional):': '高度(選填)',
+		'Insert an image': '插入圖片',
+		'E-mail:': 'Email',
+		'Insert an email': '插入Email',
+		'URL:': '網址',
+		'Insert a link': '插入超鏈結',
+		'Unlink': '取消超鏈結',
+		'More': '更多',
+		'Insert an emoticon': '插入表情符號',
+		'Video URL:': '影片網址',
+		'Insert': '插入',
+		'Insert a YouTube video': '插入 YouTube 影片',
+		'Insert current date': '插入目前日期',
+		'Insert current time': '插入目前時間',
+		'Print': '列印',
+		'View source': '查看原始碼',
+		'Description (optional):': '描述(選填)',
+		'Enter the image URL:': '輸入圖片網址',
+		'Enter the e-mail address:': '輸入 Email',
+		'Enter the displayed text:': '輸入顯示文字',
+		'Enter URL:': '輸入網址',
+		'Enter the YouTube video URL or ID:': '輸入 YouTube 網址或影片编號',
+		'Insert a Quote': '插入引用',
+		'Invalid YouTube video': '無效的YouTube影片',
+
+		dateFormat: 'year-month-day'
+	};
+})();

+ 57 - 0
public/js/sc/languages/uk.js

@@ -0,0 +1,57 @@
+(function () {
+	'use strict';
+
+	sceditor.locale['uk'] = {
+		'Bold': 'Жирний',
+		'Italic': 'Курсив',
+		'Underline': 'Підкреслений',
+		'Strikethrough': 'Закреслений',
+		'Subscript': 'Нижній індекс',
+		'Superscript': 'Верхній індекс',
+		'Align left': 'Вирівняти по лівому краю',
+		'Center': 'Вирівняти по центру',
+		'Align right': 'Вирівняти по правому краю',
+		'Justify': 'Вирівняти по ширині',
+		'Font Name': 'Шрифт',
+		'Font Size': 'Розмір шрифту',
+		'Font Color': 'Колір шрифту',
+		'Remove Formatting': 'Видалити форматування',
+		'Cut': 'Вирізати',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ваш браузер не дозволяє виконати цю команду. Будь ласка, використовуйте комбінацію клавіш Ctrl/Cmd-X',
+		'Copy': 'Копіювати',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ваш браузер не дозволяє виконати цю команду. Будь ласка, використовуйте комбінацію клавіш Ctrl/Cmd-C',
+		'Paste': 'Вставити',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ваш браузер не дозволяє виконати цю команду. Будь ласка, використовуйте комбінацію клавіш Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Вставте текст у наступне вікно:',
+		'Paste Text': 'Вставити текст',
+		'Bullet list': 'Маркований список',
+		'Numbered list': 'Нумерований список',
+		'Undo': 'Відмінити',
+		'Redo': 'Повторити',
+		'Rows:': 'Рядків:',
+		'Cols:': 'Cтовпців:',
+		'Insert a table': 'Додати таблицю',
+		'Insert a horizontal rule': 'Додати горизонтальну лінію',
+		'Code': 'Код',
+		'Insert a Quote': 'Додати цитату',
+		'Width (optional):': 'Ширина (необов\'язково):',
+		'Height (optional):': 'Висота (необов\'язково):',
+		'Insert an image': 'Додати зображення',
+		'E-mail:': 'E-mail:',
+		'Insert an email': 'Додати E-mail',
+		'URL:': 'URL:',
+		'Insert a link': 'Додати посилання',
+		'Unlink': 'Видалити посилання',
+		'More': 'Більше',
+		'Insert an emoticon': 'Додати смайлик',
+		'Video URL:': 'URL відео:',
+		'Insert': 'Вставити',
+		'Insert a YouTube video': 'Додати відео з YouTube',
+		'Insert current date': 'Додати дату',
+		'Insert current time': 'Додати час',
+		'Print': 'Надрукувати',
+		'View source': 'Показати код',
+		'Maximize': 'Розгорнути редактор',
+		dateFormat: 'day.month.year'
+	};
+})();

+ 68 - 0
public/js/sc/languages/vi.js

@@ -0,0 +1,68 @@
+/**
+ * @author Chien
+ * @license [MIT](http://www.opensource.org/licenses/mit-license.php)
+ */
+(function () {
+	'use strict';
+
+	sceditor.locale['vi'] = {
+		'Bold': 'Đậm',
+		'Italic': 'Nghiêng',
+		'Underline': 'Gạch chân',
+		'Strikethrough': 'Gạch giữa',
+		'Subscript': 'Hệ số',
+		'Superscript': 'Mũ',
+		'Align left': 'Căn trái',
+		'Center': 'Căn giữa',
+		'Align right': 'Căn phải',
+		'Justify': 'Căn đều',
+		'Font Name': 'Phông chữ',
+		'Font Size': 'Cỡ chữ',
+		'Font Color': 'Màu chữ',
+		'Remove Formatting': 'Xóa định dạng',
+		'Cut': 'Cắt',
+		'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Trình duyệt không cho phép sử dụng lệnh Cut. Vui lòng sử dụng phím tắt Ctrl/Cmd-X',
+		'Copy': 'Sao chép',
+		'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Trình duyệt không cho phép sử dụng lệnh Copy. Vui lòng sử dụng phím tắt Ctrl/Cmd-C',
+		'Paste': 'Chép vào',
+		'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Trình duyệt không cho phép sử dụng lệnh Paste. Vui lòng sử dụng phím tắt Ctrl/Cmd-V',
+		'Paste your text inside the following box:': 'Chép nội dung text vào khung sau',
+		'Paste Text': 'Chép nội dung text',
+		'Bullet list': 'Danh sách kiểu nốt',
+		'Numbered list': 'Danh sách kiểu số',
+		'Undo': 'Hủy bỏ',
+		'Redo': 'Trở lại bước trước',
+		'Rows:': 'Số dòng',
+		'Cols:': 'Số cột',
+		'Insert a table': 'Thêm bảng',
+		'Insert a horizontal rule': 'Thêm thước ngang',
+		'Code': 'Mã code',
+		'Width (optional):': 'Dài (không bắt buộc)',
+		'Height (optional):': 'Rộng (không bắt buộc)',
+		'Insert an image': 'Chèn hình ảnh',
+		'E-mail:': 'E-mail',
+		'Insert an email': 'Chèn email',
+		'URL:': 'Liên kết',
+		'Insert a link': 'Chèn liên kết',
+		'Unlink': 'Bỏ liên kết',
+		'More': 'Xem thêm',
+		'Insert an emoticon': 'Chèn biểu tượng',
+		'Video URL:': 'Đường dẫn của Video',
+		'Insert': 'Thêm vào',
+		'Insert a YouTube video': 'Chèn Youtube',
+		'Insert current date': 'Chèn ngày hiện tại',
+		'Insert current time': 'Chèn thời gian hiện tại',
+		'Print': 'In ấn',
+		'View source': 'Xem mã nguồn',
+		'Description (optional):': 'Mô tả (không bắt buộc)',
+		'Enter the image URL:': 'Nhập vào đường dẫn của hình ảnh',
+		'Enter the e-mail address:': 'Nhập vào địa chỉ email',
+		'Enter the displayed text:': 'Nhập vào nội dung hiển thị',
+		'Enter URL:': 'Nhập vào liên kết',
+		'Enter the YouTube video URL or ID:': 'Nhập vào liên kết của video hoặc ID trên Youtube',
+		'Insert a Quote': 'Chèn trích dẫn',
+		'Invalid YouTube video': 'Video Youtube không chính xác',
+
+		dateFormat: 'day/month/year'
+	};
+})();

+ 157 - 0
public/js/sc/plugins/alternative-lists.js

@@ -0,0 +1,157 @@
+/**
+ * SCEditor Inline-Code Plugin for BBCode format
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-2013, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @fileoverview SCEditor alternative lists plugin
+ * This plugin implements phpBB style of the lists:
+ * [list]
+ * [*]item
+ * [*]item
+ * [/list]
+ * @author Alex Betis
+ */
+
+(function (sceditor) {
+	'use strict';
+
+	var utils = sceditor.utils;
+
+	function isFunction(fn) {
+		return typeof fn === 'function';
+	}
+
+	sceditor.plugins['alternative-lists'] = function () {
+		var base = this;
+
+		/**
+		 * Private functions
+		 * @private
+		 */
+		var bulletHandler;
+		var orderedHandler;
+		var insertListTag;
+
+		base.init = function () {
+			var opts = this.opts;
+
+			// Enable for BBCode only
+			if (opts.format && opts.format !== 'bbcode') {
+				return;
+			}
+
+			// Override only txtExec implementation
+			sceditor.command.get('orderedlist').txtExec = orderedHandler;
+			sceditor.command.get('bulletlist').txtExec = bulletHandler;
+
+			// Override current implementation
+			sceditor.formats.bbcode.set('list', {
+				breakStart: true,
+				isInline: false,
+				skipLastLineBreak: true,
+				html: function (token, attrs, content) {
+					var listType = 'disc';
+					var toHtml = null;
+
+					if (attrs.defaultattr) {
+						listType = attrs.defaultattr;
+					}
+
+					if (listType === '1') {
+						// This listType belongs to orderedList (OL)
+						toHtml = sceditor.formats.bbcode.get('ol').html;
+					} else {
+						// unknown listType, use default bullet list behavior
+						toHtml = sceditor.formats.bbcode.get('ul').html;
+					}
+
+					if (isFunction(toHtml)) {
+						return toHtml.call(this, token, attrs, content);
+					} else {
+						token.attrs['0'] = content;
+						return sceditor.formats.bbcode.formatBBCodeString(
+							toHtml, token.attrs);
+					}
+				}
+			});
+
+			sceditor.formats.bbcode.set('ul', {
+				tags: {
+					ul: null
+				},
+				breakStart: true,
+				isInline: false,
+				skipLastLineBreak: true,
+				format: '[list]{0}[/list]',
+				html: '<ul>{0}</ul>'
+			});
+
+			sceditor.formats.bbcode.set('ol', {
+				tags: {
+					ol: null
+				},
+				breakStart: true,
+				isInline: false,
+				skipLastLineBreak: true,
+				format: '[list=1]{0}[/list]',
+				html: '<ol>{0}</ol>'
+			});
+
+			sceditor.formats.bbcode.set('li', {
+				tags: {
+					li: null
+				},
+				isInline: false,
+				closedBy: ['/ul', '/ol', '/list', '*', 'li'],
+				format: '[*]{0}',
+				html: '<li>{0}</li>'
+			});
+
+			sceditor.formats.bbcode.set('*', {
+				isInline: false,
+				excludeClosing: true,
+				closedBy: ['/ul', '/ol', '/list', '*', 'li'],
+				html: '<li>{0}</li>'
+			});
+		};
+
+		insertListTag = function (editor, listType, selected) {
+			var content = '';
+
+			utils.each(selected.split(/\r?\n/), function (item) {
+				content += (content ? '\n' : '') +
+					'[*]' + item;
+			});
+
+			if (listType === '') {
+				editor.insertText('[list]\n' + content + '\n[/list]');
+			} else {
+				editor.insertText('[list=' + listType + ']\n' + content +
+				'\n[/list]');
+			}
+		};
+
+		/**
+		 * Function for the txtExec and exec properties
+		 *
+		 * @param  {node} caller
+		 * @private
+		 */
+		orderedHandler = function (caller, selected) {
+			var editor = this;
+
+			insertListTag(editor, '1', selected);
+		};
+
+		bulletHandler = function (caller, selected) {
+			var editor = this;
+
+			insertListTag(editor, '', selected);
+		};
+
+	};
+})(sceditor);

+ 110 - 0
public/js/sc/plugins/autosave.js

@@ -0,0 +1,110 @@
+/**
+ * SCEditor AutoSave Plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ */
+(function (sceditor) {
+	'use strict';
+
+	var defaultKey = 'sce-autodraft-' + location.pathname + location.search;
+
+	function clear(key) {
+		localStorage.removeItem(key || defaultKey);
+	}
+
+	sceditor.plugins.autosave = function () {
+		var base = this;
+		var editor;
+		var isLoading = false;
+		var storageKey = defaultKey;
+		// 86400000 = 24 hrs (24 * 60 * 60 * 1000)
+		var expires = 86400000;
+		var saveHandler = function (value) {
+			localStorage.setItem(storageKey, JSON.stringify(value));
+		};
+		var loadHandler = function () {
+			return JSON.parse(localStorage.getItem(storageKey));
+		};
+
+		function gc() {
+			for (var i = 0; i < localStorage.length; i++) {
+				var key = localStorage.key(i);
+
+				if (/^sce\-autodraft\-/.test(key)) {
+					var item = JSON.parse(localStorage.getItem(storageKey));
+					if (item && item.time < Date.now() - expires) {
+						clear(key);
+					}
+				}
+			}
+		}
+
+		base.init = function () {
+			editor = this;
+			var opts = editor.opts && editor.opts.autosave || {};
+
+			saveHandler = opts.save || saveHandler;
+			loadHandler = opts.load || loadHandler;
+			storageKey = opts.storageKey || storageKey;
+			expires = opts.expires || expires;
+
+			gc();
+		};
+
+		base.signalReady = function () {
+			// Add submit event listener to clear autosave
+			var parent = editor.getContentAreaContainer();
+			while (parent) {
+				if (/form/i.test(parent.nodeName)) {
+					parent.addEventListener(
+						'submit', clear.bind(null, storageKey), true
+					);
+					break;
+				}
+
+				parent = parent.parentNode;
+			}
+
+			var state = loadHandler();
+			if (state) {
+				isLoading = true;
+				editor.sourceMode(state.sourceMode);
+				editor.val(state.value, false);
+				editor.focus();
+
+				if (state.sourceMode) {
+					editor.sourceEditorCaret(state.caret);
+				} else {
+					editor.getRangeHelper().restoreRange();
+				}
+				isLoading = false;
+			} else {
+				saveHandler({
+					caret: this.sourceEditorCaret(),
+					sourceMode: this.sourceMode(),
+					value: editor.val(null, false),
+					time: Date.now()
+				});
+			}
+		};
+
+		base.signalValuechangedEvent = function (e) {
+			if (!isLoading) {
+				saveHandler({
+					caret: this.sourceEditorCaret(),
+					sourceMode: this.sourceMode(),
+					value: e.detail.rawValue,
+					time: Date.now()
+				});
+			}
+		};
+	};
+
+	sceditor.plugins.autosave.clear = clear;
+}(sceditor));

+ 106 - 0
public/js/sc/plugins/autoyoutube.js

@@ -0,0 +1,106 @@
+/**
+ * SCEditor Auto Youtube Plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2016, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ */
+(function (document, sceditor) {
+	'use strict';
+
+	var dom = sceditor.dom;
+
+	/*
+		(^|\s)					Start of line or space
+		(?:https?:\/\/)?  		Optional scheme like http://
+		(?:www\.)?      		Optional www. prefix
+		(?:
+			youtu\.be\/     	Ends with .be/ so whatever comes next is the ID
+		|
+			youtube\.com\/watch\?v=		Matches the .com version
+		)
+		([^"&?\/ ]{11}) 				The actual YT ID
+		(?:\&[\&_\?0-9a-z\#]+)?			Any extra URL params
+		(\s|$)							End of line or space
+	*/
+	var ytUrlRegex = /(^|\s)(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/watch\?v=)([^"&?\/ ]{11})(?:\&[\&_\?0-9a-z\#]+)?(\s|$)/i;
+
+	function youtubeEmbedCode(id) {
+		return '<iframe width="560" height="315" frameborder="0" ' +
+			'src="https://www.youtube-nocookie.com/embed/' + id + '" ' +
+			'data-youtube-id="' + id + '" allowfullscreen></iframe>';
+	}
+
+	function convertYoutubeLinks(parent, isRoot) {
+		var node = parent.firstChild;
+		var wholeContent = (parent.textContent || '');
+
+		// Don't care about whitespace if is the root node
+		if (isRoot) {
+			wholeContent = wholeContent.trim();
+		}
+
+		var match = wholeContent.match(ytUrlRegex);
+		// Whole content match so only return URL embed
+		if (wholeContent === wholeContent.trim() && match &&
+			match[0].length === wholeContent.length) {
+			dom.removeAttr(parent, 'style');
+			dom.removeAttr(parent, 'class');
+			parent.innerHTML = youtubeEmbedCode(match[2]);
+			return;
+		}
+
+		while (node) {
+			// 3 is TextNodes
+			if (node.nodeType === 3) {
+				var text   = node.nodeValue;
+				var nodeParent = node.parentNode;
+
+				if ((match = text.match(ytUrlRegex))) {
+					nodeParent.insertBefore(document.createTextNode(
+						text.substr(0, match.index) + match[1]
+					), node);
+
+					nodeParent.insertBefore(
+						dom.parseHTML(youtubeEmbedCode(match[2])), node
+					);
+
+					node.nodeValue = match[3] +
+						text.substr(match.index + match[0].length);
+				}
+			} else {
+				// TODO: Make this tag configurable.
+				if (!dom.is(node, 'code')) {
+					convertYoutubeLinks(node);
+				}
+			}
+
+			node = node.nextSibling;
+		}
+	};
+
+	sceditor.plugins.autoyoutube = function () {
+		this.signalPasteRaw = function (data) {
+			// TODO: Make this tag configurable.
+			// Skip code tags
+			if (dom.closest(this.currentNode(), 'code')) {
+				return;
+			}
+
+			if (data.html || data.text) {
+				var node = document.createElement('div');
+
+				node.innerHTML = data.html ||
+					sceditor.escapeEntities(data.text);
+
+				convertYoutubeLinks(node, true);
+
+				data.html = node.innerHTML;
+			}
+		};
+	};
+})(document, sceditor);

+ 222 - 0
public/js/sc/plugins/dragdrop.js

@@ -0,0 +1,222 @@
+/**
+ * SCEditor Drag and Drop Plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ */
+(function (sceditor) {
+	'use strict';
+
+	/**
+	 * Place holder GIF shown while image is loading.
+	 * @type {string}
+	 * @private
+	 */
+	var loadingGif = 'data:image/gif;base64,R0lGODlhlgBkAPABAH19ffb29iH5BAAK' +
+		'AAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQA' +
+		'AACwAAAAAlgBkAAAC1YyPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s' +
+		'73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0u' +
+		'v2OvwD2fP6iD/gH6Pc2GIhg2JeQSNjGuLf4GMlYKIloefAIUEl52ZmJyaY5mUhqyFnq' +
+		'mQr6KRoaMKp66hbLumpQ69oK+5qrOyg4a6qYV2x8jJysvMzc7PwMHS09TV1tfY2drb3' +
+		'N3e39DR4uPk5ebn6Onq6+zt7u/g4fL99UAAAh+QQACgAAACwAAAAAlgBkAIEAAAB9fX' +
+		'329vYAAAAC3JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MC' +
+		'ofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwD2' +
+		'fP4iABgY+CcoCNeHuJdQyLjIaOiWiOj4CEhZ+SbZd/nI2RipqYhQOThKGpAZCuBZyAr' +
+		'ZprpqSupaCqtaazmLCRqai7rb2av5W5wqSShcm8fc7PwMHS09TV1tfY2drb3N3e39DR' +
+		'4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5/vVAAAIfkEAAoAAAAsAAAAAJYAZACBAAAAf' +
+		'X199vb2AAAAAuCUj6nL7Q+jnLTai7PevPsPhuJIluaJpurKtu4Lx/JM1/aN5/rO9/4P' +
+		'DAqHxKLxiEwql8ym8wmNSqfUqvWKzWq33K73Cw6Lx+Sy+YxOq9fstvsNj8vn9Lr9jr8' +
+		'E9nz+AgAYGLjQVwhXiJgguAiYgGjo9tinyCjoKLn3hpmJUGmJsBmguUnpCXCJOZraaX' +
+		'oKShoJe9DqehCqKlnqiZobuzrbyvuIO8xqKpxIPKlwrPCbBx0tPU1dbX2Nna29zd3t/' +
+		'Q0eLj5OXm5+jp6uvs7e7v4OHy8/T19vf4+fr7/P379UAAAh+QQACgAAACwAAAAAlgBk' +
+		'AIEAAAB9fX329vYAAAAC4JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3' +
+		'n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+' +
+		'f0uv2OvwT2fP6iD7gAMEhICAeImIAYiFDoOPi22KcouZfw6BhZGUBZeYlp6LbJiTD6C' +
+		'Qqg6Vm6eQqqKtkZ24iaKtrKunpQa9tmmju7Wwu7KFtMi3oYDMzompkHHS09TV1tfY2d' +
+		'rb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f31QAADs=';
+
+	/**
+	 * Basic check for browser support
+	 * @type {boolean}
+	 * @private
+	 */
+	var isSupported = typeof window.FileReader !== 'undefined';
+	var base64DataUri = /data:[^;]+;base64,/i;
+
+	function base64DataUriToBlob(url) {
+		// 5 is length of "data:" prefix
+		var mime = url.substr(5, url.indexOf(';') - 5);
+		var data = atob(url.substr(url.indexOf(',') + 1));
+		/* global Uint8Array */
+		var binary = new Uint8Array(data.length);
+
+		for (var i = 0; i < data.length; i++) {
+			binary[i] = data[i].charCodeAt(0);
+		}
+
+		try {
+			return new Blob([binary], { type: mime });
+		} catch (e) {
+			return null;
+		}
+	}
+
+	sceditor.plugins.dragdrop = function () {
+		if (!isSupported) {
+			return;
+		}
+
+		var base = this;
+		var	opts;
+		var editor;
+		var handleFile;
+		var container;
+		var cover;
+		var placeholderId = 0;
+
+
+		function hideCover() {
+			cover.style.display = 'none';
+			container.className = container.className.replace(/(^| )dnd( |$)/g, '');
+		}
+
+		function showCover() {
+			if (cover.style.display === 'none') {
+				cover.style.display = 'block';
+				container.className += ' dnd';
+			}
+		}
+
+		function isAllowed(file) {
+			// FF sets type to application/x-moz-file until it has been dropped
+			if (file.type !== 'application/x-moz-file' && opts.allowedTypes &&
+				opts.allowedTypes.indexOf(file.type) < 0) {
+				return false;
+			}
+
+			return opts.isAllowed ? opts.isAllowed(file) : true;
+		};
+
+		function createHolder(toReplace) {
+			var placeholder = document.createElement('img');
+			placeholder.src = loadingGif;
+			placeholder.className = 'sceditor-ignore';
+			placeholder.id = 'sce-dragdrop-' + placeholderId++;
+
+			function replace(html) {
+				var node = editor
+					.getBody()
+					.ownerDocument
+					.getElementById(placeholder.id);
+
+				if (node) {
+					if (typeof html === 'string') {
+						node.insertAdjacentHTML('afterend', html);
+					}
+
+					node.parentNode.removeChild(node);
+				}
+			}
+
+			return function () {
+				if (toReplace) {
+					toReplace.parentNode.replaceChild(placeholder, toReplace);
+				} else {
+					editor.wysiwygEditorInsertHtml(placeholder.outerHTML);
+				}
+
+				return {
+					insert: function (html) {
+						replace(html);
+					},
+					cancel: replace
+				};
+			};
+		}
+
+		function handleDragOver(e) {
+			var dt    = e.dataTransfer;
+			var files = dt.files.length || !dt.items ? dt.files : dt.items;
+
+			for (var i = 0; i < files.length; i++) {
+				// Dragging a string should be left to default
+				if (files[i].kind === 'string') {
+					return;
+				}
+			}
+
+			showCover();
+			e.preventDefault();
+		}
+
+		function handleDrop(e) {
+			var dt    = e.dataTransfer;
+			var files = dt.files.length || !dt.items ? dt.files : dt.items;
+
+			hideCover();
+
+			for (var i = 0; i < files.length; i++) {
+				// Dragging a string should be left to default
+				if (files[i].kind === 'string') {
+					return;
+				}
+
+				if (isAllowed(files[i])) {
+					handleFile(files[i], createHolder());
+				}
+			}
+
+			e.preventDefault();
+		}
+
+		base.signalReady = function () {
+			editor = this;
+			opts = editor.opts.dragdrop || {};
+			handleFile = opts.handleFile;
+
+			container = editor.getContentAreaContainer().parentNode;
+
+			cover = container.appendChild(sceditor.dom.parseHTML(
+				'<div class="sceditor-dnd-cover" style="display: none">' +
+					'<p>' + editor._('Drop files here') + '</p>' +
+				'</div>'
+			).firstChild);
+
+			container.addEventListener('dragover', handleDragOver);
+			container.addEventListener('dragleave', hideCover);
+			container.addEventListener('dragend', hideCover);
+			container.addEventListener('drop', handleDrop);
+
+			editor.getBody().addEventListener('dragover', handleDragOver);
+			editor.getBody().addEventListener('drop', hideCover);
+		};
+
+		base.signalPasteHtml = function (paste) {
+			if (!('handlePaste' in opts) || opts.handlePaste) {
+				var div = document.createElement('div');
+				div.innerHTML = paste.val;
+
+				var images = div.querySelectorAll('img');
+				for (var i = 0; i < images.length; i++) {
+					var image = images[i];
+
+					if (base64DataUri.test(image.src)) {
+						var file = base64DataUriToBlob(image.src);
+						if (file && isAllowed(file)) {
+							handleFile(file, createHolder(image));
+						} else {
+							image.parentNode.removeChild(image);
+						}
+					}
+				}
+
+				paste.val = div.innerHTML;
+			}
+		};
+	};
+})(sceditor);

+ 127 - 0
public/js/sc/plugins/format.js

@@ -0,0 +1,127 @@
+/**
+ * SCEditor Paragraph Formatting Plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-2013, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @fileoverview SCEditor Paragraph Formatting Plugin
+ * @author Sam Clarke
+ */
+(function (sceditor) {
+	'use strict';
+
+	sceditor.plugins.format = function () {
+		var base = this;
+
+		/**
+		 * Default tags
+		 * @type {Object}
+		 * @private
+		 */
+		var tags = {
+			p: 'Paragraph',
+			h1: 'Heading 1',
+			h2: 'Heading 2',
+			h3: 'Heading 3',
+			h4: 'Heading 4',
+			h5: 'Heading 5',
+			h6: 'Heading 6',
+			address: 'Address',
+			pre: 'Preformatted Text'
+		};
+
+		/**
+		 * Private functions
+		 * @private
+		 */
+		var	insertTag,
+			formatCmd;
+
+
+		base.init = function () {
+			var	opts  = this.opts,
+				pOpts = opts.paragraphformat;
+
+			// Don't enable if the BBCode plugin is enabled.
+			if (opts.format && opts.format === 'bbcode') {
+				return;
+			}
+
+			if (pOpts) {
+				if (pOpts.tags) {
+					tags = pOpts.tags;
+				}
+
+				if (pOpts.excludeTags) {
+					pOpts.excludeTags.forEach(function (val) {
+						delete tags[val];
+					});
+				}
+			}
+
+			if (!this.commands.format) {
+				this.commands.format = {
+					exec: formatCmd,
+					txtExec: formatCmd,
+					tooltip: 'Format Paragraph'
+				};
+			}
+
+			if (opts.toolbar === sceditor.defaultOptions.toolbar) {
+				opts.toolbar = opts.toolbar.replace(',color,',
+					',color,format,');
+			}
+		};
+
+		/**
+		 * Inserts the specified tag into the editor
+		 *
+		 * @param  {sceditor} editor
+		 * @param  {string} tag
+		 * @private
+		 */
+		insertTag = function (editor, tag) {
+			if (editor.sourceMode()) {
+				editor.insert('<' + tag + '>', '</' + tag + '>');
+			} else {
+				editor.execCommand('formatblock', '<' + tag + '>');
+			}
+
+		};
+
+		/**
+		 * Function for the exec and txtExec properties
+		 *
+		 * @param  {node} caller
+		 * @private
+		 */
+		formatCmd = function (caller) {
+			var	editor   = this,
+				content = document.createElement('div');
+
+			sceditor.utils.each(tags, function (tag, val) {
+				var link = document.createElement('a');
+				link.className = 'sceditor-option';
+				link.textContent = val.name || val;
+				link.addEventListener('click', function (e) {
+					editor.closeDropDown(true);
+
+					if (val.exec) {
+						val.exec(editor);
+					} else {
+						insertTag(editor, tag);
+					}
+
+					e.preventDefault();
+				});
+
+				content.appendChild(link);
+			});
+
+			editor.createDropDown(caller, 'format', content);
+		};
+	};
+})(sceditor);

+ 78 - 0
public/js/sc/plugins/plaintext.js

@@ -0,0 +1,78 @@
+/**
+ * SCEditor Plain Text Plugin
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2016, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ */
+(function (sceditor) {
+	'use strict';
+
+	var utils = sceditor.utils;
+	var dom = sceditor.dom;
+
+	/**
+	 * Options:
+	 *
+	 * pastetext.addButton - If to replace the plaintext button with a toggle
+	 *                       button that enables and disables plain text mode.
+	 *
+	 * pastetext.enabled - If the plain text button should be enabled at start
+	 *                     up. Only applies if addButton is enabled.
+	 */
+	sceditor.plugins.plaintext = function () {
+		var plainTextEnabled = true;
+
+		this.init = function () {
+			var commands = this.commands;
+			var opts = this.opts;
+
+			if (opts && opts.plaintext && opts.plaintext.addButton) {
+				plainTextEnabled = opts.plaintext.enabled;
+
+				commands.pastetext = utils.extend(commands.pastetext || {}, {
+					state: function () {
+						return plainTextEnabled ? 1 : 0;
+					},
+					exec: function () {
+						plainTextEnabled = !plainTextEnabled;
+					}
+				});
+			}
+		};
+
+		this.signalPasteRaw = function (data) {
+			if (plainTextEnabled) {
+				if (data.html && !data.text) {
+					var div = document.createElement('div');
+					div.innerHTML = data.html;
+
+					// TODO: Refactor into private shared module with editor
+					// innerText adds two newlines after <p> tags so convert
+					// them to <div> tags
+					utils.each(div.querySelectorAll('p'), function (_, elm) {
+						dom.convertElement(elm, 'div');
+					});
+					// Remove collapsed <br> tags as innerText converts them to
+					// newlines
+					utils.each(div.querySelectorAll('br'), function (_, elm) {
+						if (!elm.nextSibling ||
+						!dom.isInline(elm.nextSibling, true)) {
+							elm.parentNode.removeChild(elm);
+						}
+					});
+
+					document.body.appendChild(div);
+					data.text = div.innerText;
+					document.body.removeChild(div);
+				}
+
+				data.html = null;
+			}
+		};
+	};
+}(sceditor));

+ 372 - 0
public/js/sc/plugins/undo.js

@@ -0,0 +1,372 @@
+(function (sceditor) {
+	'use strict';
+
+	sceditor.plugins.undo = function () {
+		var base = this;
+		var sourceEditor;
+		var editor;
+		var body;
+		var lastInputType = '';
+		var charChangedCount = 0;
+		var isInPatchedFn = false;
+		/**
+		 * If currently restoring a state
+		 * Should ignore events while it's happening
+		 */
+		var isApplying = false;
+		/**
+		 * If current selection change event has already been stored
+		 */
+		var isSelectionChangeHandled = false;
+
+		var undoLimit  = 50;
+		var undoStates = [];
+		var redoPosition = 0;
+		var lastState;
+
+		/**
+		 * Sets the editor to the specified state.
+		 * @param  {Object} state
+		 * @private
+		 */
+		function applyState(state) {
+			isApplying = true;
+			editor.sourceMode(state.sourceMode);
+
+			if (state.sourceMode) {
+				editor.val(state.value, false);
+				editor.sourceEditorCaret(state.caret);
+			} else {
+				editor.getBody().innerHTML = state.value;
+
+				// Caret may not exist for the first state in Firefox
+				if (state.caret) {
+					var range = editor.getRangeHelper().selectedRange();
+					setRangePositions(range, state.caret);
+					editor.getRangeHelper().selectRange(range);
+				}
+			}
+
+			editor.focus();
+			isApplying = false;
+		};
+
+		/**
+		 * Patches a function on the object to call store() after invocation
+		 * @param {Object} obj
+		 * @param {string} fn
+		 */
+		function patch(obj, fn) {
+			var origFn = obj[fn];
+			obj[fn] = function () {
+				// sourceMode calls other patched methods so need to ignore them
+				var ignore = isInPatchedFn;
+
+				// Store caret position before any change is made
+				if (!ignore && !isApplying && lastState &&
+						editor.getRangeHelper().hasSelection()) {
+					updateLastState();
+				}
+
+				isInPatchedFn = true;
+				origFn.apply(this, arguments);
+
+				if (!ignore) {
+					isInPatchedFn = false;
+
+					if (!isApplying) {
+						storeState();
+						lastInputType = '';
+					}
+				}
+			};
+		}
+
+		/**
+		 * Stores the editors current state
+		 */
+		function storeState() {
+			if (redoPosition) {
+				undoStates.length -= redoPosition;
+				redoPosition = 0;
+			}
+
+			if (undoLimit > 0 && undoStates.length > undoLimit) {
+				undoStates.shift();
+			}
+
+			lastState = {};
+			updateLastState();
+			undoStates.push(lastState);
+		}
+
+		/**
+		 * Updates the last saved state with the editors current state
+		 */
+		function updateLastState() {
+			var sourceMode = editor.sourceMode();
+			lastState.caret = sourceMode ? editor.sourceEditorCaret() :
+				getRangePositions(editor.getRangeHelper().selectedRange());
+			lastState.sourceMode = sourceMode;
+			lastState.value = sourceMode ?
+				editor.getSourceEditorValue(false) :
+				editor.getBody().innerHTML;
+		}
+
+		base.init = function () {
+			// The this variable will be set to the instance of the editor
+			// calling it, hence why the plugins "this" is saved to the base
+			// variable.
+			editor = this;
+
+			undoLimit = editor.undoLimit || undoLimit;
+
+			editor.addShortcut('ctrl+z', base.undo);
+			editor.addShortcut('ctrl+shift+z', base.redo);
+			editor.addShortcut('ctrl+y', base.redo);
+		};
+
+		function documentSelectionChangeHandler() {
+			if (sourceEditor === document.activeElement) {
+				base.signalSelectionchangedEvent();
+			}
+		}
+
+		base.signalReady = function () {
+			sourceEditor = editor.getContentAreaContainer().nextSibling;
+			body = editor.getBody();
+
+			// Store initial state
+			storeState();
+
+			// Patch methods that allow inserting content into the editor
+			// programmatically
+			// TODO: remove this when there is a built in event to handle it
+			patch(editor, 'setWysiwygEditorValue');
+			patch(editor, 'setSourceEditorValue');
+			patch(editor, 'sourceEditorInsertText');
+			patch(editor.getRangeHelper(), 'insertNode');
+			patch(editor, 'toggleSourceMode');
+
+			/**
+			 * Handles the before input event so can override built in
+			 * undo / redo
+			 * @param {InputEvent} e
+			 */
+			function beforeInputHandler(e) {
+				if (e.inputType === 'historyUndo') {
+					base.undo();
+					e.preventDefault();
+				} else if (e.inputType === 'historyRedo') {
+					base.redo();
+					e.preventDefault();
+				}
+			}
+
+			body.addEventListener('beforeinput', beforeInputHandler);
+			sourceEditor.addEventListener('beforeinput', beforeInputHandler);
+
+			/**
+			 * Should always store state at the end of composing
+			 */
+			function compositionHandler() {
+				lastInputType = '';
+				storeState();
+			}
+			body.addEventListener('compositionend', compositionHandler);
+			sourceEditor.addEventListener('compositionend', compositionHandler);
+
+			// Chrome doesn't trigger selectionchange on textarea so need to
+			// listen to global event
+			document.addEventListener('selectionchange',
+				documentSelectionChangeHandler);
+		};
+
+		base.destroy = function () {
+			document.removeEventListener('selectionchange',
+				documentSelectionChangeHandler);
+		};
+
+		base.undo = function () {
+			lastState = null;
+
+			if (redoPosition < undoStates.length - 1) {
+				redoPosition++;
+				applyState(undoStates[undoStates.length - 1 - redoPosition]);
+			}
+
+			return false;
+		};
+
+		base.redo = function () {
+			if (redoPosition > 0) {
+				redoPosition--;
+				applyState(undoStates[undoStates.length - 1 - redoPosition]);
+			}
+
+			return false;
+		};
+
+		/**
+		 * Handle the selectionchanged event so can store the last caret
+		 * position before the input so undoing places it in the right place
+		 */
+		base.signalSelectionchangedEvent = function () {
+			if (isApplying || isSelectionChangeHandled) {
+				isSelectionChangeHandled = false;
+				return;
+			}
+			if (lastState) {
+				updateLastState();
+			}
+			lastInputType = '';
+		};
+
+		/**
+		 * Handles the input event
+		 * @param {InputEvent} e
+		 */
+		base.signalInputEvent = function (e) {
+			// InputType is one of
+			// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
+			// Most should cause a full undo item to be added so only need to
+			// handle a few of them
+			var inputType = e.inputType;
+
+			// Should ignore selection changes that occur because of input
+			// events as already handling them
+			isSelectionChangeHandled = true;
+
+			// inputType should be supported by all supported browsers
+			// except IE 11 in runWithoutWysiwygSupport. Shouldn't be an issue
+			// as native handling will mostly work there.
+			// Ignore if composing as will handle composition end instead
+			if (!inputType || e.isComposing) {
+				return;
+			}
+
+			switch (e.inputType) {
+				case 'deleteContentBackward':
+					if (lastState && lastInputType === inputType &&
+						charChangedCount < 20) {
+						updateLastState();
+					} else {
+						storeState();
+						charChangedCount = 0;
+					}
+
+					lastInputType = inputType;
+					break;
+
+				case 'insertText':
+					charChangedCount += e.data ? e.data.length : 1;
+
+					if (lastState && lastInputType === inputType &&
+							charChangedCount < 20 && !/\s$/.test(e.data)) {
+						updateLastState();
+					} else {
+						storeState();
+						charChangedCount = 0;
+					}
+
+					lastInputType = inputType;
+					break;
+				default:
+					lastInputType = 'sce-misc';
+					charChangedCount = 0;
+					storeState();
+					break;
+			}
+		};
+
+		/**
+		 * Creates a positions object form passed range
+		 * @param {Range} range
+		 * @return {Object<string, Array<number>}
+		 */
+		function getRangePositions(range) {
+			// In Firefox, range may not exist when the editor is first created
+			// due to Firefox returning null from getSelection() when the
+			// editors iframe is first created. See issue #910
+			if (!range) {
+				return;
+			}
+
+			// Merge any adjacent text nodes as it will be done by innerHTML
+			// which would cause positions to be off if not done
+			body.normalize();
+
+			return {
+				startPositions:
+					nodeToPositions(range.startContainer, range.startOffset),
+				endPositions:
+					nodeToPositions(range.endContainer, range.endOffset)
+			};
+		}
+
+		/**
+		 * Sets the range start/end based on the positions object
+		 * @param {Range} range
+		 * @param {Object<string, Array<number>>} positions
+		 */
+		function setRangePositions(range, positions) {
+			try {
+				var startPositions = positions.startPositions;
+				var endPositions = positions.endPositions;
+
+				range.setStart(positionsToNode(body, startPositions),
+					startPositions[0]);
+				range.setEnd(positionsToNode(body, endPositions),
+					endPositions[0]);
+			} catch (e) {
+				if (console && console.warn) {
+					console.warn('[SCEditor] Undo plugin lost caret', e);
+				}
+			}
+		}
+
+		/**
+		 * Converts the passed container and offset into positions array
+		 * @param {Node} container
+		 * @param {number} offset
+		 * @returns {Array<number>}
+		 */
+		function nodeToPositions(container, offset) {
+			var positions = [offset];
+			var node = container;
+
+			while (node && node.tagName !== 'BODY') {
+				positions.push(nodeIndex(node));
+				node = node.parentNode;
+			}
+
+			return positions;
+		}
+
+		/**
+		 * Returns index of passed node
+		 * @param {Node} node
+		 * @returns {number}
+		 */
+		function nodeIndex(node) {
+			var i = 0;
+			while ((node = node.previousSibling)) {
+				i++;
+			}
+			return i;
+		}
+
+		/**
+		 * Gets the container node from the positions array
+		 * @param {Node} node
+		 * @param {Array<number>} positions
+		 * @returns {Node}
+		 */
+		function positionsToNode(node, positions) {
+			for (var i = positions.length - 1; node && i > 0; i--) {
+				node = node.childNodes[positions[i]];
+			}
+			return node;
+		}
+	};
+}(sceditor));

+ 97 - 0
public/js/sc/plugins/v1compat.js

@@ -0,0 +1,97 @@
+/**
+ * Version 1 compatibility plugin
+ *
+ * Patches commands and BBCodes set with
+ * command.set and bbcode.set to wrap DOM
+ * node arguments in jQuery objects.
+ *
+ * Should only be used to ease migrating.
+ */
+(function (sceditor, $) {
+	'use strict';
+
+	var plugins = sceditor.plugins;
+
+	/**
+	 * Patches a method to wrap and DOM nodes in a jQuery object
+	 * @private
+	 */
+	function patchMethodArguments(fn) {
+		if (fn._scePatched) {
+			return fn;
+		}
+
+		var patch = function () {
+			var args = [];
+
+			for (var i = 0; i < arguments.length; i++) {
+				var arg = arguments[i];
+
+				if (arg && arg.nodeType) {
+					args.push($(arg));
+				} else {
+					args.push(arg);
+				}
+			}
+
+			return fn.apply(this, args);
+		};
+
+		patch._scePatched = true;
+		return patch;
+	}
+
+	/**
+	 * Patches a method to wrap any return value in a jQuery object
+	 * @private
+	 */
+	function patchMethodReturn(fn) {
+		if (fn._scePatched) {
+			return fn;
+		}
+
+		var patch = function () {
+			return $(fn.apply(this, arguments));
+		};
+
+		patch._scePatched = true;
+		return patch;
+	}
+
+	var oldSet = sceditor.command.set;
+	sceditor.command.set = function (name, cmd) {
+		if (cmd && typeof cmd.exec === 'function') {
+			cmd.exec = patchMethodArguments(cmd.exec);
+		}
+
+		if (cmd && typeof cmd.txtExec === 'function') {
+			cmd.txtExec = patchMethodArguments(cmd.txtExec);
+		}
+
+		return oldSet.call(this, name, cmd);
+	};
+
+	if (plugins.bbcode) {
+		var oldBBCodeSet = plugins.bbcode.bbcode.set;
+		plugins.bbcode.bbcode.set = function (name, bbcode) {
+			if (bbcode && typeof bbcode.format === 'function') {
+				bbcode.format = patchMethodArguments(bbcode.format);
+			}
+
+			return oldBBCodeSet.call(this, name, bbcode);
+		};
+	};
+
+	var oldCreate = sceditor.create;
+	sceditor.create = function (textarea, options) {
+		oldCreate.call(this, textarea, options);
+
+		if (textarea && textarea._sceditor) {
+			var editor = textarea._sceditor;
+
+			editor.getBody = patchMethodReturn(editor.getBody);
+			editor.getContentAreaContainer =
+				patchMethodReturn(editor.getContentAreaContainer);
+		}
+	};
+}(sceditor, jQuery));

File diff suppressed because it is too large
+ 4383 - 0
public/js/sc/sceditor.js


+ 85 - 0
public/js/sc/themes/content/default.css

@@ -0,0 +1,85 @@
+/*! SCEditor | (C) 2011-2013, Sam Clarke | sceditor.com/license */
+html, body, p, code:before, table {
+	margin: 0;
+	padding: 0;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 14px;
+	color: #111;
+	line-height: 1.25;
+	overflow: visible;
+}
+html {
+	height: 100%;
+}
+.ios {
+	/* Needed for iOS scrolling bug fix */
+	overflow: auto;
+	-webkit-overflow-scrolling: touch;
+}
+.ios body {
+	/* Needed for iOS scrolling bug fix */
+	position: relative;
+	overflow: auto;
+}
+body {
+	/* Needed to make sure body covers the whole editor and that
+		long lines don't cause horizontal scrolling */
+	min-height: 100%;
+	word-wrap: break-word;
+}
+
+body.placeholder::before {
+    content: attr(placeholder);
+    color: #555;
+    font-style: italic;
+}
+
+ul, ol {
+	margin-top: 0;
+	margin-bottom: 0;
+	padding-top: 0;
+	padding-bottom: 0;
+}
+
+table, td {
+	border: 1px dotted #000;
+	empty-cells: show;
+}
+
+table td {
+	min-width: 5px;
+}
+
+code {
+	display: block;
+	background: #f1f1f1;
+	white-space: pre;
+	padding: 1em;
+	text-align: left;
+	margin: .25em 0;
+	direction: ltr;
+}
+
+blockquote {
+	background: #fff7d9;
+	margin: .25em 0;
+	border-left: .3em solid #f4e59f;
+	padding: .5em .5em .5em .75em;
+}
+blockquote cite {
+	font-weight: bold;
+	display: block;
+	font-size: 1em;
+	margin: 0 -.5em .25em -.75em;
+	padding: 0 .5em .15em .75em;
+	border-bottom: 1px solid #f4e59f;
+}
+
+h1, h2, h3, h4, h5, h6 {
+	padding: 0; margin: 0;
+}
+
+/* Prevent empty paragraphs from collapsing */
+div, p {
+	min-height: 1.25em;
+}

+ 530 - 0
public/js/sc/themes/default.css

@@ -0,0 +1,530 @@
+/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */
+/**
+ * Default SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-16, Sam Clarke
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+div.sceditor-grip,
+.sceditor-button div {
+  background-image: url("famfamfam.png");
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+}
+.sceditor-button-youtube div {
+  background-position: 0px 0px;
+}
+.sceditor-button-link div {
+  background-position: 0px -16px;
+}
+.sceditor-button-unlink div {
+  background-position: 0px -32px;
+}
+.sceditor-button-underline div {
+  background-position: 0px -48px;
+}
+.sceditor-button-time div {
+  background-position: 0px -64px;
+}
+.sceditor-button-table div {
+  background-position: 0px -80px;
+}
+.sceditor-button-superscript div {
+  background-position: 0px -96px;
+}
+.sceditor-button-subscript div {
+  background-position: 0px -112px;
+}
+.sceditor-button-strike div {
+  background-position: 0px -128px;
+}
+.sceditor-button-source div {
+  background-position: 0px -144px;
+}
+.sceditor-button-size div {
+  background-position: 0px -160px;
+}
+.sceditor-button-rtl div {
+  background-position: 0px -176px;
+}
+.sceditor-button-right div {
+  background-position: 0px -192px;
+}
+.sceditor-button-removeformat div {
+  background-position: 0px -208px;
+}
+.sceditor-button-quote div {
+  background-position: 0px -224px;
+}
+.sceditor-button-print div {
+  background-position: 0px -240px;
+}
+.sceditor-button-pastetext div {
+  background-position: 0px -256px;
+}
+.sceditor-button-paste div {
+  background-position: 0px -272px;
+}
+.sceditor-button-outdent div {
+  background-position: 0px -288px;
+}
+.sceditor-button-orderedlist div {
+  background-position: 0px -304px;
+}
+.sceditor-button-maximize div {
+  background-position: 0px -320px;
+}
+.sceditor-button-ltr div {
+  background-position: 0px -336px;
+}
+.sceditor-button-left div {
+  background-position: 0px -352px;
+}
+.sceditor-button-justify div {
+  background-position: 0px -368px;
+}
+.sceditor-button-italic div {
+  background-position: 0px -384px;
+}
+.sceditor-button-indent div {
+  background-position: 0px -400px;
+}
+.sceditor-button-image div {
+  background-position: 0px -416px;
+}
+.sceditor-button-horizontalrule div {
+  background-position: 0px -432px;
+}
+.sceditor-button-format div {
+  background-position: 0px -448px;
+}
+.sceditor-button-font div {
+  background-position: 0px -464px;
+}
+.sceditor-button-emoticon div {
+  background-position: 0px -480px;
+}
+.sceditor-button-email div {
+  background-position: 0px -496px;
+}
+.sceditor-button-date div {
+  background-position: 0px -512px;
+}
+.sceditor-button-cut div {
+  background-position: 0px -528px;
+}
+.sceditor-button-copy div {
+  background-position: 0px -544px;
+}
+.sceditor-button-color div {
+  background-position: 0px -560px;
+}
+.sceditor-button-code div {
+  background-position: 0px -576px;
+}
+.sceditor-button-center div {
+  background-position: 0px -592px;
+}
+.sceditor-button-bulletlist div {
+  background-position: 0px -608px;
+}
+.sceditor-button-bold div {
+  background-position: 0px -624px;
+}
+div.sceditor-grip {
+  background-position: 0px -640px;
+  width: 10px;
+  height: 10px;
+}
+.rtl div.sceditor-grip {
+  background-position: 0px -650px;
+}
+/**
+ * SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+/*---------------------------------------------------
+    LESS Elements 0.7
+  ---------------------------------------------------
+    A set of useful LESS mixins
+    More info at: http://lesselements.com
+  ---------------------------------------------------*/
+.sceditor-container {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  position: relative;
+  background: #fff;
+  border: 1px solid #d9d9d9;
+  font-size: 13px;
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  color: #333;
+  line-height: 1;
+  font-weight: bold;
+  height: 250px;
+  border-radius: 4px;
+  background-clip: padding-box;
+}
+.sceditor-container *,
+.sceditor-container *:before,
+.sceditor-container *:after {
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.sceditor-container,
+.sceditor-container div,
+div.sceditor-dropdown,
+div.sceditor-dropdown div {
+  padding: 0;
+  margin: 0;
+  z-index: 3;
+}
+.sceditor-container iframe,
+.sceditor-container textarea {
+  display: block;
+  -ms-flex: 1 1 0%;
+  flex: 1 1 0%;
+  line-height: 1.25;
+  border: 0;
+  outline: none;
+  font-family: Verdana, Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  color: #111;
+  padding: 0;
+  margin: 5px;
+  resize: none;
+  background: #fff;
+  height: auto !important;
+  width: auto !important;
+  width: calc(100% - 10px) !important;
+  min-height: 1px;
+}
+.sceditor-container textarea {
+  margin: 7px 5px;
+}
+div.sceditor-dnd-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background: rgba(255, 255, 255, 0.2);
+  border: 5px dashed #aaa;
+  z-index: 200;
+  font-size: 2em;
+  text-align: center;
+  color: #aaa;
+}
+div.sceditor-dnd-cover p {
+  position: relative;
+  top: 45%;
+  pointer-events: none;
+}
+div.sceditor-resize-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  background: #000;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  opacity: 0.3;
+}
+div.sceditor-grip {
+  overflow: hidden;
+  width: 10px;
+  height: 10px;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  z-index: 3;
+  line-height: 0;
+}
+div.sceditor-grip.has-icon {
+  background-image: none;
+}
+.sceditor-maximize {
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100% !important;
+  width: 100% !important;
+  border-radius: 0;
+  background-clip: padding-box;
+  z-index: 2000;
+}
+html.sceditor-maximize,
+body.sceditor-maximize {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  margin: 0;
+  overflow: hidden;
+}
+.sceditor-maximize div.sceditor-grip {
+  display: none;
+}
+.sceditor-maximize div.sceditor-toolbar {
+  border-radius: 0;
+  background-clip: padding-box;
+}
+/**
+	 * Dropdown styleing
+	 */
+div.sceditor-dropdown {
+  position: absolute;
+  border: 1px solid #ccc;
+  background: #fff;
+  z-index: 4000;
+  padding: 10px;
+  font-weight: normal;
+  font-size: 15px;
+  border-radius: 2px;
+  background-clip: padding-box;
+  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2);
+}
+div.sceditor-dropdown *,
+div.sceditor-dropdown *:before,
+div.sceditor-dropdown *:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+div.sceditor-dropdown a,
+div.sceditor-dropdown a:link {
+  color: #333;
+}
+div.sceditor-dropdown form {
+  margin: 0;
+}
+div.sceditor-dropdown label {
+  display: block;
+  font-weight: bold;
+  color: #3c3c3c;
+  padding: 4px 0;
+}
+div.sceditor-dropdown input,
+div.sceditor-dropdown textarea {
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  outline: 0;
+  padding: 4px;
+  border: 1px solid #ccc;
+  border-top-color: #888;
+  margin: 0 0 0.75em;
+  border-radius: 1px;
+  background-clip: padding-box;
+}
+div.sceditor-dropdown textarea {
+  padding: 6px;
+}
+div.sceditor-dropdown input:focus,
+div.sceditor-dropdown textarea:focus {
+  border-color: #aaa;
+  border-top-color: #666;
+  box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1);
+}
+div.sceditor-dropdown .button {
+  font-weight: bold;
+  color: #444;
+  padding: 6px 12px;
+  background: #ececec;
+  border: solid 1px #ccc;
+  border-radius: 2px;
+  background-clip: padding-box;
+  cursor: pointer;
+  margin: 0.3em 0 0;
+}
+div.sceditor-dropdown .button:hover {
+  background: #f3f3f3;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+div.sceditor-font-picker,
+div.sceditor-fontsize-picker,
+div.sceditor-format {
+  padding: 6px 0;
+}
+div.sceditor-color-picker {
+  padding: 4px;
+}
+div.sceditor-emoticons,
+div.sceditor-more-emoticons {
+  padding: 0;
+}
+.sceditor-pastetext textarea {
+  border: 1px solid #bbb;
+  width: 20em;
+}
+.sceditor-emoticons img,
+.sceditor-more-emoticons img {
+  padding: 0;
+  cursor: pointer;
+  margin: 2px;
+}
+.sceditor-more {
+  border-top: 1px solid #bbb;
+  display: block;
+  text-align: center;
+  cursor: pointer;
+  font-weight: bold;
+  padding: 6px 0;
+}
+.sceditor-dropdown a:hover {
+  background: #eee;
+}
+.sceditor-fontsize-option,
+.sceditor-font-option,
+.sceditor-format a {
+  display: block;
+  padding: 7px 10px;
+  cursor: pointer;
+  text-decoration: none;
+  color: #222;
+}
+.sceditor-fontsize-option {
+  padding: 7px 13px;
+}
+.sceditor-color-column {
+  float: left;
+}
+.sceditor-color-option {
+  display: block;
+  border: 2px solid #fff;
+  height: 18px;
+  width: 18px;
+  overflow: hidden;
+}
+.sceditor-color-option:hover {
+  border: 1px solid #aaa;
+}
+/**
+	 * Toolbar styleing
+	 */
+div.sceditor-toolbar {
+  flex-shrink: 0;
+  overflow: hidden;
+  padding: 3px 5px 2px;
+  background: #f7f7f7;
+  border-bottom: 1px solid #c0c0c0;
+  line-height: 0;
+  text-align: left;
+  user-select: none;
+  border-radius: 3px 3px 0 0;
+  background-clip: padding-box;
+}
+div.sceditor-group {
+  display: inline-block;
+  background: #ddd;
+  margin: 1px 5px 1px 0;
+  padding: 1px;
+  border-bottom: 1px solid #aaa;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button {
+  float: left;
+  cursor: pointer;
+  padding: 3px 5px;
+  width: 16px;
+  height: 20px;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2);
+}
+.sceditor-button:active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3);
+}
+.sceditor-button.disabled:hover {
+  background: inherit;
+  cursor: default;
+  box-shadow: none;
+}
+.sceditor-button,
+.sceditor-button div {
+  display: block;
+}
+.sceditor-button svg {
+  display: inline-block;
+  height: 16px;
+  width: 16px;
+  margin: 2px 0;
+  fill: #111;
+  text-decoration: none;
+  pointer-events: none;
+  line-height: 1;
+}
+.sceditor-button.disabled svg {
+  fill: #888;
+}
+.sceditor-button div {
+  display: inline-block;
+  margin: 2px 0;
+  padding: 0;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0;
+  color: transparent;
+}
+.sceditor-button.has-icon div {
+  display: none;
+}
+.sceditor-button.disabled div {
+  opacity: 0.3;
+}
+.text .sceditor-button,
+.text .sceditor-button div,
+.sceditor-button.text,
+.sceditor-button.text div,
+.text-icon .sceditor-button,
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon,
+.sceditor-button.text-icon div {
+  display: inline-block;
+  width: auto;
+  line-height: 16px;
+  font-size: 1em;
+  color: inherit;
+  text-indent: 0;
+}
+.text-icon .sceditor-button.has-icon div,
+.sceditor-button.has-icon div,
+.text .sceditor-button div,
+.sceditor-button.text div {
+  padding: 0 2px;
+  background: none;
+}
+.text .sceditor-button svg,
+.sceditor-button.text svg {
+  display: none;
+}
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon div {
+  padding: 0 2px 0 20px;
+}
+.rtl div.sceditor-toolbar {
+  text-align: right;
+}
+.rtl .sceditor-button {
+  float: right;
+}
+.rtl div.sceditor-grip {
+  right: auto;
+  left: 0;
+}

+ 548 - 0
public/js/sc/themes/defaultdark.css

@@ -0,0 +1,548 @@
+/*! SCEditor | (C) 2017, Sam Clarke | sceditor.com/license */
+/**
+ * Default SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+div.sceditor-grip,
+.sceditor-button div {
+  background-image: url("famfamfam.png");
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+}
+.sceditor-button-youtube div {
+  background-position: 0px 0px;
+}
+.sceditor-button-link div {
+  background-position: 0px -16px;
+}
+.sceditor-button-unlink div {
+  background-position: 0px -32px;
+}
+.sceditor-button-underline div {
+  background-position: 0px -48px;
+}
+.sceditor-button-time div {
+  background-position: 0px -64px;
+}
+.sceditor-button-table div {
+  background-position: 0px -80px;
+}
+.sceditor-button-superscript div {
+  background-position: 0px -96px;
+}
+.sceditor-button-subscript div {
+  background-position: 0px -112px;
+}
+.sceditor-button-strike div {
+  background-position: 0px -128px;
+}
+.sceditor-button-source div {
+  background-position: 0px -144px;
+}
+.sceditor-button-size div {
+  background-position: 0px -160px;
+}
+.sceditor-button-rtl div {
+  background-position: 0px -176px;
+}
+.sceditor-button-right div {
+  background-position: 0px -192px;
+}
+.sceditor-button-removeformat div {
+  background-position: 0px -208px;
+}
+.sceditor-button-quote div {
+  background-position: 0px -224px;
+}
+.sceditor-button-print div {
+  background-position: 0px -240px;
+}
+.sceditor-button-pastetext div {
+  background-position: 0px -256px;
+}
+.sceditor-button-paste div {
+  background-position: 0px -272px;
+}
+.sceditor-button-outdent div {
+  background-position: 0px -288px;
+}
+.sceditor-button-orderedlist div {
+  background-position: 0px -304px;
+}
+.sceditor-button-maximize div {
+  background-position: 0px -320px;
+}
+.sceditor-button-ltr div {
+  background-position: 0px -336px;
+}
+.sceditor-button-left div {
+  background-position: 0px -352px;
+}
+.sceditor-button-justify div {
+  background-position: 0px -368px;
+}
+.sceditor-button-italic div {
+  background-position: 0px -384px;
+}
+.sceditor-button-indent div {
+  background-position: 0px -400px;
+}
+.sceditor-button-image div {
+  background-position: 0px -416px;
+}
+.sceditor-button-horizontalrule div {
+  background-position: 0px -432px;
+}
+.sceditor-button-format div {
+  background-position: 0px -448px;
+}
+.sceditor-button-font div {
+  background-position: 0px -464px;
+}
+.sceditor-button-emoticon div {
+  background-position: 0px -480px;
+}
+.sceditor-button-email div {
+  background-position: 0px -496px;
+}
+.sceditor-button-date div {
+  background-position: 0px -512px;
+}
+.sceditor-button-cut div {
+  background-position: 0px -528px;
+}
+.sceditor-button-copy div {
+  background-position: 0px -544px;
+}
+.sceditor-button-color div {
+  background-position: 0px -560px;
+}
+.sceditor-button-code div {
+  background-position: 0px -576px;
+}
+.sceditor-button-center div {
+  background-position: 0px -592px;
+}
+.sceditor-button-bulletlist div {
+  background-position: 0px -608px;
+}
+.sceditor-button-bold div {
+  background-position: 0px -624px;
+}
+div.sceditor-grip {
+  background-position: 0px -640px;
+  width: 10px;
+  height: 10px;
+}
+.rtl div.sceditor-grip {
+  background-position: 0px -650px;
+}
+/**
+ * SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+/*---------------------------------------------------
+    LESS Elements 0.7
+  ---------------------------------------------------
+    A set of useful LESS mixins
+    More info at: http://lesselements.com
+  ---------------------------------------------------*/
+.sceditor-container {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  position: relative;
+  background: #fff;
+  border: 1px solid #d9d9d9;
+  font-size: 13px;
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  color: #333;
+  line-height: 1;
+  font-weight: bold;
+  height: 250px;
+  border-radius: 4px;
+  background-clip: padding-box;
+}
+.sceditor-container *,
+.sceditor-container *:before,
+.sceditor-container *:after {
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.sceditor-container,
+.sceditor-container div,
+div.sceditor-dropdown,
+div.sceditor-dropdown div {
+  padding: 0;
+  margin: 0;
+  z-index: 3;
+}
+.sceditor-container iframe,
+.sceditor-container textarea {
+  display: block;
+  -ms-flex: 1 1 0%;
+  flex: 1 1 0%;
+  line-height: 1.25;
+  border: 0;
+  outline: none;
+  font-family: Verdana, Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  color: #111;
+  padding: 0;
+  margin: 5px;
+  resize: none;
+  background: #fff;
+  height: auto !important;
+  width: auto !important;
+  width: calc(100% - 10px) !important;
+  min-height: 1px;
+}
+.sceditor-container textarea {
+  margin: 7px 5px;
+}
+div.sceditor-dnd-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background: rgba(255, 255, 255, 0.2);
+  border: 5px dashed #aaa;
+  z-index: 200;
+  font-size: 2em;
+  text-align: center;
+  color: #aaa;
+}
+div.sceditor-dnd-cover p {
+  position: relative;
+  top: 45%;
+  pointer-events: none;
+}
+div.sceditor-resize-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  background: #000;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  opacity: 0.3;
+}
+div.sceditor-grip {
+  overflow: hidden;
+  width: 10px;
+  height: 10px;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  z-index: 3;
+  line-height: 0;
+}
+div.sceditor-grip.has-icon {
+  background-image: none;
+}
+.sceditor-maximize {
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100% !important;
+  width: 100% !important;
+  border-radius: 0;
+  background-clip: padding-box;
+  z-index: 2000;
+}
+html.sceditor-maximize,
+body.sceditor-maximize {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  margin: 0;
+  overflow: hidden;
+}
+.sceditor-maximize div.sceditor-grip {
+  display: none;
+}
+.sceditor-maximize div.sceditor-toolbar {
+  border-radius: 0;
+  background-clip: padding-box;
+}
+/**
+	 * Dropdown styleing
+	 */
+div.sceditor-dropdown {
+  position: absolute;
+  border: 1px solid #ccc;
+  background: #fff;
+  z-index: 4000;
+  padding: 10px;
+  font-weight: normal;
+  font-size: 15px;
+  border-radius: 2px;
+  background-clip: padding-box;
+  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2);
+}
+div.sceditor-dropdown *,
+div.sceditor-dropdown *:before,
+div.sceditor-dropdown *:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+div.sceditor-dropdown a,
+div.sceditor-dropdown a:link {
+  color: #333;
+}
+div.sceditor-dropdown form {
+  margin: 0;
+}
+div.sceditor-dropdown label {
+  display: block;
+  font-weight: bold;
+  color: #3c3c3c;
+  padding: 4px 0;
+}
+div.sceditor-dropdown input,
+div.sceditor-dropdown textarea {
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  outline: 0;
+  padding: 4px;
+  border: 1px solid #ccc;
+  border-top-color: #888;
+  margin: 0 0 0.75em;
+  border-radius: 1px;
+  background-clip: padding-box;
+}
+div.sceditor-dropdown textarea {
+  padding: 6px;
+}
+div.sceditor-dropdown input:focus,
+div.sceditor-dropdown textarea:focus {
+  border-color: #aaa;
+  border-top-color: #666;
+  box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1);
+}
+div.sceditor-dropdown .button {
+  font-weight: bold;
+  color: #444;
+  padding: 6px 12px;
+  background: #ececec;
+  border: solid 1px #ccc;
+  border-radius: 2px;
+  background-clip: padding-box;
+  cursor: pointer;
+  margin: 0.3em 0 0;
+}
+div.sceditor-dropdown .button:hover {
+  background: #f3f3f3;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+div.sceditor-font-picker,
+div.sceditor-fontsize-picker,
+div.sceditor-format {
+  padding: 6px 0;
+}
+div.sceditor-color-picker {
+  padding: 4px;
+}
+div.sceditor-emoticons,
+div.sceditor-more-emoticons {
+  padding: 0;
+}
+.sceditor-pastetext textarea {
+  border: 1px solid #bbb;
+  width: 20em;
+}
+.sceditor-emoticons img,
+.sceditor-more-emoticons img {
+  padding: 0;
+  cursor: pointer;
+  margin: 2px;
+}
+.sceditor-more {
+  border-top: 1px solid #bbb;
+  display: block;
+  text-align: center;
+  cursor: pointer;
+  font-weight: bold;
+  padding: 6px 0;
+}
+.sceditor-dropdown a:hover {
+  background: #eee;
+}
+.sceditor-fontsize-option,
+.sceditor-font-option,
+.sceditor-format a {
+  display: block;
+  padding: 7px 10px;
+  cursor: pointer;
+  text-decoration: none;
+  color: #222;
+}
+.sceditor-fontsize-option {
+  padding: 7px 13px;
+}
+.sceditor-color-column {
+  float: left;
+}
+.sceditor-color-option {
+  display: block;
+  border: 2px solid #fff;
+  height: 18px;
+  width: 18px;
+  overflow: hidden;
+}
+.sceditor-color-option:hover {
+  border: 1px solid #aaa;
+}
+/**
+	 * Toolbar styleing
+	 */
+div.sceditor-toolbar {
+  flex-shrink: 0;
+  overflow: hidden;
+  padding: 3px 5px 2px;
+  background: #f7f7f7;
+  border-bottom: 1px solid #c0c0c0;
+  line-height: 0;
+  text-align: left;
+  user-select: none;
+  border-radius: 3px 3px 0 0;
+  background-clip: padding-box;
+}
+div.sceditor-group {
+  display: inline-block;
+  background: #ddd;
+  margin: 1px 5px 1px 0;
+  padding: 1px;
+  border-bottom: 1px solid #aaa;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button {
+  float: left;
+  cursor: pointer;
+  padding: 3px 5px;
+  width: 16px;
+  height: 20px;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2);
+}
+.sceditor-button:active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3);
+}
+.sceditor-button.disabled:hover {
+  background: inherit;
+  cursor: default;
+  box-shadow: none;
+}
+.sceditor-button,
+.sceditor-button div {
+  display: block;
+}
+.sceditor-button svg {
+  display: inline-block;
+  height: 16px;
+  width: 16px;
+  margin: 2px 0;
+  fill: #111;
+  text-decoration: none;
+  pointer-events: none;
+  line-height: 1;
+}
+.sceditor-button.disabled svg {
+  fill: #888;
+}
+.sceditor-button div {
+  display: inline-block;
+  margin: 2px 0;
+  padding: 0;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0;
+  color: transparent;
+}
+.sceditor-button.has-icon div {
+  display: none;
+}
+.sceditor-button.disabled div {
+  opacity: 0.3;
+}
+.text .sceditor-button,
+.text .sceditor-button div,
+.sceditor-button.text,
+.sceditor-button.text div,
+.text-icon .sceditor-button,
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon,
+.sceditor-button.text-icon div {
+  display: inline-block;
+  width: auto;
+  line-height: 16px;
+  font-size: 1em;
+  color: inherit;
+  text-indent: 0;
+}
+.text-icon .sceditor-button.has-icon div,
+.sceditor-button.has-icon div,
+.text .sceditor-button div,
+.sceditor-button.text div {
+  padding: 0 2px;
+  background: none;
+}
+.text .sceditor-button svg,
+.sceditor-button.text svg {
+  display: none;
+}
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon div {
+  padding: 0 2px 0 20px;
+}
+.rtl div.sceditor-toolbar {
+  text-align: right;
+}
+.rtl .sceditor-button {
+  float: right;
+}
+.rtl div.sceditor-grip {
+  right: auto;
+  left: 0;
+}
+div.sceditor-toolbar {
+  background: #5d5d5d;
+}
+div.sceditor-group {
+  background: #303030;
+  border-bottom: 1px solid #000;
+}
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active {
+  background: #6b6b6b;
+}
+.sceditor-button svg {
+  fill: #fff;
+}
+.sceditor-button.disabled svg {
+  fill: #777;
+}

BIN
public/js/sc/themes/famfamfam.png


+ 604 - 0
public/js/sc/themes/modern.css

@@ -0,0 +1,604 @@
+/**
+ * Modern theme
+ *
+ * Copyright (C) 2012, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/)
+ * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/)
+ */
+/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */
+/**
+ * Default SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-16, Sam Clarke
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+div.sceditor-grip,
+.sceditor-button div {
+  background-image: url("famfamfam.png");
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+}
+.sceditor-button-youtube div {
+  background-position: 0px 0px;
+}
+.sceditor-button-link div {
+  background-position: 0px -16px;
+}
+.sceditor-button-unlink div {
+  background-position: 0px -32px;
+}
+.sceditor-button-underline div {
+  background-position: 0px -48px;
+}
+.sceditor-button-time div {
+  background-position: 0px -64px;
+}
+.sceditor-button-table div {
+  background-position: 0px -80px;
+}
+.sceditor-button-superscript div {
+  background-position: 0px -96px;
+}
+.sceditor-button-subscript div {
+  background-position: 0px -112px;
+}
+.sceditor-button-strike div {
+  background-position: 0px -128px;
+}
+.sceditor-button-source div {
+  background-position: 0px -144px;
+}
+.sceditor-button-size div {
+  background-position: 0px -160px;
+}
+.sceditor-button-rtl div {
+  background-position: 0px -176px;
+}
+.sceditor-button-right div {
+  background-position: 0px -192px;
+}
+.sceditor-button-removeformat div {
+  background-position: 0px -208px;
+}
+.sceditor-button-quote div {
+  background-position: 0px -224px;
+}
+.sceditor-button-print div {
+  background-position: 0px -240px;
+}
+.sceditor-button-pastetext div {
+  background-position: 0px -256px;
+}
+.sceditor-button-paste div {
+  background-position: 0px -272px;
+}
+.sceditor-button-outdent div {
+  background-position: 0px -288px;
+}
+.sceditor-button-orderedlist div {
+  background-position: 0px -304px;
+}
+.sceditor-button-maximize div {
+  background-position: 0px -320px;
+}
+.sceditor-button-ltr div {
+  background-position: 0px -336px;
+}
+.sceditor-button-left div {
+  background-position: 0px -352px;
+}
+.sceditor-button-justify div {
+  background-position: 0px -368px;
+}
+.sceditor-button-italic div {
+  background-position: 0px -384px;
+}
+.sceditor-button-indent div {
+  background-position: 0px -400px;
+}
+.sceditor-button-image div {
+  background-position: 0px -416px;
+}
+.sceditor-button-horizontalrule div {
+  background-position: 0px -432px;
+}
+.sceditor-button-format div {
+  background-position: 0px -448px;
+}
+.sceditor-button-font div {
+  background-position: 0px -464px;
+}
+.sceditor-button-emoticon div {
+  background-position: 0px -480px;
+}
+.sceditor-button-email div {
+  background-position: 0px -496px;
+}
+.sceditor-button-date div {
+  background-position: 0px -512px;
+}
+.sceditor-button-cut div {
+  background-position: 0px -528px;
+}
+.sceditor-button-copy div {
+  background-position: 0px -544px;
+}
+.sceditor-button-color div {
+  background-position: 0px -560px;
+}
+.sceditor-button-code div {
+  background-position: 0px -576px;
+}
+.sceditor-button-center div {
+  background-position: 0px -592px;
+}
+.sceditor-button-bulletlist div {
+  background-position: 0px -608px;
+}
+.sceditor-button-bold div {
+  background-position: 0px -624px;
+}
+div.sceditor-grip {
+  background-position: 0px -640px;
+  width: 10px;
+  height: 10px;
+}
+.rtl div.sceditor-grip {
+  background-position: 0px -650px;
+}
+/**
+ * SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+/*---------------------------------------------------
+    LESS Elements 0.7
+  ---------------------------------------------------
+    A set of useful LESS mixins
+    More info at: http://lesselements.com
+  ---------------------------------------------------*/
+.sceditor-container {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  position: relative;
+  background: #fff;
+  border: 1px solid #d9d9d9;
+  font-size: 13px;
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  color: #333;
+  line-height: 1;
+  font-weight: bold;
+  height: 250px;
+  border-radius: 4px;
+  background-clip: padding-box;
+}
+.sceditor-container *,
+.sceditor-container *:before,
+.sceditor-container *:after {
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.sceditor-container,
+.sceditor-container div,
+div.sceditor-dropdown,
+div.sceditor-dropdown div {
+  padding: 0;
+  margin: 0;
+  z-index: 3;
+}
+.sceditor-container iframe,
+.sceditor-container textarea {
+  display: block;
+  -ms-flex: 1 1 0%;
+  flex: 1 1 0%;
+  line-height: 1.25;
+  border: 0;
+  outline: none;
+  font-family: Verdana, Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  color: #111;
+  padding: 0;
+  margin: 5px;
+  resize: none;
+  background: #fff;
+  height: auto !important;
+  width: auto !important;
+  width: calc(100% - 10px) !important;
+  min-height: 1px;
+}
+.sceditor-container textarea {
+  margin: 7px 5px;
+}
+div.sceditor-dnd-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background: rgba(255, 255, 255, 0.2);
+  border: 5px dashed #aaa;
+  z-index: 200;
+  font-size: 2em;
+  text-align: center;
+  color: #aaa;
+}
+div.sceditor-dnd-cover p {
+  position: relative;
+  top: 45%;
+  pointer-events: none;
+}
+div.sceditor-resize-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  background: #000;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  opacity: 0.3;
+}
+div.sceditor-grip {
+  overflow: hidden;
+  width: 10px;
+  height: 10px;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  z-index: 3;
+  line-height: 0;
+}
+div.sceditor-grip.has-icon {
+  background-image: none;
+}
+.sceditor-maximize {
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100% !important;
+  width: 100% !important;
+  border-radius: 0;
+  background-clip: padding-box;
+  z-index: 2000;
+}
+html.sceditor-maximize,
+body.sceditor-maximize {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  margin: 0;
+  overflow: hidden;
+}
+.sceditor-maximize div.sceditor-grip {
+  display: none;
+}
+.sceditor-maximize div.sceditor-toolbar {
+  border-radius: 0;
+  background-clip: padding-box;
+}
+/**
+	 * Dropdown styleing
+	 */
+div.sceditor-dropdown {
+  position: absolute;
+  border: 1px solid #ccc;
+  background: #fff;
+  z-index: 4000;
+  padding: 10px;
+  font-weight: normal;
+  font-size: 15px;
+  border-radius: 2px;
+  background-clip: padding-box;
+  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2);
+}
+div.sceditor-dropdown *,
+div.sceditor-dropdown *:before,
+div.sceditor-dropdown *:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+div.sceditor-dropdown a,
+div.sceditor-dropdown a:link {
+  color: #333;
+}
+div.sceditor-dropdown form {
+  margin: 0;
+}
+div.sceditor-dropdown label {
+  display: block;
+  font-weight: bold;
+  color: #3c3c3c;
+  padding: 4px 0;
+}
+div.sceditor-dropdown input,
+div.sceditor-dropdown textarea {
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  outline: 0;
+  padding: 4px;
+  border: 1px solid #ccc;
+  border-top-color: #888;
+  margin: 0 0 0.75em;
+  border-radius: 1px;
+  background-clip: padding-box;
+}
+div.sceditor-dropdown textarea {
+  padding: 6px;
+}
+div.sceditor-dropdown input:focus,
+div.sceditor-dropdown textarea:focus {
+  border-color: #aaa;
+  border-top-color: #666;
+  box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1);
+}
+div.sceditor-dropdown .button {
+  font-weight: bold;
+  color: #444;
+  padding: 6px 12px;
+  background: #ececec;
+  border: solid 1px #ccc;
+  border-radius: 2px;
+  background-clip: padding-box;
+  cursor: pointer;
+  margin: 0.3em 0 0;
+}
+div.sceditor-dropdown .button:hover {
+  background: #f3f3f3;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+div.sceditor-font-picker,
+div.sceditor-fontsize-picker,
+div.sceditor-format {
+  padding: 6px 0;
+}
+div.sceditor-color-picker {
+  padding: 4px;
+}
+div.sceditor-emoticons,
+div.sceditor-more-emoticons {
+  padding: 0;
+}
+.sceditor-pastetext textarea {
+  border: 1px solid #bbb;
+  width: 20em;
+}
+.sceditor-emoticons img,
+.sceditor-more-emoticons img {
+  padding: 0;
+  cursor: pointer;
+  margin: 2px;
+}
+.sceditor-more {
+  border-top: 1px solid #bbb;
+  display: block;
+  text-align: center;
+  cursor: pointer;
+  font-weight: bold;
+  padding: 6px 0;
+}
+.sceditor-dropdown a:hover {
+  background: #eee;
+}
+.sceditor-fontsize-option,
+.sceditor-font-option,
+.sceditor-format a {
+  display: block;
+  padding: 7px 10px;
+  cursor: pointer;
+  text-decoration: none;
+  color: #222;
+}
+.sceditor-fontsize-option {
+  padding: 7px 13px;
+}
+.sceditor-color-column {
+  float: left;
+}
+.sceditor-color-option {
+  display: block;
+  border: 2px solid #fff;
+  height: 18px;
+  width: 18px;
+  overflow: hidden;
+}
+.sceditor-color-option:hover {
+  border: 1px solid #aaa;
+}
+/**
+	 * Toolbar styleing
+	 */
+div.sceditor-toolbar {
+  flex-shrink: 0;
+  overflow: hidden;
+  padding: 3px 5px 2px;
+  background: #f7f7f7;
+  border-bottom: 1px solid #c0c0c0;
+  line-height: 0;
+  text-align: left;
+  user-select: none;
+  border-radius: 3px 3px 0 0;
+  background-clip: padding-box;
+}
+div.sceditor-group {
+  display: inline-block;
+  background: #ddd;
+  margin: 1px 5px 1px 0;
+  padding: 1px;
+  border-bottom: 1px solid #aaa;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button {
+  float: left;
+  cursor: pointer;
+  padding: 3px 5px;
+  width: 16px;
+  height: 20px;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2);
+}
+.sceditor-button:active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3);
+}
+.sceditor-button.disabled:hover {
+  background: inherit;
+  cursor: default;
+  box-shadow: none;
+}
+.sceditor-button,
+.sceditor-button div {
+  display: block;
+}
+.sceditor-button svg {
+  display: inline-block;
+  height: 16px;
+  width: 16px;
+  margin: 2px 0;
+  fill: #111;
+  text-decoration: none;
+  pointer-events: none;
+  line-height: 1;
+}
+.sceditor-button.disabled svg {
+  fill: #888;
+}
+.sceditor-button div {
+  display: inline-block;
+  margin: 2px 0;
+  padding: 0;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0;
+  color: transparent;
+}
+.sceditor-button.has-icon div {
+  display: none;
+}
+.sceditor-button.disabled div {
+  opacity: 0.3;
+}
+.text .sceditor-button,
+.text .sceditor-button div,
+.sceditor-button.text,
+.sceditor-button.text div,
+.text-icon .sceditor-button,
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon,
+.sceditor-button.text-icon div {
+  display: inline-block;
+  width: auto;
+  line-height: 16px;
+  font-size: 1em;
+  color: inherit;
+  text-indent: 0;
+}
+.text-icon .sceditor-button.has-icon div,
+.sceditor-button.has-icon div,
+.text .sceditor-button div,
+.sceditor-button.text div {
+  padding: 0 2px;
+  background: none;
+}
+.text .sceditor-button svg,
+.sceditor-button.text svg {
+  display: none;
+}
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon div {
+  padding: 0 2px 0 20px;
+}
+.rtl div.sceditor-toolbar {
+  text-align: right;
+}
+.rtl .sceditor-button {
+  float: right;
+}
+.rtl div.sceditor-grip {
+  right: auto;
+  left: 0;
+}
+.sceditor-container {
+  border: 1px solid #999;
+}
+.sceditor-container textarea {
+  font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace;
+  background: #2e3436;
+  color: #fff;
+  margin: 0;
+  padding: 5px;
+}
+div.sceditor-toolbar {
+  background: #ccc;
+  background: linear-gradient(to bottom, #cccccc 0%, #b2b2b2 100%);
+}
+div.sceditor-group {
+  display: inline;
+  background: transparent;
+  margin: 0;
+  padding: 0;
+  border: 0;
+}
+.sceditor-button {
+  padding: 4px;
+  margin: 2px 1px 2px 3px;
+  height: 16px;
+  border-radius: 12px;
+  background-clip: padding-box;
+}
+.sceditor-button:hover,
+.sceditor-button.active,
+.sceditor-button.active:hover {
+  box-shadow: none;
+}
+.sceditor-button:hover {
+  background: #fff;
+  background: rgba(255, 255, 255, 0.75);
+  margin: 1px 0 1px 2px;
+  border: 1px solid #eee;
+}
+.sceditor-button.disabled:hover {
+  margin: 2px 1px 2px 3px;
+  border: 0;
+}
+.sceditor-button.active {
+  background: #b1b1b1;
+  background: rgba(0, 0, 0, 0.1);
+  margin: 1px 0 1px 2px;
+  border: 1px solid #999;
+}
+.sceditor-button.active:hover {
+  background: #fff;
+  background: rgba(255, 255, 255, 0.25);
+}
+.sceditor-button:active,
+.sceditor-button.active:active {
+  margin: 1px 0 1px 2px;
+  border: 1px solid #999;
+  box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.5);
+}
+.sceditor-button div,
+.sceditor-button svg {
+  margin: 0;
+}

+ 596 - 0
public/js/sc/themes/office-toolbar.css

@@ -0,0 +1,596 @@
+/**
+ * Copyright (C) 2012, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/)
+ * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/)
+ */
+/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */
+/**
+ * Default SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-16, Sam Clarke
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+div.sceditor-grip,
+.sceditor-button div {
+  background-image: url("famfamfam.png");
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+}
+.sceditor-button-youtube div {
+  background-position: 0px 0px;
+}
+.sceditor-button-link div {
+  background-position: 0px -16px;
+}
+.sceditor-button-unlink div {
+  background-position: 0px -32px;
+}
+.sceditor-button-underline div {
+  background-position: 0px -48px;
+}
+.sceditor-button-time div {
+  background-position: 0px -64px;
+}
+.sceditor-button-table div {
+  background-position: 0px -80px;
+}
+.sceditor-button-superscript div {
+  background-position: 0px -96px;
+}
+.sceditor-button-subscript div {
+  background-position: 0px -112px;
+}
+.sceditor-button-strike div {
+  background-position: 0px -128px;
+}
+.sceditor-button-source div {
+  background-position: 0px -144px;
+}
+.sceditor-button-size div {
+  background-position: 0px -160px;
+}
+.sceditor-button-rtl div {
+  background-position: 0px -176px;
+}
+.sceditor-button-right div {
+  background-position: 0px -192px;
+}
+.sceditor-button-removeformat div {
+  background-position: 0px -208px;
+}
+.sceditor-button-quote div {
+  background-position: 0px -224px;
+}
+.sceditor-button-print div {
+  background-position: 0px -240px;
+}
+.sceditor-button-pastetext div {
+  background-position: 0px -256px;
+}
+.sceditor-button-paste div {
+  background-position: 0px -272px;
+}
+.sceditor-button-outdent div {
+  background-position: 0px -288px;
+}
+.sceditor-button-orderedlist div {
+  background-position: 0px -304px;
+}
+.sceditor-button-maximize div {
+  background-position: 0px -320px;
+}
+.sceditor-button-ltr div {
+  background-position: 0px -336px;
+}
+.sceditor-button-left div {
+  background-position: 0px -352px;
+}
+.sceditor-button-justify div {
+  background-position: 0px -368px;
+}
+.sceditor-button-italic div {
+  background-position: 0px -384px;
+}
+.sceditor-button-indent div {
+  background-position: 0px -400px;
+}
+.sceditor-button-image div {
+  background-position: 0px -416px;
+}
+.sceditor-button-horizontalrule div {
+  background-position: 0px -432px;
+}
+.sceditor-button-format div {
+  background-position: 0px -448px;
+}
+.sceditor-button-font div {
+  background-position: 0px -464px;
+}
+.sceditor-button-emoticon div {
+  background-position: 0px -480px;
+}
+.sceditor-button-email div {
+  background-position: 0px -496px;
+}
+.sceditor-button-date div {
+  background-position: 0px -512px;
+}
+.sceditor-button-cut div {
+  background-position: 0px -528px;
+}
+.sceditor-button-copy div {
+  background-position: 0px -544px;
+}
+.sceditor-button-color div {
+  background-position: 0px -560px;
+}
+.sceditor-button-code div {
+  background-position: 0px -576px;
+}
+.sceditor-button-center div {
+  background-position: 0px -592px;
+}
+.sceditor-button-bulletlist div {
+  background-position: 0px -608px;
+}
+.sceditor-button-bold div {
+  background-position: 0px -624px;
+}
+div.sceditor-grip {
+  background-position: 0px -640px;
+  width: 10px;
+  height: 10px;
+}
+.rtl div.sceditor-grip {
+  background-position: 0px -650px;
+}
+/**
+ * SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+/*---------------------------------------------------
+    LESS Elements 0.7
+  ---------------------------------------------------
+    A set of useful LESS mixins
+    More info at: http://lesselements.com
+  ---------------------------------------------------*/
+.sceditor-container {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  position: relative;
+  background: #fff;
+  border: 1px solid #d9d9d9;
+  font-size: 13px;
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  color: #333;
+  line-height: 1;
+  font-weight: bold;
+  height: 250px;
+  border-radius: 4px;
+  background-clip: padding-box;
+}
+.sceditor-container *,
+.sceditor-container *:before,
+.sceditor-container *:after {
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.sceditor-container,
+.sceditor-container div,
+div.sceditor-dropdown,
+div.sceditor-dropdown div {
+  padding: 0;
+  margin: 0;
+  z-index: 3;
+}
+.sceditor-container iframe,
+.sceditor-container textarea {
+  display: block;
+  -ms-flex: 1 1 0%;
+  flex: 1 1 0%;
+  line-height: 1.25;
+  border: 0;
+  outline: none;
+  font-family: Verdana, Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  color: #111;
+  padding: 0;
+  margin: 5px;
+  resize: none;
+  background: #fff;
+  height: auto !important;
+  width: auto !important;
+  width: calc(100% - 10px) !important;
+  min-height: 1px;
+}
+.sceditor-container textarea {
+  margin: 7px 5px;
+}
+div.sceditor-dnd-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background: rgba(255, 255, 255, 0.2);
+  border: 5px dashed #aaa;
+  z-index: 200;
+  font-size: 2em;
+  text-align: center;
+  color: #aaa;
+}
+div.sceditor-dnd-cover p {
+  position: relative;
+  top: 45%;
+  pointer-events: none;
+}
+div.sceditor-resize-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  background: #000;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  opacity: 0.3;
+}
+div.sceditor-grip {
+  overflow: hidden;
+  width: 10px;
+  height: 10px;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  z-index: 3;
+  line-height: 0;
+}
+div.sceditor-grip.has-icon {
+  background-image: none;
+}
+.sceditor-maximize {
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100% !important;
+  width: 100% !important;
+  border-radius: 0;
+  background-clip: padding-box;
+  z-index: 2000;
+}
+html.sceditor-maximize,
+body.sceditor-maximize {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  margin: 0;
+  overflow: hidden;
+}
+.sceditor-maximize div.sceditor-grip {
+  display: none;
+}
+.sceditor-maximize div.sceditor-toolbar {
+  border-radius: 0;
+  background-clip: padding-box;
+}
+/**
+	 * Dropdown styleing
+	 */
+div.sceditor-dropdown {
+  position: absolute;
+  border: 1px solid #ccc;
+  background: #fff;
+  z-index: 4000;
+  padding: 10px;
+  font-weight: normal;
+  font-size: 15px;
+  border-radius: 2px;
+  background-clip: padding-box;
+  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2);
+}
+div.sceditor-dropdown *,
+div.sceditor-dropdown *:before,
+div.sceditor-dropdown *:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+div.sceditor-dropdown a,
+div.sceditor-dropdown a:link {
+  color: #333;
+}
+div.sceditor-dropdown form {
+  margin: 0;
+}
+div.sceditor-dropdown label {
+  display: block;
+  font-weight: bold;
+  color: #3c3c3c;
+  padding: 4px 0;
+}
+div.sceditor-dropdown input,
+div.sceditor-dropdown textarea {
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  outline: 0;
+  padding: 4px;
+  border: 1px solid #ccc;
+  border-top-color: #888;
+  margin: 0 0 0.75em;
+  border-radius: 1px;
+  background-clip: padding-box;
+}
+div.sceditor-dropdown textarea {
+  padding: 6px;
+}
+div.sceditor-dropdown input:focus,
+div.sceditor-dropdown textarea:focus {
+  border-color: #aaa;
+  border-top-color: #666;
+  box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1);
+}
+div.sceditor-dropdown .button {
+  font-weight: bold;
+  color: #444;
+  padding: 6px 12px;
+  background: #ececec;
+  border: solid 1px #ccc;
+  border-radius: 2px;
+  background-clip: padding-box;
+  cursor: pointer;
+  margin: 0.3em 0 0;
+}
+div.sceditor-dropdown .button:hover {
+  background: #f3f3f3;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+div.sceditor-font-picker,
+div.sceditor-fontsize-picker,
+div.sceditor-format {
+  padding: 6px 0;
+}
+div.sceditor-color-picker {
+  padding: 4px;
+}
+div.sceditor-emoticons,
+div.sceditor-more-emoticons {
+  padding: 0;
+}
+.sceditor-pastetext textarea {
+  border: 1px solid #bbb;
+  width: 20em;
+}
+.sceditor-emoticons img,
+.sceditor-more-emoticons img {
+  padding: 0;
+  cursor: pointer;
+  margin: 2px;
+}
+.sceditor-more {
+  border-top: 1px solid #bbb;
+  display: block;
+  text-align: center;
+  cursor: pointer;
+  font-weight: bold;
+  padding: 6px 0;
+}
+.sceditor-dropdown a:hover {
+  background: #eee;
+}
+.sceditor-fontsize-option,
+.sceditor-font-option,
+.sceditor-format a {
+  display: block;
+  padding: 7px 10px;
+  cursor: pointer;
+  text-decoration: none;
+  color: #222;
+}
+.sceditor-fontsize-option {
+  padding: 7px 13px;
+}
+.sceditor-color-column {
+  float: left;
+}
+.sceditor-color-option {
+  display: block;
+  border: 2px solid #fff;
+  height: 18px;
+  width: 18px;
+  overflow: hidden;
+}
+.sceditor-color-option:hover {
+  border: 1px solid #aaa;
+}
+/**
+	 * Toolbar styleing
+	 */
+div.sceditor-toolbar {
+  flex-shrink: 0;
+  overflow: hidden;
+  padding: 3px 5px 2px;
+  background: #f7f7f7;
+  border-bottom: 1px solid #c0c0c0;
+  line-height: 0;
+  text-align: left;
+  user-select: none;
+  border-radius: 3px 3px 0 0;
+  background-clip: padding-box;
+}
+div.sceditor-group {
+  display: inline-block;
+  background: #ddd;
+  margin: 1px 5px 1px 0;
+  padding: 1px;
+  border-bottom: 1px solid #aaa;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button {
+  float: left;
+  cursor: pointer;
+  padding: 3px 5px;
+  width: 16px;
+  height: 20px;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2);
+}
+.sceditor-button:active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3);
+}
+.sceditor-button.disabled:hover {
+  background: inherit;
+  cursor: default;
+  box-shadow: none;
+}
+.sceditor-button,
+.sceditor-button div {
+  display: block;
+}
+.sceditor-button svg {
+  display: inline-block;
+  height: 16px;
+  width: 16px;
+  margin: 2px 0;
+  fill: #111;
+  text-decoration: none;
+  pointer-events: none;
+  line-height: 1;
+}
+.sceditor-button.disabled svg {
+  fill: #888;
+}
+.sceditor-button div {
+  display: inline-block;
+  margin: 2px 0;
+  padding: 0;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0;
+  color: transparent;
+}
+.sceditor-button.has-icon div {
+  display: none;
+}
+.sceditor-button.disabled div {
+  opacity: 0.3;
+}
+.text .sceditor-button,
+.text .sceditor-button div,
+.sceditor-button.text,
+.sceditor-button.text div,
+.text-icon .sceditor-button,
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon,
+.sceditor-button.text-icon div {
+  display: inline-block;
+  width: auto;
+  line-height: 16px;
+  font-size: 1em;
+  color: inherit;
+  text-indent: 0;
+}
+.text-icon .sceditor-button.has-icon div,
+.sceditor-button.has-icon div,
+.text .sceditor-button div,
+.sceditor-button.text div {
+  padding: 0 2px;
+  background: none;
+}
+.text .sceditor-button svg,
+.sceditor-button.text svg {
+  display: none;
+}
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon div {
+  padding: 0 2px 0 20px;
+}
+.rtl div.sceditor-toolbar {
+  text-align: right;
+}
+.rtl .sceditor-button {
+  float: right;
+}
+.rtl div.sceditor-grip {
+  right: auto;
+  left: 0;
+}
+.sceditor-container {
+  border: 1px solid #8db2e3;
+}
+.sceditor-container textarea {
+  font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace;
+}
+div.sceditor-toolbar {
+  border-bottom: 1px solid #95a9c3;
+  background: #dee8f5;
+  background: linear-gradient(to bottom, #dee8f5 0%, #c7d8ed 29%, #ccdcee 61%, #c0d8ef 100%);
+}
+div.sceditor-group {
+  border: 1px solid #7596bf;
+  background: transparent;
+  padding: 0;
+  background: #cadcf0;
+  background: linear-gradient(to bottom, #cadcf0 24%, #bcd0e9 38%, #d0e1f7 99%);
+}
+.sceditor-button {
+  height: 16px;
+  padding: 3px 4px;
+  border-radius: 0;
+  background-clip: padding-box;
+  box-shadow: inset 0 1px #d5e3f1, inset 0 -1px #e3edfb, inset 1px 0 #cddcef, inset -1px 0 #b8ceea;
+}
+.sceditor-button:first-child {
+  border-radius: 4px 0 0 4px;
+  background-clip: padding-box;
+}
+.sceditor-button:last-child {
+  border-radius: 0 4px 4px 0;
+  background-clip: padding-box;
+}
+.sceditor-button div,
+.sceditor-button svg {
+  margin: 0;
+}
+.sceditor-button.active {
+  background: #fbdbb5;
+  background: linear-gradient(to bottom, #fbdbb5 11%, #feb456 29%, #fdeb9f 99%);
+  box-shadow: inset 0 1px #ebd1b4, inset 0 -1px #ffe47f, inset -1px 0 #b8ceea;
+}
+.sceditor-button:hover {
+  background: #fef7d5;
+  background: linear-gradient(to bottom, #fef7d5 0%, #fae5a9 42%, #ffd048 42%, #ffe59f 100%);
+  box-shadow: inset 0 1px #fffbe8, inset -1px 0 #ffefc4, inset 0 -1px #fff9cc;
+}
+.sceditor-button:active {
+  background: #e7a66d;
+  background: linear-gradient(to bottom, #e7a66d 0%, #fcb16d 1%, #ff8d05 42%, #ffc450 100%);
+  box-shadow: inset 0 1px 1px #7b6645, inset 0 -1px #d19c33;
+}
+.sceditor-button.active:hover {
+  background: #dba368;
+  background: linear-gradient(to bottom, #dba368 0%, #ffbd79 4%, #fea335 34%, #ffc64c 66%, #fee069 100%);
+  box-shadow: inset 0 1px 1px #9e8255, inset 0 -1px #fcce6b;
+}

+ 618 - 0
public/js/sc/themes/office.css

@@ -0,0 +1,618 @@
+/**
+ * Copyright (C) 2012, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/)
+ * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/)
+ */
+/**
+ * Copyright (C) 2012, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/)
+ * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/)
+ */
+/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */
+/**
+ * Default SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-16, Sam Clarke
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+div.sceditor-grip,
+.sceditor-button div {
+  background-image: url("famfamfam.png");
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+}
+.sceditor-button-youtube div {
+  background-position: 0px 0px;
+}
+.sceditor-button-link div {
+  background-position: 0px -16px;
+}
+.sceditor-button-unlink div {
+  background-position: 0px -32px;
+}
+.sceditor-button-underline div {
+  background-position: 0px -48px;
+}
+.sceditor-button-time div {
+  background-position: 0px -64px;
+}
+.sceditor-button-table div {
+  background-position: 0px -80px;
+}
+.sceditor-button-superscript div {
+  background-position: 0px -96px;
+}
+.sceditor-button-subscript div {
+  background-position: 0px -112px;
+}
+.sceditor-button-strike div {
+  background-position: 0px -128px;
+}
+.sceditor-button-source div {
+  background-position: 0px -144px;
+}
+.sceditor-button-size div {
+  background-position: 0px -160px;
+}
+.sceditor-button-rtl div {
+  background-position: 0px -176px;
+}
+.sceditor-button-right div {
+  background-position: 0px -192px;
+}
+.sceditor-button-removeformat div {
+  background-position: 0px -208px;
+}
+.sceditor-button-quote div {
+  background-position: 0px -224px;
+}
+.sceditor-button-print div {
+  background-position: 0px -240px;
+}
+.sceditor-button-pastetext div {
+  background-position: 0px -256px;
+}
+.sceditor-button-paste div {
+  background-position: 0px -272px;
+}
+.sceditor-button-outdent div {
+  background-position: 0px -288px;
+}
+.sceditor-button-orderedlist div {
+  background-position: 0px -304px;
+}
+.sceditor-button-maximize div {
+  background-position: 0px -320px;
+}
+.sceditor-button-ltr div {
+  background-position: 0px -336px;
+}
+.sceditor-button-left div {
+  background-position: 0px -352px;
+}
+.sceditor-button-justify div {
+  background-position: 0px -368px;
+}
+.sceditor-button-italic div {
+  background-position: 0px -384px;
+}
+.sceditor-button-indent div {
+  background-position: 0px -400px;
+}
+.sceditor-button-image div {
+  background-position: 0px -416px;
+}
+.sceditor-button-horizontalrule div {
+  background-position: 0px -432px;
+}
+.sceditor-button-format div {
+  background-position: 0px -448px;
+}
+.sceditor-button-font div {
+  background-position: 0px -464px;
+}
+.sceditor-button-emoticon div {
+  background-position: 0px -480px;
+}
+.sceditor-button-email div {
+  background-position: 0px -496px;
+}
+.sceditor-button-date div {
+  background-position: 0px -512px;
+}
+.sceditor-button-cut div {
+  background-position: 0px -528px;
+}
+.sceditor-button-copy div {
+  background-position: 0px -544px;
+}
+.sceditor-button-color div {
+  background-position: 0px -560px;
+}
+.sceditor-button-code div {
+  background-position: 0px -576px;
+}
+.sceditor-button-center div {
+  background-position: 0px -592px;
+}
+.sceditor-button-bulletlist div {
+  background-position: 0px -608px;
+}
+.sceditor-button-bold div {
+  background-position: 0px -624px;
+}
+div.sceditor-grip {
+  background-position: 0px -640px;
+  width: 10px;
+  height: 10px;
+}
+.rtl div.sceditor-grip {
+  background-position: 0px -650px;
+}
+/**
+ * SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+/*---------------------------------------------------
+    LESS Elements 0.7
+  ---------------------------------------------------
+    A set of useful LESS mixins
+    More info at: http://lesselements.com
+  ---------------------------------------------------*/
+.sceditor-container {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  position: relative;
+  background: #fff;
+  border: 1px solid #d9d9d9;
+  font-size: 13px;
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  color: #333;
+  line-height: 1;
+  font-weight: bold;
+  height: 250px;
+  border-radius: 4px;
+  background-clip: padding-box;
+}
+.sceditor-container *,
+.sceditor-container *:before,
+.sceditor-container *:after {
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.sceditor-container,
+.sceditor-container div,
+div.sceditor-dropdown,
+div.sceditor-dropdown div {
+  padding: 0;
+  margin: 0;
+  z-index: 3;
+}
+.sceditor-container iframe,
+.sceditor-container textarea {
+  display: block;
+  -ms-flex: 1 1 0%;
+  flex: 1 1 0%;
+  line-height: 1.25;
+  border: 0;
+  outline: none;
+  font-family: Verdana, Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  color: #111;
+  padding: 0;
+  margin: 5px;
+  resize: none;
+  background: #fff;
+  height: auto !important;
+  width: auto !important;
+  width: calc(100% - 10px) !important;
+  min-height: 1px;
+}
+.sceditor-container textarea {
+  margin: 7px 5px;
+}
+div.sceditor-dnd-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background: rgba(255, 255, 255, 0.2);
+  border: 5px dashed #aaa;
+  z-index: 200;
+  font-size: 2em;
+  text-align: center;
+  color: #aaa;
+}
+div.sceditor-dnd-cover p {
+  position: relative;
+  top: 45%;
+  pointer-events: none;
+}
+div.sceditor-resize-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  background: #000;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  opacity: 0.3;
+}
+div.sceditor-grip {
+  overflow: hidden;
+  width: 10px;
+  height: 10px;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  z-index: 3;
+  line-height: 0;
+}
+div.sceditor-grip.has-icon {
+  background-image: none;
+}
+.sceditor-maximize {
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100% !important;
+  width: 100% !important;
+  border-radius: 0;
+  background-clip: padding-box;
+  z-index: 2000;
+}
+html.sceditor-maximize,
+body.sceditor-maximize {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  margin: 0;
+  overflow: hidden;
+}
+.sceditor-maximize div.sceditor-grip {
+  display: none;
+}
+.sceditor-maximize div.sceditor-toolbar {
+  border-radius: 0;
+  background-clip: padding-box;
+}
+/**
+	 * Dropdown styleing
+	 */
+div.sceditor-dropdown {
+  position: absolute;
+  border: 1px solid #ccc;
+  background: #fff;
+  z-index: 4000;
+  padding: 10px;
+  font-weight: normal;
+  font-size: 15px;
+  border-radius: 2px;
+  background-clip: padding-box;
+  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2);
+}
+div.sceditor-dropdown *,
+div.sceditor-dropdown *:before,
+div.sceditor-dropdown *:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+div.sceditor-dropdown a,
+div.sceditor-dropdown a:link {
+  color: #333;
+}
+div.sceditor-dropdown form {
+  margin: 0;
+}
+div.sceditor-dropdown label {
+  display: block;
+  font-weight: bold;
+  color: #3c3c3c;
+  padding: 4px 0;
+}
+div.sceditor-dropdown input,
+div.sceditor-dropdown textarea {
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  outline: 0;
+  padding: 4px;
+  border: 1px solid #ccc;
+  border-top-color: #888;
+  margin: 0 0 0.75em;
+  border-radius: 1px;
+  background-clip: padding-box;
+}
+div.sceditor-dropdown textarea {
+  padding: 6px;
+}
+div.sceditor-dropdown input:focus,
+div.sceditor-dropdown textarea:focus {
+  border-color: #aaa;
+  border-top-color: #666;
+  box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1);
+}
+div.sceditor-dropdown .button {
+  font-weight: bold;
+  color: #444;
+  padding: 6px 12px;
+  background: #ececec;
+  border: solid 1px #ccc;
+  border-radius: 2px;
+  background-clip: padding-box;
+  cursor: pointer;
+  margin: 0.3em 0 0;
+}
+div.sceditor-dropdown .button:hover {
+  background: #f3f3f3;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+div.sceditor-font-picker,
+div.sceditor-fontsize-picker,
+div.sceditor-format {
+  padding: 6px 0;
+}
+div.sceditor-color-picker {
+  padding: 4px;
+}
+div.sceditor-emoticons,
+div.sceditor-more-emoticons {
+  padding: 0;
+}
+.sceditor-pastetext textarea {
+  border: 1px solid #bbb;
+  width: 20em;
+}
+.sceditor-emoticons img,
+.sceditor-more-emoticons img {
+  padding: 0;
+  cursor: pointer;
+  margin: 2px;
+}
+.sceditor-more {
+  border-top: 1px solid #bbb;
+  display: block;
+  text-align: center;
+  cursor: pointer;
+  font-weight: bold;
+  padding: 6px 0;
+}
+.sceditor-dropdown a:hover {
+  background: #eee;
+}
+.sceditor-fontsize-option,
+.sceditor-font-option,
+.sceditor-format a {
+  display: block;
+  padding: 7px 10px;
+  cursor: pointer;
+  text-decoration: none;
+  color: #222;
+}
+.sceditor-fontsize-option {
+  padding: 7px 13px;
+}
+.sceditor-color-column {
+  float: left;
+}
+.sceditor-color-option {
+  display: block;
+  border: 2px solid #fff;
+  height: 18px;
+  width: 18px;
+  overflow: hidden;
+}
+.sceditor-color-option:hover {
+  border: 1px solid #aaa;
+}
+/**
+	 * Toolbar styleing
+	 */
+div.sceditor-toolbar {
+  flex-shrink: 0;
+  overflow: hidden;
+  padding: 3px 5px 2px;
+  background: #f7f7f7;
+  border-bottom: 1px solid #c0c0c0;
+  line-height: 0;
+  text-align: left;
+  user-select: none;
+  border-radius: 3px 3px 0 0;
+  background-clip: padding-box;
+}
+div.sceditor-group {
+  display: inline-block;
+  background: #ddd;
+  margin: 1px 5px 1px 0;
+  padding: 1px;
+  border-bottom: 1px solid #aaa;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button {
+  float: left;
+  cursor: pointer;
+  padding: 3px 5px;
+  width: 16px;
+  height: 20px;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2);
+}
+.sceditor-button:active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3);
+}
+.sceditor-button.disabled:hover {
+  background: inherit;
+  cursor: default;
+  box-shadow: none;
+}
+.sceditor-button,
+.sceditor-button div {
+  display: block;
+}
+.sceditor-button svg {
+  display: inline-block;
+  height: 16px;
+  width: 16px;
+  margin: 2px 0;
+  fill: #111;
+  text-decoration: none;
+  pointer-events: none;
+  line-height: 1;
+}
+.sceditor-button.disabled svg {
+  fill: #888;
+}
+.sceditor-button div {
+  display: inline-block;
+  margin: 2px 0;
+  padding: 0;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0;
+  color: transparent;
+}
+.sceditor-button.has-icon div {
+  display: none;
+}
+.sceditor-button.disabled div {
+  opacity: 0.3;
+}
+.text .sceditor-button,
+.text .sceditor-button div,
+.sceditor-button.text,
+.sceditor-button.text div,
+.text-icon .sceditor-button,
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon,
+.sceditor-button.text-icon div {
+  display: inline-block;
+  width: auto;
+  line-height: 16px;
+  font-size: 1em;
+  color: inherit;
+  text-indent: 0;
+}
+.text-icon .sceditor-button.has-icon div,
+.sceditor-button.has-icon div,
+.text .sceditor-button div,
+.sceditor-button.text div {
+  padding: 0 2px;
+  background: none;
+}
+.text .sceditor-button svg,
+.sceditor-button.text svg {
+  display: none;
+}
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon div {
+  padding: 0 2px 0 20px;
+}
+.rtl div.sceditor-toolbar {
+  text-align: right;
+}
+.rtl .sceditor-button {
+  float: right;
+}
+.rtl div.sceditor-grip {
+  right: auto;
+  left: 0;
+}
+.sceditor-container {
+  border: 1px solid #8db2e3;
+}
+.sceditor-container textarea {
+  font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace;
+}
+div.sceditor-toolbar {
+  border-bottom: 1px solid #95a9c3;
+  background: #dee8f5;
+  background: linear-gradient(to bottom, #dee8f5 0%, #c7d8ed 29%, #ccdcee 61%, #c0d8ef 100%);
+}
+div.sceditor-group {
+  border: 1px solid #7596bf;
+  background: transparent;
+  padding: 0;
+  background: #cadcf0;
+  background: linear-gradient(to bottom, #cadcf0 24%, #bcd0e9 38%, #d0e1f7 99%);
+}
+.sceditor-button {
+  height: 16px;
+  padding: 3px 4px;
+  border-radius: 0;
+  background-clip: padding-box;
+  box-shadow: inset 0 1px #d5e3f1, inset 0 -1px #e3edfb, inset 1px 0 #cddcef, inset -1px 0 #b8ceea;
+}
+.sceditor-button:first-child {
+  border-radius: 4px 0 0 4px;
+  background-clip: padding-box;
+}
+.sceditor-button:last-child {
+  border-radius: 0 4px 4px 0;
+  background-clip: padding-box;
+}
+.sceditor-button div,
+.sceditor-button svg {
+  margin: 0;
+}
+.sceditor-button.active {
+  background: #fbdbb5;
+  background: linear-gradient(to bottom, #fbdbb5 11%, #feb456 29%, #fdeb9f 99%);
+  box-shadow: inset 0 1px #ebd1b4, inset 0 -1px #ffe47f, inset -1px 0 #b8ceea;
+}
+.sceditor-button:hover {
+  background: #fef7d5;
+  background: linear-gradient(to bottom, #fef7d5 0%, #fae5a9 42%, #ffd048 42%, #ffe59f 100%);
+  box-shadow: inset 0 1px #fffbe8, inset -1px 0 #ffefc4, inset 0 -1px #fff9cc;
+}
+.sceditor-button:active {
+  background: #e7a66d;
+  background: linear-gradient(to bottom, #e7a66d 0%, #fcb16d 1%, #ff8d05 42%, #ffc450 100%);
+  box-shadow: inset 0 1px 1px #7b6645, inset 0 -1px #d19c33;
+}
+.sceditor-button.active:hover {
+  background: #dba368;
+  background: linear-gradient(to bottom, #dba368 0%, #ffbd79 4%, #fea335 34%, #ffc64c 66%, #fee069 100%);
+  box-shadow: inset 0 1px 1px #9e8255, inset 0 -1px #fcce6b;
+}
+.sceditor-container {
+  background: #a3c2ea;
+  background: linear-gradient(to bottom, #a3c2ea 0%, #6d92c1 39%, #577fb3 64%, #6591cc 100%);
+}
+.sceditor-container iframe,
+.sceditor-container textarea {
+  border: 1px solid #646464;
+  background: #fff;
+  margin: 7px 40px;
+  padding: 20px;
+  width: calc(100% - 120px) !important;
+  box-shadow: 1px 1px 5px #293a52;
+}

+ 619 - 0
public/js/sc/themes/square.css

@@ -0,0 +1,619 @@
+/**
+ * Square theme
+ *
+ * This theme is best suited to short toolbars that
+ * don't span multiple lines.
+ *
+ * Copyright (C) 2012, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/)
+ * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/)
+ */
+/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */
+/**
+ * Default SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2011-16, Sam Clarke
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+div.sceditor-grip,
+.sceditor-button div {
+  background-image: url("famfamfam.png");
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+}
+.sceditor-button-youtube div {
+  background-position: 0px 0px;
+}
+.sceditor-button-link div {
+  background-position: 0px -16px;
+}
+.sceditor-button-unlink div {
+  background-position: 0px -32px;
+}
+.sceditor-button-underline div {
+  background-position: 0px -48px;
+}
+.sceditor-button-time div {
+  background-position: 0px -64px;
+}
+.sceditor-button-table div {
+  background-position: 0px -80px;
+}
+.sceditor-button-superscript div {
+  background-position: 0px -96px;
+}
+.sceditor-button-subscript div {
+  background-position: 0px -112px;
+}
+.sceditor-button-strike div {
+  background-position: 0px -128px;
+}
+.sceditor-button-source div {
+  background-position: 0px -144px;
+}
+.sceditor-button-size div {
+  background-position: 0px -160px;
+}
+.sceditor-button-rtl div {
+  background-position: 0px -176px;
+}
+.sceditor-button-right div {
+  background-position: 0px -192px;
+}
+.sceditor-button-removeformat div {
+  background-position: 0px -208px;
+}
+.sceditor-button-quote div {
+  background-position: 0px -224px;
+}
+.sceditor-button-print div {
+  background-position: 0px -240px;
+}
+.sceditor-button-pastetext div {
+  background-position: 0px -256px;
+}
+.sceditor-button-paste div {
+  background-position: 0px -272px;
+}
+.sceditor-button-outdent div {
+  background-position: 0px -288px;
+}
+.sceditor-button-orderedlist div {
+  background-position: 0px -304px;
+}
+.sceditor-button-maximize div {
+  background-position: 0px -320px;
+}
+.sceditor-button-ltr div {
+  background-position: 0px -336px;
+}
+.sceditor-button-left div {
+  background-position: 0px -352px;
+}
+.sceditor-button-justify div {
+  background-position: 0px -368px;
+}
+.sceditor-button-italic div {
+  background-position: 0px -384px;
+}
+.sceditor-button-indent div {
+  background-position: 0px -400px;
+}
+.sceditor-button-image div {
+  background-position: 0px -416px;
+}
+.sceditor-button-horizontalrule div {
+  background-position: 0px -432px;
+}
+.sceditor-button-format div {
+  background-position: 0px -448px;
+}
+.sceditor-button-font div {
+  background-position: 0px -464px;
+}
+.sceditor-button-emoticon div {
+  background-position: 0px -480px;
+}
+.sceditor-button-email div {
+  background-position: 0px -496px;
+}
+.sceditor-button-date div {
+  background-position: 0px -512px;
+}
+.sceditor-button-cut div {
+  background-position: 0px -528px;
+}
+.sceditor-button-copy div {
+  background-position: 0px -544px;
+}
+.sceditor-button-color div {
+  background-position: 0px -560px;
+}
+.sceditor-button-code div {
+  background-position: 0px -576px;
+}
+.sceditor-button-center div {
+  background-position: 0px -592px;
+}
+.sceditor-button-bulletlist div {
+  background-position: 0px -608px;
+}
+.sceditor-button-bold div {
+  background-position: 0px -624px;
+}
+div.sceditor-grip {
+  background-position: 0px -640px;
+  width: 10px;
+  height: 10px;
+}
+.rtl div.sceditor-grip {
+  background-position: 0px -650px;
+}
+/**
+ * SCEditor
+ * http://www.sceditor.com/
+ *
+ * Copyright (C) 2017, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ */
+/*---------------------------------------------------
+    LESS Elements 0.7
+  ---------------------------------------------------
+    A set of useful LESS mixins
+    More info at: http://lesselements.com
+  ---------------------------------------------------*/
+.sceditor-container {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  position: relative;
+  background: #fff;
+  border: 1px solid #d9d9d9;
+  font-size: 13px;
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  color: #333;
+  line-height: 1;
+  font-weight: bold;
+  height: 250px;
+  border-radius: 4px;
+  background-clip: padding-box;
+}
+.sceditor-container *,
+.sceditor-container *:before,
+.sceditor-container *:after {
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.sceditor-container,
+.sceditor-container div,
+div.sceditor-dropdown,
+div.sceditor-dropdown div {
+  padding: 0;
+  margin: 0;
+  z-index: 3;
+}
+.sceditor-container iframe,
+.sceditor-container textarea {
+  display: block;
+  -ms-flex: 1 1 0%;
+  flex: 1 1 0%;
+  line-height: 1.25;
+  border: 0;
+  outline: none;
+  font-family: Verdana, Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  color: #111;
+  padding: 0;
+  margin: 5px;
+  resize: none;
+  background: #fff;
+  height: auto !important;
+  width: auto !important;
+  width: calc(100% - 10px) !important;
+  min-height: 1px;
+}
+.sceditor-container textarea {
+  margin: 7px 5px;
+}
+div.sceditor-dnd-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background: rgba(255, 255, 255, 0.2);
+  border: 5px dashed #aaa;
+  z-index: 200;
+  font-size: 2em;
+  text-align: center;
+  color: #aaa;
+}
+div.sceditor-dnd-cover p {
+  position: relative;
+  top: 45%;
+  pointer-events: none;
+}
+div.sceditor-resize-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  background: #000;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  opacity: 0.3;
+}
+div.sceditor-grip {
+  overflow: hidden;
+  width: 10px;
+  height: 10px;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  z-index: 3;
+  line-height: 0;
+}
+div.sceditor-grip.has-icon {
+  background-image: none;
+}
+.sceditor-maximize {
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100% !important;
+  width: 100% !important;
+  border-radius: 0;
+  background-clip: padding-box;
+  z-index: 2000;
+}
+html.sceditor-maximize,
+body.sceditor-maximize {
+  height: 100%;
+  width: 100%;
+  padding: 0;
+  margin: 0;
+  overflow: hidden;
+}
+.sceditor-maximize div.sceditor-grip {
+  display: none;
+}
+.sceditor-maximize div.sceditor-toolbar {
+  border-radius: 0;
+  background-clip: padding-box;
+}
+/**
+	 * Dropdown styleing
+	 */
+div.sceditor-dropdown {
+  position: absolute;
+  border: 1px solid #ccc;
+  background: #fff;
+  z-index: 4000;
+  padding: 10px;
+  font-weight: normal;
+  font-size: 15px;
+  border-radius: 2px;
+  background-clip: padding-box;
+  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2);
+}
+div.sceditor-dropdown *,
+div.sceditor-dropdown *:before,
+div.sceditor-dropdown *:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+div.sceditor-dropdown a,
+div.sceditor-dropdown a:link {
+  color: #333;
+}
+div.sceditor-dropdown form {
+  margin: 0;
+}
+div.sceditor-dropdown label {
+  display: block;
+  font-weight: bold;
+  color: #3c3c3c;
+  padding: 4px 0;
+}
+div.sceditor-dropdown input,
+div.sceditor-dropdown textarea {
+  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+  outline: 0;
+  padding: 4px;
+  border: 1px solid #ccc;
+  border-top-color: #888;
+  margin: 0 0 0.75em;
+  border-radius: 1px;
+  background-clip: padding-box;
+}
+div.sceditor-dropdown textarea {
+  padding: 6px;
+}
+div.sceditor-dropdown input:focus,
+div.sceditor-dropdown textarea:focus {
+  border-color: #aaa;
+  border-top-color: #666;
+  box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1);
+}
+div.sceditor-dropdown .button {
+  font-weight: bold;
+  color: #444;
+  padding: 6px 12px;
+  background: #ececec;
+  border: solid 1px #ccc;
+  border-radius: 2px;
+  background-clip: padding-box;
+  cursor: pointer;
+  margin: 0.3em 0 0;
+}
+div.sceditor-dropdown .button:hover {
+  background: #f3f3f3;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+div.sceditor-font-picker,
+div.sceditor-fontsize-picker,
+div.sceditor-format {
+  padding: 6px 0;
+}
+div.sceditor-color-picker {
+  padding: 4px;
+}
+div.sceditor-emoticons,
+div.sceditor-more-emoticons {
+  padding: 0;
+}
+.sceditor-pastetext textarea {
+  border: 1px solid #bbb;
+  width: 20em;
+}
+.sceditor-emoticons img,
+.sceditor-more-emoticons img {
+  padding: 0;
+  cursor: pointer;
+  margin: 2px;
+}
+.sceditor-more {
+  border-top: 1px solid #bbb;
+  display: block;
+  text-align: center;
+  cursor: pointer;
+  font-weight: bold;
+  padding: 6px 0;
+}
+.sceditor-dropdown a:hover {
+  background: #eee;
+}
+.sceditor-fontsize-option,
+.sceditor-font-option,
+.sceditor-format a {
+  display: block;
+  padding: 7px 10px;
+  cursor: pointer;
+  text-decoration: none;
+  color: #222;
+}
+.sceditor-fontsize-option {
+  padding: 7px 13px;
+}
+.sceditor-color-column {
+  float: left;
+}
+.sceditor-color-option {
+  display: block;
+  border: 2px solid #fff;
+  height: 18px;
+  width: 18px;
+  overflow: hidden;
+}
+.sceditor-color-option:hover {
+  border: 1px solid #aaa;
+}
+/**
+	 * Toolbar styleing
+	 */
+div.sceditor-toolbar {
+  flex-shrink: 0;
+  overflow: hidden;
+  padding: 3px 5px 2px;
+  background: #f7f7f7;
+  border-bottom: 1px solid #c0c0c0;
+  line-height: 0;
+  text-align: left;
+  user-select: none;
+  border-radius: 3px 3px 0 0;
+  background-clip: padding-box;
+}
+div.sceditor-group {
+  display: inline-block;
+  background: #ddd;
+  margin: 1px 5px 1px 0;
+  padding: 1px;
+  border-bottom: 1px solid #aaa;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button {
+  float: left;
+  cursor: pointer;
+  padding: 3px 5px;
+  width: 16px;
+  height: 20px;
+  border-radius: 3px;
+  background-clip: padding-box;
+}
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2);
+}
+.sceditor-button:active {
+  background: #fff;
+  box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3);
+}
+.sceditor-button.disabled:hover {
+  background: inherit;
+  cursor: default;
+  box-shadow: none;
+}
+.sceditor-button,
+.sceditor-button div {
+  display: block;
+}
+.sceditor-button svg {
+  display: inline-block;
+  height: 16px;
+  width: 16px;
+  margin: 2px 0;
+  fill: #111;
+  text-decoration: none;
+  pointer-events: none;
+  line-height: 1;
+}
+.sceditor-button.disabled svg {
+  fill: #888;
+}
+.sceditor-button div {
+  display: inline-block;
+  margin: 2px 0;
+  padding: 0;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0;
+  color: transparent;
+}
+.sceditor-button.has-icon div {
+  display: none;
+}
+.sceditor-button.disabled div {
+  opacity: 0.3;
+}
+.text .sceditor-button,
+.text .sceditor-button div,
+.sceditor-button.text,
+.sceditor-button.text div,
+.text-icon .sceditor-button,
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon,
+.sceditor-button.text-icon div {
+  display: inline-block;
+  width: auto;
+  line-height: 16px;
+  font-size: 1em;
+  color: inherit;
+  text-indent: 0;
+}
+.text-icon .sceditor-button.has-icon div,
+.sceditor-button.has-icon div,
+.text .sceditor-button div,
+.sceditor-button.text div {
+  padding: 0 2px;
+  background: none;
+}
+.text .sceditor-button svg,
+.sceditor-button.text svg {
+  display: none;
+}
+.text-icon .sceditor-button div,
+.sceditor-button.text-icon div {
+  padding: 0 2px 0 20px;
+}
+.rtl div.sceditor-toolbar {
+  text-align: right;
+}
+.rtl .sceditor-button {
+  float: right;
+}
+.rtl div.sceditor-grip {
+  right: auto;
+  left: 0;
+}
+.sceditor-container {
+  border: 1px solid #d6d6d6;
+  border-radius: 0;
+  background-clip: padding-box;
+}
+.sceditor-container textarea {
+  font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace;
+  background: #2e3436;
+  color: #fff;
+  margin: 0;
+  padding: 5px;
+}
+div.sceditor-toolbar,
+div.sceditor-group {
+  background: #f2f2f2;
+  background: linear-gradient(to bottom, #f2f2f2 0%, #dddddd 89%);
+}
+div.sceditor-toolbar {
+  padding: 0;
+  border-bottom: 1px solid #bbb;
+  background-size: 100% 32px;
+}
+div.sceditor-group {
+  margin: 0;
+  padding: 2px 4px;
+  border: 0;
+  border-right: 1px solid #ccc;
+  border-left: 1px solid #eaeaea;
+  border-radius: 0;
+  background-clip: padding-box;
+}
+div.sceditor-group:last-child {
+  border-right: 0;
+}
+div.sceditor-group:first-child {
+  border-left: 0;
+}
+.sceditor-button {
+  height: 16px;
+  padding: 5px;
+  margin: 1px;
+  border-radius: 0;
+  background-clip: padding-box;
+}
+.sceditor-button div,
+.sceditor-button svg {
+  margin: 0;
+}
+.sceditor-button.active,
+.sceditor-button:hover,
+.sceditor-button:active,
+.sceditor-button.active:hover {
+  margin: 0;
+  box-shadow: none;
+}
+.sceditor-button.active {
+  background: #f4f4f4;
+  border: 1px solid #ccc;
+}
+.sceditor-button:hover {
+  background: #fefefe;
+  border: 1px solid #ddd;
+}
+.sceditor-button.disabled:hover {
+  margin: 1px;
+  border: 0;
+}
+.sceditor-button:active {
+  background: #eee;
+  border: 1px solid #ccc;
+}
+.sceditor-button.active:hover {
+  background: #f8f8f8;
+  border: 1px solid #ddd;
+}

Some files were not shown because too many files changed in this diff