rabin 7 år sedan
förälder
incheckning
6f625f62a5
30 ändrade filer med 8700 tillägg och 22 borttagningar
  1. 2 1
      admin/page/upload.py
  2. 62 0
      admin/static/js/main.js
  3. BIN
      admin/static/lib/simditor/images/image.png
  4. 138 0
      admin/static/lib/simditor/plugins/autosave/simditor-autosave.js
  5. 170 0
      admin/static/lib/simditor/plugins/checklist/simditor-checklist.js
  6. 25 0
      admin/static/lib/simditor/plugins/checklist/styles/simditor-checklist.css
  7. 27 0
      admin/static/lib/simditor/plugins/checklist/styles/simditor-checklist.scss
  8. 121 0
      admin/static/lib/simditor/plugins/dropzone/simditor-dropzone.js
  9. 1006 0
      admin/static/lib/simditor/plugins/html/beautify-html.js
  10. 7 0
      admin/static/lib/simditor/plugins/html/simditor-html.css
  11. 104 0
      admin/static/lib/simditor/plugins/html/simditor-html.js
  12. 241 0
      admin/static/lib/simditor/scripts/hotkeys.js
  13. 6 0
      admin/static/lib/simditor/scripts/hotkeys.min.js
  14. 1 0
      admin/static/lib/simditor/scripts/jquery.min.js
  15. 172 0
      admin/static/lib/simditor/scripts/module.js
  16. 2 0
      admin/static/lib/simditor/scripts/module.min.js
  17. 5585 0
      admin/static/lib/simditor/scripts/simditor.js
  18. 7 0
      admin/static/lib/simditor/scripts/simditor.min.js
  19. 261 0
      admin/static/lib/simditor/scripts/uploader.js
  20. 6 0
      admin/static/lib/simditor/scripts/uploader.min.js
  21. 696 0
      admin/static/lib/simditor/styles/editor.scss
  22. 0 0
      admin/static/lib/simditor/styles/fonticon.scss
  23. 7 0
      admin/static/lib/simditor/styles/simditor.css
  24. 4 0
      admin/static/lib/simditor/styles/simditor.scss
  25. 1 1
      admin/templates/common/list.html
  26. 1 1
      admin/templates/common/set.html
  27. 1 1
      admin/templates/common/update.html
  28. 1 0
      admin/templates/inc/script.html
  29. 23 10
      admin/templates/theme/set.html
  30. 23 8
      admin/templates/theme/update.html

+ 2 - 1
admin/page/upload.py

@@ -34,7 +34,8 @@ class upload_path(Load):
 		if crop:
 			#裁图类型,暂时居中裁图
 			file_path = cropImage(file_path)
-		self.out('yes', {'src':url + file_path.replace(Demeter.path + 'runtime', '')})
+		#self.out('yes', {'src':url + file_path.replace(Demeter.path + 'runtime', '')})
+		self.write({'state':True,'file_path':url + file_path.replace(Demeter.path + 'runtime', '')})
 
 def cropImage(file):
 	img = Image.open(file)

+ 62 - 0
admin/static/js/main.js

@@ -1,6 +1,7 @@
 if (typeof(cur) == "undefined" && parent.config.cur) {
 	cur = parent.config.cur
 }
+var editors = [];
 
 function msg(value) {
 	if (!value.data) {
@@ -165,4 +166,65 @@ function request(type, url, data, callback) {
 //跳转页面
 function showPage(url) {
 	location.href = url
+}
+
+function initEditor()
+{
+	if ($('.layui-editor').length) {
+		$ ('.layui-editor').each(function() {
+			var parent = $(this).parent().parent();
+			loadEditor($(this));
+		})
+	}
+}
+function loadEditor(e)
+{	
+	if (!e.length) {
+		return;
+	}
+	var id = e.attr('id');
+	var key = e.attr('key');
+	var toolbar = [
+	  'title'
+	  ,'bold'
+	  ,'italic'
+	  ,'underline'
+	  ,'strikethrough'
+	  ,'fontScale'
+	  ,'color'
+	  ,'ol'
+	  ,'ul'
+	  ,'blockquote'
+	  ,'code'
+	  ,'table'
+	  ,'link'
+	  ,'image'
+	  ,'hr'
+	  ,'indent'
+	  ,'outdent'
+	  ,'alignment'
+	  , '|'
+	  ,'html'
+	];
+
+	if (typeof(editors[id]) == "object") {
+		editors[id].destroy();
+		editors[id] = undefined;
+	}
+
+	if (typeof(editors[id]) == "undefined") {
+		var xsrf = getCookie("_xsrf");
+        var uploadUrl = '/upload?_xsrf='+xsrf;
+		editors[id] = new Simditor( {
+			textarea: e,
+			upload: {
+				url: uploadUrl,
+				params: {key:key},
+				fileKey: 'file',
+				connectionCount: 10,
+				leaveConfirm: 'Uploading is in progress, are you sure to leave this page?'
+			},
+			toolbar : toolbar
+        });
+	}
 }

BIN
admin/static/lib/simditor/images/image.png


+ 138 - 0
admin/static/lib/simditor/plugins/autosave/simditor-autosave.js

@@ -0,0 +1,138 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define('simditor-autosave', ["jquery","simple-module","simditor"], function (a0,b1,c2) {
+      return (root['SimditorAutosave'] = factory(a0,b1,c2));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"),require("simple-module"),require("simditor"));
+  } else {
+    root['SimditorAutosave'] = factory(jQuery,SimpleModule,Simditor);
+  }
+}(this, function ($, SimpleModule, Simditor) {
+
+var SimditorAutosave,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+SimditorAutosave = (function(superClass) {
+  extend(SimditorAutosave, superClass);
+
+  function SimditorAutosave() {
+    return SimditorAutosave.__super__.constructor.apply(this, arguments);
+  }
+
+  SimditorAutosave.pluginName = 'Autosave';
+
+  SimditorAutosave.prototype.opts = {
+    autosave: true,
+    autosavePath: null
+  };
+
+  SimditorAutosave.prototype._init = function() {
+    var currentVal, link, name, val;
+    this.editor = this._module;
+    if (!this.opts.autosave) {
+      return;
+    }
+    this.name = typeof this.opts.autosave === 'string' ? this.opts.autosave : 'simditor';
+    if (this.opts.autosavePath) {
+      this.path = this.opts.autosavePath;
+    } else {
+      link = $("<a/>", {
+        href: location.href
+      });
+      name = this.editor.textarea.data('autosave') || this.name;
+      this.path = "/" + (link[0].pathname.replace(/\/$/g, "").replace(/^\//g, "")) + "/autosave/" + name + "/";
+    }
+    if (!this.path) {
+      return;
+    }
+    this.editor.on("valuechanged", (function(_this) {
+      return function() {
+        return _this.storage.set(_this.path, _this.editor.getValue());
+      };
+    })(this));
+    this.editor.el.closest('form').on('ajax:success.simditor-' + this.editor.id, (function(_this) {
+      return function(e) {
+        return _this.storage.remove(_this.path);
+      };
+    })(this));
+    val = this.storage.get(this.path);
+    if (!val) {
+      return;
+    }
+    currentVal = this.editor.textarea.val();
+    if (val === currentVal) {
+      return;
+    }
+    if (this.editor.textarea.is('[data-autosave-confirm]')) {
+      if (confirm(this.editor.textarea.data('autosave-confirm') || 'Are you sure to restore unsaved changes?')) {
+        return this.editor.setValue(val);
+      } else {
+        return this.storage.remove(this.path);
+      }
+    } else {
+      return this.editor.setValue(val);
+    }
+  };
+
+  SimditorAutosave.prototype.storage = {
+    supported: function() {
+      var error;
+      try {
+        localStorage.setItem('_storageSupported', 'yes');
+        localStorage.removeItem('_storageSupported');
+        return true;
+      } catch (_error) {
+        error = _error;
+        return false;
+      }
+    },
+    set: function(key, val, session) {
+      var storage;
+      if (session == null) {
+        session = false;
+      }
+      if (!this.supported()) {
+        return;
+      }
+      storage = session ? sessionStorage : localStorage;
+      return storage.setItem(key, val);
+    },
+    get: function(key, session) {
+      var storage;
+      if (session == null) {
+        session = false;
+      }
+      if (!this.supported()) {
+        return;
+      }
+      storage = session ? sessionStorage : localStorage;
+      return storage[key];
+    },
+    remove: function(key, session) {
+      var storage;
+      if (session == null) {
+        session = false;
+      }
+      if (!this.supported()) {
+        return;
+      }
+      storage = session ? sessionStorage : localStorage;
+      return storage.removeItem(key);
+    }
+  };
+
+  return SimditorAutosave;
+
+})(SimpleModule);
+
+Simditor.connect(SimditorAutosave);
+
+return SimditorAutosave;
+
+}));

+ 170 - 0
admin/static/lib/simditor/plugins/checklist/simditor-checklist.js

@@ -0,0 +1,170 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define('simditor-checklist', ["jquery","simditor"], function (a0,b1) {
+      return (root['ChecklistButton'] = factory(a0,b1));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"),require("simditor"));
+  } else {
+    root['ChecklistButton'] = factory(jQuery,Simditor);
+  }
+}(this, function ($, Simditor) {
+
+var ChecklistButton,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty,
+  slice = [].slice;
+
+ChecklistButton = (function(superClass) {
+  extend(ChecklistButton, superClass);
+
+  ChecklistButton.prototype.type = 'ul.simditor-checklist';
+
+  ChecklistButton.prototype.name = 'checklist';
+
+  ChecklistButton.prototype.icon = 'checklist';
+
+  ChecklistButton.prototype.htmlTag = 'li';
+
+  ChecklistButton.prototype.disableTag = 'pre, table';
+
+  function ChecklistButton() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    ChecklistButton.__super__.constructor.apply(this, args);
+    if ('input' && $.inArray('input', this.editor.formatter._allowedTags) < 0) {
+      this.editor.formatter._allowedTags.push('input');
+    }
+    $.extend(this.editor.formatter._allowedAttributes, {
+      input: ['type', 'checked']
+    });
+  }
+
+  ChecklistButton.prototype._init = function() {
+    ChecklistButton.__super__._init.call(this);
+    this.editor.on('decorate', (function(_this) {
+      return function(e, $el) {
+        return $el.find('ul > li input[type=checkbox]').each(function(i, checkbox) {
+          return _this._decorate($(checkbox));
+        });
+      };
+    })(this));
+    this.editor.on('undecorate', (function(_this) {
+      return function(e, $el) {
+        return $el.find('.simditor-checklist > li').each(function(i, node) {
+          return _this._undecorate($(node));
+        });
+      };
+    })(this));
+    this.editor.body.on('click', '.simditor-checklist > li', (function(_this) {
+      return function(e) {
+        var $node, range;
+        e.preventDefault();
+        e.stopPropagation();
+        $node = $(e.currentTarget);
+        range = document.createRange();
+        _this.editor.selection.save();
+        range.setStart($node[0], 0);
+        range.setEnd($node[0], _this.editor.util.getNodeLength($node[0]));
+        _this.editor.selection.range(range);
+        document.execCommand('strikethrough');
+        $node.attr('checked', !$node.attr('checked'));
+        _this.editor.selection.restore();
+        return _this.editor.trigger('valuechanged');
+      };
+    })(this));
+    return this.editor.keystroke.add('13', 'li', (function(_this) {
+      return function(e, $node) {
+        return setTimeout(function() {
+          var $li;
+          $li = _this.editor.selection.blockNodes().last().next();
+          if ($li.length) {
+            $li[0].removeAttribute('checked');
+            if (document.queryCommandState('strikethrough')) {
+              return document.execCommand('strikethrough');
+            }
+          }
+        }, 0);
+      };
+    })(this));
+  };
+
+  ChecklistButton.prototype._status = function() {
+    var $node;
+    ChecklistButton.__super__._status.call(this);
+    $node = this.editor.selection.rootNodes();
+    if ($node.is('.simditor-checklist')) {
+      this.editor.toolbar.findButton('ul').setActive(false);
+      this.editor.toolbar.findButton('ol').setActive(false);
+      this.editor.toolbar.findButton('ul').setDisabled(true);
+      return this.editor.toolbar.findButton('ol').setDisabled(true);
+    } else {
+      return this.editor.toolbar.findButton('checklist').setActive(false);
+    }
+  };
+
+  ChecklistButton.prototype.command = function(param) {
+    var $list, $rootNodes;
+    $rootNodes = this.editor.selection.blockNodes();
+    this.editor.selection.save();
+    $list = null;
+    $rootNodes.each((function(_this) {
+      return function(i, node) {
+        var $node;
+        $node = $(node);
+        if ($node.is('blockquote, li') || $node.is(_this.disableTag) || !$.contains(document, node)) {
+          return;
+        }
+        if ($node.is('.simditor-checklist')) {
+          $node.children('li').each(function(i, li) {
+            var $childList, $li;
+            $li = $(li);
+            $childList = $li.children('ul, ol').insertAfter($node);
+            return $('<p/>').append($(li).html() || _this.editor.util.phBr).insertBefore($node);
+          });
+          return $node.remove();
+        } else if ($node.is('ul, ol')) {
+          return $('<ul class="simditor-checklist" />').append($node.contents()).replaceAll($node);
+        } else if ($list && $node.prev().is($list)) {
+          $('<li/>').append($node.html() || _this.editor.util.phBr).appendTo($list);
+          return $node.remove();
+        } else {
+          $list = $('<ul class="simditor-checklist"><li></li></ul>');
+          $list.find('li').append($node.html() || _this.editor.util.phBr);
+          return $list.replaceAll($node);
+        }
+      };
+    })(this));
+    this.editor.selection.restore();
+    return this.editor.trigger('valuechanged');
+  };
+
+  ChecklistButton.prototype._decorate = function($checkbox) {
+    var $node, checked;
+    checked = !!$checkbox.attr('checked');
+    $node = $checkbox.closest('li');
+    $checkbox.remove();
+    $node.attr('checked', checked);
+    return $node.closest('ul').addClass('simditor-checklist');
+  };
+
+  ChecklistButton.prototype._undecorate = function($node) {
+    var $checkbox, checked;
+    checked = !!$node.attr('checked');
+    $checkbox = $('<input type="checkbox">').attr('checked', checked);
+    return $node.attr('checked', '').prepend($checkbox);
+  };
+
+  return ChecklistButton;
+
+})(Simditor.Button);
+
+Simditor.Toolbar.addButton(ChecklistButton);
+
+return ChecklistButton;
+
+}));

+ 25 - 0
admin/static/lib/simditor/plugins/checklist/styles/simditor-checklist.css

@@ -0,0 +1,25 @@
+.simditor .simditor-body .simditor-checklist {
+  margin: 15px 0;
+  padding: 0 0 0 40px;
+  line-height: 1.6;
+  list-style-type: none;
+}
+
+.simditor .simditor-body .simditor-checklist > li {
+  margin: 0;
+  pointer-events: none;
+}
+.simditor .simditor-body .simditor-checklist > li::before {
+  content: '';
+  pointer-events: all;
+  display: inline-block;
+  margin: 0 5px 0 -25px;
+  width: 20px;
+  height: 20px;
+  cursor: default;
+  vertical-align: middle;
+  background-image: url("data:image/gif;base64,R0lGODlhFAAUAPUAAP///9nZ2XV1dWNjY3Z2dv7+/mpqasbGxsPDw21tbfv7+2RkZM/Pz/X19crKymdnZ+/v725ubsvLy/Hx8XBwcO7u7nd3d83Nzezs7Hp6eoCAgNfX14SEhIqKitLS0uDg4I2NjZSUlNTU1Ojo6NXV1ZaWlp6entjY2J+fn6WlpeXl5ebm5qmpqY6OjvPz85CQkP39/Xt7e/Ly8t3d3dzc3P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAAA1ACwAAAAAFAAUAAAG/8BarVar1Wq1Wq1Wq9VqtVqtVqvVarVarVar1Wq1Wq1Wq9VqtVqtVqvVarVarVar1Wq1Wq1Wq9WAtVpAMBgMBoPBYEAI1Gq1Wq1WKxgOAAAAAAAAAIhEoVar1Wo1xYLRaDQajUaj4XgoarVarVaDACOSyWQymUwmEwkFUqvVarVaxXLBYDAYDAaDuWQqtVqtVqtVNIzNZrPZbDYbBqdSq9VqtVql4wF+Pp/P5/P5eECVWq1Wq9UqIdFoNBqNRqMRqVSp1Wq1Wq1i2kwmk8lkMpmcUJVarVar1SopFQAABAAAAABgxarUarVaraZoOVwul8vlcrkuL0WtVqvVajBHTGYgEAgEAoEg5oDVarVarVaDyWY0Gg1Io9FmMlitVqvVarVarVYoFAqFQqFQq9VqtVqtVqvVarVarVar1Wq1Wq1Wq9VqtVqtVqvVarUasFar1Wq1Wq1Wq9VqtVqtVqvVarVarVar1YIAOw==");
+}
+.simditor .simditor-body .simditor-checklist > li[checked]::before {
+  background-image: url("data:image/gif;base64,R0lGODlhFAAUAPYAAAAAAHeHkD9QYB8wSOjo6Haw6C1huLbn8MfY8Ja42I630Ia48NjY2IefoJbH6Ja44H648D9CkCVZsE9XWFWY2D5wqI+foH6gwE9fYHeIoF93iPj4+J7P+J7A4IegyE54sPDw8I+nuB5BmAAHCEdXWC9BiFaAuFdveF2g4F9veB5JoA8PD12Y4GWg4CZRqKbI6GWo4BcoOK7Q8Cc4SDVwwAcPEEdQYA8YIDc/QBYykC1pwD9IWGaY0C8/SE2Q0B84UF6QyFWY4E2Q2D5omAcIGA8gMEZoiEZvkC5ZsE9ZmP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAABKACwAAAAAFAAUAAAH/4BKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKEys4SkpKSkpKSkpKSkpKSkpKShYAIyBKSkpKSkpKIUkRERERERERNwA2SkpKSkpKShslHggICAgICAEANyUbSkpKSkpKGzkoMjIyMjIdKwAVORtKSkpKSkogIhSAOz0ZMjI2ADZBIiBKSkpKSkogKkEaACsJFwArHUEqIEpKSkpKSiAuQSgxAD8xAEMoQS4gSkpKSkpKIEgoMDw1AAADMDAoSCBKSkpKSkogBigFBUcANTwFBS0GIEpKSkpKSiA0MAsLCzNHCwsLLTQgSkpKSkpKICYLHBwcDhwcgJYcHAsmIEpKSkpKShspCgcHBwcHBwcHCikbSkpKSkpKGxYYGBgYGBgYGBgYFhtKSkpKSkpKGyAMDAwMDAwMDCAbSkpKSkpKSkpKShsbGxsbGxsbSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkqASkpKSkpKSkpKSkpKSkpKSkpKSkpKSoEAOw==");
+}

+ 27 - 0
admin/static/lib/simditor/plugins/checklist/styles/simditor-checklist.scss

@@ -0,0 +1,27 @@
+.simditor .simditor-body .simditor-checklist {
+  margin: 15px 0;
+  padding: 0 0 0 40px;
+  line-height: 1.6;
+  list-style-type: none;
+}
+
+.simditor .simditor-body .simditor-checklist > li {
+  margin: 0;
+  pointer-events: none;
+
+  &::before {
+    content: '';
+    pointer-events: all;
+    display: inline-block;
+    margin: 0 5px 0 -25px;
+    width: 20px;
+    height: 20px;
+    cursor: default;
+    vertical-align: middle;
+    background-image: url('data:image/gif;base64,R0lGODlhFAAUAPUAAP///9nZ2XV1dWNjY3Z2dv7+/mpqasbGxsPDw21tbfv7+2RkZM/Pz/X19crKymdnZ+/v725ubsvLy/Hx8XBwcO7u7nd3d83Nzezs7Hp6eoCAgNfX14SEhIqKitLS0uDg4I2NjZSUlNTU1Ojo6NXV1ZaWlp6entjY2J+fn6WlpeXl5ebm5qmpqY6OjvPz85CQkP39/Xt7e/Ly8t3d3dzc3P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAAA1ACwAAAAAFAAUAAAG/8BarVar1Wq1Wq1Wq9VqtVqtVqvVarVarVar1Wq1Wq1Wq9VqtVqtVqvVarVarVar1Wq1Wq1Wq9WAtVpAMBgMBoPBYEAI1Gq1Wq1WKxgOAAAAAAAAAIhEoVar1Wo1xYLRaDQajUaj4XgoarVarVaDACOSyWQymUwmEwkFUqvVarVaxXLBYDAYDAaDuWQqtVqtVqtVNIzNZrPZbDYbBqdSq9VqtVql4wF+Pp/P5/P5eECVWq1Wq9UqIdFoNBqNRqMRqVSp1Wq1Wq1i2kwmk8lkMpmcUJVarVar1SopFQAABAAAAABgxarUarVaraZoOVwul8vlcrkuL0WtVqvVajBHTGYgEAgEAoEg5oDVarVarVaDyWY0Gg1Io9FmMlitVqvVarVarVYoFAqFQqFQq9VqtVqtVqvVarVarVar1Wq1Wq1Wq9VqtVqtVqvVarUasFar1Wq1Wq1Wq9VqtVqtVqvVarVarVar1YIAOw==');
+  }
+
+  &[checked]::before {
+    background-image: url('data:image/gif;base64,R0lGODlhFAAUAPYAAAAAAHeHkD9QYB8wSOjo6Haw6C1huLbn8MfY8Ja42I630Ia48NjY2IefoJbH6Ja44H648D9CkCVZsE9XWFWY2D5wqI+foH6gwE9fYHeIoF93iPj4+J7P+J7A4IegyE54sPDw8I+nuB5BmAAHCEdXWC9BiFaAuFdveF2g4F9veB5JoA8PD12Y4GWg4CZRqKbI6GWo4BcoOK7Q8Cc4SDVwwAcPEEdQYA8YIDc/QBYykC1pwD9IWGaY0C8/SE2Q0B84UF6QyFWY4E2Q2D5omAcIGA8gMEZoiEZvkC5ZsE9ZmP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAABKACwAAAAAFAAUAAAH/4BKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKEys4SkpKSkpKSkpKSkpKSkpKShYAIyBKSkpKSkpKIUkRERERERERNwA2SkpKSkpKShslHggICAgICAEANyUbSkpKSkpKGzkoMjIyMjIdKwAVORtKSkpKSkogIhSAOz0ZMjI2ADZBIiBKSkpKSkogKkEaACsJFwArHUEqIEpKSkpKSiAuQSgxAD8xAEMoQS4gSkpKSkpKIEgoMDw1AAADMDAoSCBKSkpKSkogBigFBUcANTwFBS0GIEpKSkpKSiA0MAsLCzNHCwsLLTQgSkpKSkpKICYLHBwcDhwcgJYcHAsmIEpKSkpKShspCgcHBwcHBwcHCikbSkpKSkpKGxYYGBgYGBgYGBgYFhtKSkpKSkpKGyAMDAwMDAwMDCAbSkpKSkpKSkpKShsbGxsbGxsbSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkqASkpKSkpKSkpKSkpKSkpKSkpKSkpKSoEAOw==');
+  }
+}

+ 121 - 0
admin/static/lib/simditor/plugins/dropzone/simditor-dropzone.js

@@ -0,0 +1,121 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(["jquery",
+      "simple-module",
+      "simditor"], function ($, SimpleModule) {
+      return (root.returnExportsGlobal = factory($, SimpleModule));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like enviroments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"),
+      require("simple-module"),
+      require("simditor"));
+  } else {
+    root['Simditor'] = factory(jQuery,
+      SimpleModule);
+  }
+}(this, function ($, SimpleModule) {
+
+var Dropzone,
+  __hasProp = {}.hasOwnProperty,
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+Dropzone = (function(_super) {
+  __extends(Dropzone, _super);
+
+  function Dropzone() {
+    return Dropzone.__super__.constructor.apply(this, arguments);
+  }
+
+  Dropzone.pluginName = "Dropzone";
+
+  Dropzone.prototype._entered = 0;
+
+  Dropzone.prototype._init = function() {
+    this.editor = this._module;
+    if (this.editor.uploader == null) {
+      throw new Error("Can't work without 'simple-uploader' module");
+      return;
+    }
+    $(document.body).on("dragover", function(e) {
+      e.originalEvent.dataTransfer.dropEffect = "none";
+      return e.preventDefault();
+    });
+    $(document.body).on('drop', function(e) {
+      return e.preventDefault();
+    });
+    this.imageBtn = this.editor.toolbar.findButton("image");
+    return this.editor.body.on("dragover", function(e) {
+      e.originalEvent.dataTransfer.dropEffect = "copy";
+      e.stopPropagation();
+      return e.preventDefault();
+    }).on("dragenter", (function(_this) {
+      return function(e) {
+        if ((_this._entered += 1) === 1) {
+          _this.show();
+        }
+        e.preventDefault();
+        return e.stopPropagation();
+      };
+    })(this)).on("dragleave", (function(_this) {
+      return function(e) {
+        if ((_this._entered -= 1) <= 0) {
+          _this.hide();
+        }
+        e.preventDefault();
+        return e.stopPropagation();
+      };
+    })(this)).on("drop", (function(_this) {
+      return function(e) {
+        var file, imageFiles, _i, _j, _len, _len1, _ref;
+        imageFiles = [];
+        _ref = e.originalEvent.dataTransfer.files;
+        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+          file = _ref[_i];
+          if (!_this.validFile(file)) {
+            alert("「" + file.name + "]」文件不是图片。");
+            _this.hide();
+            return false;
+          }
+          imageFiles.push(file);
+        }
+        for (_j = 0, _len1 = imageFiles.length; _j < _len1; _j++) {
+          file = imageFiles[_j];
+          _this.editor.uploader.upload(file, {
+            inline: true
+          });
+        }
+        _this.hide();
+        e.stopPropagation();
+        return e.preventDefault();
+      };
+    })(this));
+  };
+
+  Dropzone.prototype.show = function() {
+    return this.imageBtn.setActive(true);
+  };
+
+  Dropzone.prototype.hide = function() {
+    this.imageBtn.setActive(false);
+    return this._entered = 0;
+  };
+
+  Dropzone.prototype.validFile = function(file) {
+    return file.type.indexOf("image/") > -1;
+  };
+
+  return Dropzone;
+
+})(SimpleModule);
+
+Simditor.connect(Dropzone);
+
+
+return Simditor;
+
+
+}));

+ 1006 - 0
admin/static/lib/simditor/plugins/html/beautify-html.js

@@ -0,0 +1,1006 @@
+/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
+/*
+  The MIT License (MIT)
+  Copyright (c) 2007-2013 Einar Lielmanis and contributors.
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+ Style HTML
+---------------
+  Written by Nochum Sossonko, (nsossonko@hotmail.com)
+  Based on code initially developed by: Einar Lielmanis, <einar@jsbeautifier.org>
+    http://jsbeautifier.org/
+  Usage:
+    style_html(html_source);
+    style_html(html_source, options);
+  The options are:
+    indent_inner_html (default false)  — indent <head> and <body> sections,
+    indent_size (default 4)          — indentation size,
+    indent_char (default space)      — character to indent with,
+    wrap_line_length (default 250)            -  maximum amount of characters per line (0 = disable)
+    brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "none"
+            put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are.
+    unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
+    indent_scripts (default normal)  - "keep"|"separate"|"normal"
+    preserve_newlines (default true) - whether existing line breaks before elements should be preserved
+                                        Only works before elements, not inside tags or for text.
+    max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk
+    indent_handlebars (default false) - format and indent {{#foo}} and {{/foo}}
+    end_with_newline (false)          - end with a newline
+    extra_liners (default [head,body,/html]) -List of tags that should have an extra newline before them.
+    e.g.
+    style_html(html_source, {
+      'indent_inner_html': false,
+      'indent_size': 2,
+      'indent_char': ' ',
+      'wrap_line_length': 78,
+      'brace_style': 'expand',
+      'preserve_newlines': true,
+      'max_preserve_newlines': 5,
+      'indent_handlebars': false,
+      'extra_liners': ['/html']
+    });
+*/
+
+(function() {
+
+    // function trim(s) {
+    //     return s.replace(/^\s+|\s+$/g, '');
+    // }
+
+    function ltrim(s) {
+        return s.replace(/^\s+/g, '');
+    }
+
+    function rtrim(s) {
+        return s.replace(/\s+$/g, '');
+    }
+
+    function style_html(html_source, options, js_beautify, css_beautify) {
+        //Wrapper function to invoke all the necessary constructors and deal with the output.
+
+        var multi_parser,
+            indent_inner_html,
+            indent_size,
+            indent_character,
+            wrap_line_length,
+            brace_style,
+            unformatted,
+            preserve_newlines,
+            max_preserve_newlines,
+            indent_handlebars,
+            wrap_attributes,
+            wrap_attributes_indent_size,
+            end_with_newline,
+            extra_liners,
+            eol;
+
+        options = options || {};
+
+        // backwards compatibility to 1.3.4
+        if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) &&
+            (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) {
+            options.wrap_line_length = options.max_char;
+        }
+
+        indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html;
+        indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10);
+        indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char;
+        brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style;
+        wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10);
+        unformatted = options.unformatted || [
+            // https://www.w3.org/TR/html5/dom.html#phrasing-content
+            'a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite',
+            'code', 'data', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img',
+            'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript',
+            'object', 'output', 'progress', 'q', 'ruby', 's', 'samp', /* 'script', */ 'select', 'small',
+            'span', 'strong', 'sub', 'sup', 'svg', 'template', 'textarea', 'time', 'u', 'var',
+            'video', 'wbr', 'text',
+            // prexisting - not sure of full effect of removing, leaving in
+            'acronym', 'address', 'big', 'dt', 'ins', 'small', 'strike', 'tt',
+            'pre',
+            'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
+        ];
+        preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
+        max_preserve_newlines = preserve_newlines ?
+            (isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10)) :
+            0;
+        indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars;
+        wrap_attributes = (options.wrap_attributes === undefined) ? 'auto' : options.wrap_attributes;
+        wrap_attributes_indent_size = (isNaN(parseInt(options.wrap_attributes_indent_size, 10))) ? indent_size : parseInt(options.wrap_attributes_indent_size, 10);
+        end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
+        extra_liners = (typeof options.extra_liners === 'object') && options.extra_liners ?
+            options.extra_liners.concat() : (typeof options.extra_liners === 'string') ?
+            options.extra_liners.split(',') : 'head,body,/html'.split(',');
+        eol = options.eol ? options.eol : '\n';
+
+        if (options.indent_with_tabs) {
+            indent_character = '\t';
+            indent_size = 1;
+        }
+
+        eol = eol.replace(/\\r/, '\r').replace(/\\n/, '\n');
+
+        function Parser() {
+
+            this.pos = 0; //Parser position
+            this.token = '';
+            this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
+            this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
+                parent: 'parent1',
+                parentcount: 1,
+                parent1: ''
+            };
+            this.tag_type = '';
+            this.token_text = this.last_token = this.last_text = this.token_type = '';
+            this.newlines = 0;
+            this.indent_content = indent_inner_html;
+
+            this.Utils = { //Uilities made available to the various functions
+                whitespace: "\n\r\t ".split(''),
+
+                single_token: [
+                    // HTLM void elements - aka self-closing tags - aka singletons
+                    // https://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
+                    'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
+                    'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr',
+                    // NOTE: Optional tags - are not understood.
+                    // https://www.w3.org/TR/html5/syntax.html#optional-tags
+                    // The rules for optional tags are too complex for a simple list
+                    // Also, the content of these tags should still be indented in many cases.
+                    // 'li' is a good exmple.
+
+                    // Doctype and xml elements
+                    '!doctype', '?xml',
+                    // ?php tag
+                    '?php',
+                    // other tags that were in this list, keeping just in case
+                    'basefont', 'isindex'
+                ],
+                extra_liners: extra_liners, //for tags that need a line of whitespace before them
+                in_array: function(what, arr) {
+                    for (var i = 0; i < arr.length; i++) {
+                        if (what === arr[i]) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+            };
+
+            // Return true if the given text is composed entirely of whitespace.
+            this.is_whitespace = function(text) {
+                for (var n = 0; n < text.length; n++) {
+                    if (!this.Utils.in_array(text.charAt(n), this.Utils.whitespace)) {
+                        return false;
+                    }
+                }
+                return true;
+            };
+
+            this.traverse_whitespace = function() {
+                var input_char = '';
+
+                input_char = this.input.charAt(this.pos);
+                if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+                    this.newlines = 0;
+                    while (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+                        if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) {
+                            this.newlines += 1;
+                        }
+
+                        this.pos++;
+                        input_char = this.input.charAt(this.pos);
+                    }
+                    return true;
+                }
+                return false;
+            };
+
+            // Append a space to the given content (string array) or, if we are
+            // at the wrap_line_length, append a newline/indentation.
+            // return true if a newline was added, false if a space was added
+            this.space_or_wrap = function(content) {
+                if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached
+                    this.print_newline(false, content);
+                    this.print_indentation(content);
+                    return true;
+                } else {
+                    this.line_char_count++;
+                    content.push(' ');
+                    return false;
+                }
+            };
+
+            this.get_content = function() { //function to capture regular content between tags
+                var input_char = '',
+                    content = [];
+
+                while (this.input.charAt(this.pos) !== '<') {
+                    if (this.pos >= this.input.length) {
+                        return content.length ? content.join('') : ['', 'TK_EOF'];
+                    }
+
+                    if (this.traverse_whitespace()) {
+                        this.space_or_wrap(content);
+                        continue;
+                    }
+
+                    if (indent_handlebars) {
+                        // Handlebars parsing is complicated.
+                        // {{#foo}} and {{/foo}} are formatted tags.
+                        // {{something}} should get treated as content, except:
+                        // {{else}} specifically behaves like {{#if}} and {{/if}}
+                        var peek3 = this.input.substr(this.pos, 3);
+                        if (peek3 === '{{#' || peek3 === '{{/') {
+                            // These are tags and not content.
+                            break;
+                        } else if (peek3 === '{{!') {
+                            return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT'];
+                        } else if (this.input.substr(this.pos, 2) === '{{') {
+                            if (this.get_tag(true) === '{{else}}') {
+                                break;
+                            }
+                        }
+                    }
+
+                    input_char = this.input.charAt(this.pos);
+                    this.pos++;
+                    this.line_char_count++;
+                    content.push(input_char); //letter at-a-time (or string) inserted to an array
+                }
+                return content.length ? content.join('') : '';
+            };
+
+            this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify
+                if (this.pos === this.input.length) {
+                    return ['', 'TK_EOF'];
+                }
+                var content = '';
+                var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
+                reg_match.lastIndex = this.pos;
+                var reg_array = reg_match.exec(this.input);
+                var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script
+                if (this.pos < end_script) { //get everything in between the script tags
+                    content = this.input.substring(this.pos, end_script);
+                    this.pos = end_script;
+                }
+                return content;
+            };
+
+            this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
+                if (this.tags[tag + 'count']) { //check for the existence of this tag type
+                    this.tags[tag + 'count']++;
+                    this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+                } else { //otherwise initialize this tag type
+                    this.tags[tag + 'count'] = 1;
+                    this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+                }
+                this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
+                this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
+            };
+
+            this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
+                if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
+                    var temp_parent = this.tags.parent; //check to see if it's a closable tag.
+                    while (temp_parent) { //till we reach '' (the initial value);
+                        if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
+                            break;
+                        }
+                        temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
+                    }
+                    if (temp_parent) { //if we caught something
+                        this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
+                        this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
+                    }
+                    delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
+                    delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
+                    if (this.tags[tag + 'count'] === 1) {
+                        delete this.tags[tag + 'count'];
+                    } else {
+                        this.tags[tag + 'count']--;
+                    }
+                }
+            };
+
+            this.indent_to_tag = function(tag) {
+                // Match the indentation level to the last use of this tag, but don't remove it.
+                if (!this.tags[tag + 'count']) {
+                    return;
+                }
+                var temp_parent = this.tags.parent;
+                while (temp_parent) {
+                    if (tag + this.tags[tag + 'count'] === temp_parent) {
+                        break;
+                    }
+                    temp_parent = this.tags[temp_parent + 'parent'];
+                }
+                if (temp_parent) {
+                    this.indent_level = this.tags[tag + this.tags[tag + 'count']];
+                }
+            };
+
+            this.get_tag = function(peek) { //function to get a full tag and parse its type
+                var input_char = '',
+                    content = [],
+                    comment = '',
+                    space = false,
+                    first_attr = true,
+                    tag_start, tag_end,
+                    tag_start_char,
+                    orig_pos = this.pos,
+                    orig_line_char_count = this.line_char_count;
+
+                peek = peek !== undefined ? peek : false;
+
+                do {
+                    if (this.pos >= this.input.length) {
+                        if (peek) {
+                            this.pos = orig_pos;
+                            this.line_char_count = orig_line_char_count;
+                        }
+                        return content.length ? content.join('') : ['', 'TK_EOF'];
+                    }
+
+                    input_char = this.input.charAt(this.pos);
+                    this.pos++;
+
+                    if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
+                        space = true;
+                        continue;
+                    }
+
+                    if (input_char === "'" || input_char === '"') {
+                        input_char += this.get_unformatted(input_char);
+                        space = true;
+
+                    }
+
+                    if (input_char === '=') { //no space before =
+                        space = false;
+                    }
+
+                    if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
+                        //no space after = or before >
+                        var wrapped = this.space_or_wrap(content);
+                        var indentAttrs = wrapped && input_char !== '/' && wrap_attributes !== 'force';
+                        space = false;
+                        if (!first_attr && wrap_attributes === 'force' && input_char !== '/') {
+                            this.print_newline(false, content);
+                            this.print_indentation(content);
+                            indentAttrs = true;
+                        }
+                        if (indentAttrs) {
+                            //indent attributes an auto or forced line-wrap
+                            for (var count = 0; count < wrap_attributes_indent_size; count++) {
+                                content.push(indent_character);
+                            }
+                        }
+                        for (var i = 0; i < content.length; i++) {
+                            if (content[i] === ' ') {
+                                first_attr = false;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (indent_handlebars && tag_start_char === '<') {
+                        // When inside an angle-bracket tag, put spaces around
+                        // handlebars not inside of strings.
+                        if ((input_char + this.input.charAt(this.pos)) === '{{') {
+                            input_char += this.get_unformatted('}}');
+                            if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') {
+                                input_char = ' ' + input_char;
+                            }
+                            space = true;
+                        }
+                    }
+
+                    if (input_char === '<' && !tag_start_char) {
+                        tag_start = this.pos - 1;
+                        tag_start_char = '<';
+                    }
+
+                    if (indent_handlebars && !tag_start_char) {
+                        if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] === '{') {
+                            if (input_char === '#' || input_char === '/' || input_char === '!') {
+                                tag_start = this.pos - 3;
+                            } else {
+                                tag_start = this.pos - 2;
+                            }
+                            tag_start_char = '{';
+                        }
+                    }
+
+                    this.line_char_count++;
+                    content.push(input_char); //inserts character at-a-time (or string)
+
+                    if (content[1] && (content[1] === '!' || content[1] === '?' || content[1] === '%')) { //if we're in a comment, do something special
+                        // We treat all comments as literals, even more than preformatted tags
+                        // we just look for the appropriate close tag
+                        content = [this.get_comment(tag_start)];
+                        break;
+                    }
+
+                    if (indent_handlebars && content[1] && content[1] === '{' && content[2] && content[2] === '!') { //if we're in a comment, do something special
+                        // We treat all comments as literals, even more than preformatted tags
+                        // we just look for the appropriate close tag
+                        content = [this.get_comment(tag_start)];
+                        break;
+                    }
+
+                    if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') {
+                        break;
+                    }
+                } while (input_char !== '>');
+
+                var tag_complete = content.join('');
+                var tag_index;
+                var tag_offset;
+
+                if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
+                    tag_index = tag_complete.indexOf(' ');
+                } else if (tag_complete.charAt(0) === '{') {
+                    tag_index = tag_complete.indexOf('}');
+                } else { //otherwise go with the tag ending
+                    tag_index = tag_complete.indexOf('>');
+                }
+                if (tag_complete.charAt(0) === '<' || !indent_handlebars) {
+                    tag_offset = 1;
+                } else {
+                    tag_offset = tag_complete.charAt(2) === '#' ? 3 : 2;
+                }
+                var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase();
+                if (tag_complete.charAt(tag_complete.length - 2) === '/' ||
+                    this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
+                    if (!peek) {
+                        this.tag_type = 'SINGLE';
+                    }
+                } else if (indent_handlebars && tag_complete.charAt(0) === '{' && tag_check === 'else') {
+                    if (!peek) {
+                        this.indent_to_tag('if');
+                        this.tag_type = 'HANDLEBARS_ELSE';
+                        this.indent_content = true;
+                        this.traverse_whitespace();
+                    }
+                } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
+                    comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function
+                    content.push(comment);
+                    tag_end = this.pos - 1;
+                    this.tag_type = 'SINGLE';
+                } else if (tag_check === 'script' &&
+                    (tag_complete.search('type') === -1 ||
+                        (tag_complete.search('type') > -1 &&
+                            tag_complete.search(/\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json)/) > -1))) {
+                    if (!peek) {
+                        this.record_tag(tag_check);
+                        this.tag_type = 'SCRIPT';
+                    }
+                } else if (tag_check === 'style' &&
+                    (tag_complete.search('type') === -1 ||
+                        (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) {
+                    if (!peek) {
+                        this.record_tag(tag_check);
+                        this.tag_type = 'STYLE';
+                    }
+                } else if (tag_check.charAt(0) === '!') { //peek for <! comment
+                    // for comments content is already correct.
+                    if (!peek) {
+                        this.tag_type = 'SINGLE';
+                        this.traverse_whitespace();
+                    }
+                } else if (!peek) {
+                    if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
+                        this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
+                        this.tag_type = 'END';
+                    } else { //otherwise it's a start-tag
+                        this.record_tag(tag_check); //push it on the tag stack
+                        if (tag_check.toLowerCase() !== 'html') {
+                            this.indent_content = true;
+                        }
+                        this.tag_type = 'START';
+                    }
+
+                    // Allow preserving of newlines after a start or end tag
+                    if (this.traverse_whitespace()) {
+                        this.space_or_wrap(content);
+                    }
+
+                    if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
+                        this.print_newline(false, this.output);
+                        if (this.output.length && this.output[this.output.length - 2] !== '\n') {
+                            this.print_newline(true, this.output);
+                        }
+                    }
+                }
+
+                if (peek) {
+                    this.pos = orig_pos;
+                    this.line_char_count = orig_line_char_count;
+                }
+
+                return content.join(''); //returns fully formatted tag
+            };
+
+            this.get_comment = function(start_pos) { //function to return comment content in its entirety
+                // this is will have very poor perf, but will work for now.
+                var comment = '',
+                    delimiter = '>',
+                    matched = false;
+
+                this.pos = start_pos;
+                var input_char = this.input.charAt(this.pos);
+                this.pos++;
+
+                while (this.pos <= this.input.length) {
+                    comment += input_char;
+
+                    // only need to check for the delimiter if the last chars match
+                    if (comment.charAt(comment.length - 1) === delimiter.charAt(delimiter.length - 1) &&
+                        comment.indexOf(delimiter) !== -1) {
+                        break;
+                    }
+
+                    // only need to search for custom delimiter for the first few characters
+                    if (!matched && comment.length < 10) {
+                        if (comment.indexOf('<![if') === 0) { //peek for <![if conditional comment
+                            delimiter = '<![endif]>';
+                            matched = true;
+                        } else if (comment.indexOf('<![cdata[') === 0) { //if it's a <[cdata[ comment...
+                            delimiter = ']]>';
+                            matched = true;
+                        } else if (comment.indexOf('<![') === 0) { // some other ![ comment? ...
+                            delimiter = ']>';
+                            matched = true;
+                        } else if (comment.indexOf('<!--') === 0) { // <!-- comment ...
+                            delimiter = '-->';
+                            matched = true;
+                        } else if (comment.indexOf('{{!') === 0) { // {{! handlebars comment
+                            delimiter = '}}';
+                            matched = true;
+                        } else if (comment.indexOf('<?') === 0) { // {{! handlebars comment
+                            delimiter = '?>';
+                            matched = true;
+                        } else if (comment.indexOf('<%') === 0) { // {{! handlebars comment
+                            delimiter = '%>';
+                            matched = true;
+                        }
+                    }
+
+                    input_char = this.input.charAt(this.pos);
+                    this.pos++;
+                }
+
+                return comment;
+            };
+
+            function tokenMatcher(delimiter) {
+                var token = '';
+
+                var add = function(str) {
+                    var newToken = token + str.toLowerCase();
+                    token = newToken.length <= delimiter.length ? newToken : newToken.substr(newToken.length - delimiter.length, delimiter.length);
+                };
+
+                var doesNotMatch = function() {
+                    return token.indexOf(delimiter) === -1;
+                };
+
+                return {
+                    add: add,
+                    doesNotMatch: doesNotMatch
+                };
+            }
+
+            this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety
+                if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
+                    return '';
+                }
+                var input_char = '';
+                var content = '';
+                var space = true;
+
+                var delimiterMatcher = tokenMatcher(delimiter);
+
+                do {
+
+                    if (this.pos >= this.input.length) {
+                        return content;
+                    }
+
+                    input_char = this.input.charAt(this.pos);
+                    this.pos++;
+
+                    if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+                        if (!space) {
+                            this.line_char_count--;
+                            continue;
+                        }
+                        if (input_char === '\n' || input_char === '\r') {
+                            content += '\n';
+                            /*  Don't change tab indention for unformatted blocks.  If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
+                for (var i=0; i<this.indent_level; i++) {
+                  content += this.indent_string;
+                }
+                space = false; //...and make sure other indentation is erased
+                */
+                            this.line_char_count = 0;
+                            continue;
+                        }
+                    }
+                    content += input_char;
+                    delimiterMatcher.add(input_char);
+                    this.line_char_count++;
+                    space = true;
+
+                    if (indent_handlebars && input_char === '{' && content.length && content.charAt(content.length - 2) === '{') {
+                        // Handlebars expressions in strings should also be unformatted.
+                        content += this.get_unformatted('}}');
+                        // Don't consider when stopping for delimiters.
+                    }
+                } while (delimiterMatcher.doesNotMatch());
+
+                return content;
+            };
+
+            this.get_token = function() { //initial handler for token-retrieval
+                var token;
+
+                if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
+                    var type = this.last_token.substr(7);
+                    token = this.get_contents_to(type);
+                    if (typeof token !== 'string') {
+                        return token;
+                    }
+                    return [token, 'TK_' + type];
+                }
+                if (this.current_mode === 'CONTENT') {
+                    token = this.get_content();
+                    if (typeof token !== 'string') {
+                        return token;
+                    } else {
+                        return [token, 'TK_CONTENT'];
+                    }
+                }
+
+                if (this.current_mode === 'TAG') {
+                    token = this.get_tag();
+                    if (typeof token !== 'string') {
+                        return token;
+                    } else {
+                        var tag_name_type = 'TK_TAG_' + this.tag_type;
+                        return [token, tag_name_type];
+                    }
+                }
+            };
+
+            this.get_full_indent = function(level) {
+                level = this.indent_level + level || 0;
+                if (level < 1) {
+                    return '';
+                }
+
+                return Array(level + 1).join(this.indent_string);
+            };
+
+            this.is_unformatted = function(tag_check, unformatted) {
+                //is this an HTML5 block-level link?
+                if (!this.Utils.in_array(tag_check, unformatted)) {
+                    return false;
+                }
+
+                if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) {
+                    return true;
+                }
+
+                //at this point we have an  tag; is its first child something we want to remain
+                //unformatted?
+                var next_tag = this.get_tag(true /* peek. */ );
+
+                // test next_tag to see if it is just html tag (no external content)
+                var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
+
+                // if next_tag comes back but is not an isolated tag, then
+                // let's treat the 'a' tag as having content
+                // and respect the unformatted option
+                if (!tag || this.Utils.in_array(tag, unformatted)) {
+                    return true;
+                } else {
+                    return false;
+                }
+            };
+
+            this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions
+
+                this.input = js_source || ''; //gets the input for the Parser
+
+                // HACK: newline parsing inconsistent. This brute force normalizes the input.
+                this.input = this.input.replace(/\r\n|[\r\u2028\u2029]/g, '\n');
+
+                this.output = [];
+                this.indent_character = indent_character;
+                this.indent_string = '';
+                this.indent_size = indent_size;
+                this.brace_style = brace_style;
+                this.indent_level = 0;
+                this.wrap_line_length = wrap_line_length;
+                this.line_char_count = 0; //count to see if wrap_line_length was exceeded
+
+                for (var i = 0; i < this.indent_size; i++) {
+                    this.indent_string += this.indent_character;
+                }
+
+                this.print_newline = function(force, arr) {
+                    this.line_char_count = 0;
+                    if (!arr || !arr.length) {
+                        return;
+                    }
+                    if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
+                        if ((arr[arr.length - 1] !== '\n')) {
+                            arr[arr.length - 1] = rtrim(arr[arr.length - 1]);
+                        }
+                        arr.push('\n');
+                    }
+                };
+
+                this.print_indentation = function(arr) {
+                    for (var i = 0; i < this.indent_level; i++) {
+                        arr.push(this.indent_string);
+                        this.line_char_count += this.indent_string.length;
+                    }
+                };
+
+                this.print_token = function(text) {
+                    // Avoid printing initial whitespace.
+                    if (this.is_whitespace(text) && !this.output.length) {
+                        return;
+                    }
+                    if (text || text !== '') {
+                        if (this.output.length && this.output[this.output.length - 1] === '\n') {
+                            this.print_indentation(this.output);
+                            text = ltrim(text);
+                        }
+                    }
+                    this.print_token_raw(text);
+                };
+
+                this.print_token_raw = function(text) {
+                    // If we are going to print newlines, truncate trailing
+                    // whitespace, as the newlines will represent the space.
+                    if (this.newlines > 0) {
+                        text = rtrim(text);
+                    }
+
+                    if (text && text !== '') {
+                        if (text.length > 1 && text.charAt(text.length - 1) === '\n') {
+                            // unformatted tags can grab newlines as their last character
+                            this.output.push(text.slice(0, -1));
+                            this.print_newline(false, this.output);
+                        } else {
+                            this.output.push(text);
+                        }
+                    }
+
+                    for (var n = 0; n < this.newlines; n++) {
+                        this.print_newline(n > 0, this.output);
+                    }
+                    this.newlines = 0;
+                };
+
+                this.indent = function() {
+                    this.indent_level++;
+                };
+
+                this.unindent = function() {
+                    if (this.indent_level > 0) {
+                        this.indent_level--;
+                    }
+                };
+            };
+            return this;
+        }
+
+        /*_____________________--------------------_____________________*/
+
+        multi_parser = new Parser(); //wrapping functions Parser
+        multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values
+
+        while (true) {
+            var t = multi_parser.get_token();
+            multi_parser.token_text = t[0];
+            multi_parser.token_type = t[1];
+
+            if (multi_parser.token_type === 'TK_EOF') {
+                break;
+            }
+
+            switch (multi_parser.token_type) {
+                case 'TK_TAG_START':
+                    multi_parser.print_newline(false, multi_parser.output);
+                    multi_parser.print_token(multi_parser.token_text);
+                    if (multi_parser.indent_content) {
+                        multi_parser.indent();
+                        multi_parser.indent_content = false;
+                    }
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_STYLE':
+                case 'TK_TAG_SCRIPT':
+                    multi_parser.print_newline(false, multi_parser.output);
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_END':
+                    //Print new line only if the tag has no content and has child
+                    if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
+                        var tag_name = multi_parser.token_text.match(/\w+/)[0];
+                        var tag_extracted_from_last_output = null;
+                        if (multi_parser.output.length) {
+                            tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/);
+                        }
+                        if (tag_extracted_from_last_output === null ||
+                            (tag_extracted_from_last_output[1] !== tag_name && !multi_parser.Utils.in_array(tag_extracted_from_last_output[1], unformatted))) {
+                            multi_parser.print_newline(false, multi_parser.output);
+                        }
+                    }
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_SINGLE':
+                    // Don't add a newline before elements that should remain unformatted.
+                    var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i);
+                    if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
+                        multi_parser.print_newline(false, multi_parser.output);
+                    }
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_HANDLEBARS_ELSE':
+                    // Don't add a newline if opening {{#if}} tag is on the current line
+                    var foundIfOnCurrentLine = false;
+                    for (var lastCheckedOutput = multi_parser.output.length - 1; lastCheckedOutput >= 0; lastCheckedOutput--) {
+                        if (multi_parser.output[lastCheckedOutput] === '\n') {
+                            break;
+                        } else {
+                            if (multi_parser.output[lastCheckedOutput].match(/{{#if/)) {
+                                foundIfOnCurrentLine = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!foundIfOnCurrentLine) {
+                        multi_parser.print_newline(false, multi_parser.output);
+                    }
+                    multi_parser.print_token(multi_parser.token_text);
+                    if (multi_parser.indent_content) {
+                        multi_parser.indent();
+                        multi_parser.indent_content = false;
+                    }
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_HANDLEBARS_COMMENT':
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'TAG';
+                    break;
+                case 'TK_CONTENT':
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'TAG';
+                    break;
+                case 'TK_STYLE':
+                case 'TK_SCRIPT':
+                    if (multi_parser.token_text !== '') {
+                        multi_parser.print_newline(false, multi_parser.output);
+                        var text = multi_parser.token_text,
+                            _beautifier,
+                            script_indent_level = 1;
+                        if (multi_parser.token_type === 'TK_SCRIPT') {
+                            _beautifier = typeof js_beautify === 'function' && js_beautify;
+                        } else if (multi_parser.token_type === 'TK_STYLE') {
+                            _beautifier = typeof css_beautify === 'function' && css_beautify;
+                        }
+
+                        if (options.indent_scripts === "keep") {
+                            script_indent_level = 0;
+                        } else if (options.indent_scripts === "separate") {
+                            script_indent_level = -multi_parser.indent_level;
+                        }
+
+                        var indentation = multi_parser.get_full_indent(script_indent_level);
+                        if (_beautifier) {
+
+                            // call the Beautifier if avaliable
+                            var Child_options = function() {
+                                this.eol = '\n';
+                            };
+                            Child_options.prototype = options;
+                            var child_options = new Child_options();
+                            text = _beautifier(text.replace(/^\s*/, indentation), child_options);
+                        } else {
+                            // simply indent the string otherwise
+                            var white = text.match(/^\s*/)[0];
+                            var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
+                            var reindent = multi_parser.get_full_indent(script_indent_level - _level);
+                            text = text.replace(/^\s*/, indentation)
+                                .replace(/\r\n|\r|\n/g, '\n' + reindent)
+                                .replace(/\s+$/, '');
+                        }
+                        if (text) {
+                            multi_parser.print_token_raw(text);
+                            multi_parser.print_newline(true, multi_parser.output);
+                        }
+                    }
+                    multi_parser.current_mode = 'TAG';
+                    break;
+                default:
+                    // We should not be getting here but we don't want to drop input on the floor
+                    // Just output the text and move on
+                    if (multi_parser.token_text !== '') {
+                        multi_parser.print_token(multi_parser.token_text);
+                    }
+                    break;
+            }
+            multi_parser.last_token = multi_parser.token_type;
+            multi_parser.last_text = multi_parser.token_text;
+        }
+        var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, '');
+
+        // establish end_with_newline
+        if (end_with_newline) {
+            sweet_code += '\n';
+        }
+
+        if (eol !== '\n') {
+            sweet_code = sweet_code.replace(/[\n]/g, eol);
+        }
+
+        return sweet_code;
+    }
+
+    if (typeof define === "function" && define.amd) {
+        // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- )
+        define(["require", "./beautify", "./beautify-css"], function(requireamd) {
+            var js_beautify = requireamd("./beautify");
+            var css_beautify = requireamd("./beautify-css");
+
+            return {
+                html_beautify: function(html_source, options) {
+                    return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
+                }
+            };
+        });
+    } else if (typeof exports !== "undefined") {
+        // Add support for CommonJS. Just put this file somewhere on your require.paths
+        // and you will be able to `var html_beautify = require("beautify").html_beautify`.
+        var js_beautify = require('./beautify.js');
+        var css_beautify = require('./beautify-css.js');
+
+        exports.html_beautify = function(html_source, options) {
+            return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
+        };
+    } else if (typeof window !== "undefined") {
+        // If we're running a web page and don't have either of the above, add our one global
+        window.html_beautify = function(html_source, options) {
+            return style_html(html_source, options, window.js_beautify, window.css_beautify);
+        };
+    } else if (typeof global !== "undefined") {
+        // If we don't even have window, try global.
+        global.html_beautify = function(html_source, options) {
+            return style_html(html_source, options, global.js_beautify, global.css_beautify);
+        };
+    }
+
+}());

+ 7 - 0
admin/static/lib/simditor/plugins/html/simditor-html.css

@@ -0,0 +1,7 @@
+.simditor.simditor-html .simditor-wrapper > textarea {
+  display: block !important;
+}
+.simditor.simditor-html .simditor-body,
+.simditor.simditor-html .simditor-placeholder {
+  display: none;
+}

+ 104 - 0
admin/static/lib/simditor/plugins/html/simditor-html.js

@@ -0,0 +1,104 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define('simditor-html', ["jquery","simditor","html_beautify"], function (a0,b1,c2) {
+      return (root['HTMLButton'] = factory(a0,b1,c2));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"),require("simditor"),require("js_beautify"));
+  } else {
+    root['SimditorHTML'] = factory(jQuery,Simditor,html_beautify);
+  }
+}(this, function ($, Simditor, beautify) {
+
+var HTMLButton,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty,
+  slice = [].slice;
+
+HTMLButton = (function(superClass) {
+  extend(HTMLButton, superClass);
+
+  function HTMLButton() {
+    return HTMLButton.__super__.constructor.apply(this, arguments);
+  }
+
+  HTMLButton.prototype.name = 'html';
+
+  HTMLButton.prototype.icon = 'html5';
+
+  HTMLButton.prototype.needFocus = false;
+
+  HTMLButton.prototype._init = function() {
+    HTMLButton.__super__._init.call(this);
+    this.editor.textarea.on('focus', (function(_this) {
+      return function(e) {
+        return _this.editor.el.addClass('focus').removeClass('error');
+      };
+    })(this));
+    this.editor.textarea.on('blur', (function(_this) {
+      return function(e) {
+        _this.editor.el.removeClass('focus');
+        return _this.editor.setValue(_this.editor.textarea.val());
+      };
+    })(this));
+    return this.editor.textarea.on('input', (function(_this) {
+      return function(e) {
+        return _this._resizeTextarea();
+      };
+    })(this));
+  };
+
+  HTMLButton.prototype.status = function() {};
+
+  HTMLButton.prototype.command = function() {
+    var button, i, len, ref;
+    this.editor.blur();
+    this.editor.el.toggleClass('simditor-html');
+    this.editor.htmlMode = this.editor.el.hasClass('simditor-html');
+    if (this.editor.htmlMode) {
+      this.editor.hidePopover();
+      this.editor.textarea.val(this.beautifyHTML(this.editor.textarea.val()));
+      this._resizeTextarea();
+    } else {
+      this.editor.setValue(this.editor.textarea.val());
+    }
+    ref = this.editor.toolbar.buttons;
+    for (i = 0, len = ref.length; i < len; i++) {
+      button = ref[i];
+      if (button.name === 'html') {
+        button.setActive(this.editor.htmlMode);
+      } else {
+        button.setDisabled(this.editor.htmlMode);
+      }
+    }
+    return null;
+  };
+
+  HTMLButton.prototype.beautifyHTML = function() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    if (beautify.html) {
+      return beautify.html.apply(beautify, args);
+    } else {
+      return beautify.apply(null, args);
+    }
+  };
+
+  HTMLButton.prototype._resizeTextarea = function() {
+    this._textareaPadding || (this._textareaPadding = this.editor.textarea.innerHeight() - this.editor.textarea.height());
+    return this.editor.textarea.height(this.editor.textarea[0].scrollHeight - this._textareaPadding);
+  };
+
+  return HTMLButton;
+
+})(Simditor.Button);
+
+Simditor.Toolbar.addButton(HTMLButton);
+
+return HTMLButton;
+
+}));

+ 241 - 0
admin/static/lib/simditor/scripts/hotkeys.js

@@ -0,0 +1,241 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define('simple-hotkeys', ["jquery","simple-module"], function ($, SimpleModule) {
+      return (root['hotkeys'] = factory($, SimpleModule));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"),require("simple-module"));
+  } else {
+    root.simple = root.simple || {};
+    root.simple['hotkeys'] = factory(jQuery,SimpleModule);
+  }
+}(this, function ($, SimpleModule) {
+
+var Hotkeys, hotkeys,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+Hotkeys = (function(superClass) {
+  extend(Hotkeys, superClass);
+
+  function Hotkeys() {
+    return Hotkeys.__super__.constructor.apply(this, arguments);
+  }
+
+  Hotkeys.count = 0;
+
+  Hotkeys.keyNameMap = {
+    8: "Backspace",
+    9: "Tab",
+    13: "Enter",
+    16: "Shift",
+    17: "Control",
+    18: "Alt",
+    19: "Pause",
+    20: "CapsLock",
+    27: "Esc",
+    32: "Spacebar",
+    33: "PageUp",
+    34: "PageDown",
+    35: "End",
+    36: "Home",
+    37: "Left",
+    38: "Up",
+    39: "Right",
+    40: "Down",
+    45: "Insert",
+    46: "Del",
+    91: "Meta",
+    93: "Meta",
+    48: "0",
+    49: "1",
+    50: "2",
+    51: "3",
+    52: "4",
+    53: "5",
+    54: "6",
+    55: "7",
+    56: "8",
+    57: "9",
+    65: "A",
+    66: "B",
+    67: "C",
+    68: "D",
+    69: "E",
+    70: "F",
+    71: "G",
+    72: "H",
+    73: "I",
+    74: "J",
+    75: "K",
+    76: "L",
+    77: "M",
+    78: "N",
+    79: "O",
+    80: "P",
+    81: "Q",
+    82: "R",
+    83: "S",
+    84: "T",
+    85: "U",
+    86: "V",
+    87: "W",
+    88: "X",
+    89: "Y",
+    90: "Z",
+    96: "0",
+    97: "1",
+    98: "2",
+    99: "3",
+    100: "4",
+    101: "5",
+    102: "6",
+    103: "7",
+    104: "8",
+    105: "9",
+    106: "Multiply",
+    107: "Add",
+    109: "Subtract",
+    110: "Decimal",
+    111: "Divide",
+    112: "F1",
+    113: "F2",
+    114: "F3",
+    115: "F4",
+    116: "F5",
+    117: "F6",
+    118: "F7",
+    119: "F8",
+    120: "F9",
+    121: "F10",
+    122: "F11",
+    123: "F12",
+    124: "F13",
+    125: "F14",
+    126: "F15",
+    127: "F16",
+    128: "F17",
+    129: "F18",
+    130: "F19",
+    131: "F20",
+    132: "F21",
+    133: "F22",
+    134: "F23",
+    135: "F24",
+    59: ";",
+    61: "=",
+    186: ";",
+    187: "=",
+    188: ",",
+    190: ".",
+    191: "/",
+    192: "`",
+    219: "[",
+    220: "\\",
+    221: "]",
+    222: "'"
+  };
+
+  Hotkeys.aliases = {
+    "escape": "esc",
+    "delete": "del",
+    "return": "enter",
+    "ctrl": "control",
+    "space": "spacebar",
+    "ins": "insert",
+    "cmd": "meta",
+    "command": "meta",
+    "wins": "meta",
+    "windows": "meta"
+  };
+
+  Hotkeys.normalize = function(shortcut) {
+    var i, j, key, keyname, keys, len;
+    keys = shortcut.toLowerCase().replace(/\s+/gi, "").split("+");
+    for (i = j = 0, len = keys.length; j < len; i = ++j) {
+      key = keys[i];
+      keys[i] = this.aliases[key] || key;
+    }
+    keyname = keys.pop();
+    keys.sort().push(keyname);
+    return keys.join("_");
+  };
+
+  Hotkeys.prototype.opts = {
+    el: document
+  };
+
+  Hotkeys.prototype._init = function() {
+    this.id = ++this.constructor.count;
+    this._map = {};
+    this._delegate = typeof this.opts.el === "string" ? document : this.opts.el;
+    return $(this._delegate).on("keydown.simple-hotkeys-" + this.id, this.opts.el, (function(_this) {
+      return function(e) {
+        var ref;
+        return (ref = _this._getHander(e)) != null ? ref.call(_this, e) : void 0;
+      };
+    })(this));
+  };
+
+  Hotkeys.prototype._getHander = function(e) {
+    var keyname, shortcut;
+    if (!(keyname = this.constructor.keyNameMap[e.which])) {
+      return;
+    }
+    shortcut = "";
+    if (e.altKey) {
+      shortcut += "alt_";
+    }
+    if (e.ctrlKey) {
+      shortcut += "control_";
+    }
+    if (e.metaKey) {
+      shortcut += "meta_";
+    }
+    if (e.shiftKey) {
+      shortcut += "shift_";
+    }
+    shortcut += keyname.toLowerCase();
+    return this._map[shortcut];
+  };
+
+  Hotkeys.prototype.respondTo = function(subject) {
+    if (typeof subject === 'string') {
+      return this._map[this.constructor.normalize(subject)] != null;
+    } else {
+      return this._getHander(subject) != null;
+    }
+  };
+
+  Hotkeys.prototype.add = function(shortcut, handler) {
+    this._map[this.constructor.normalize(shortcut)] = handler;
+    return this;
+  };
+
+  Hotkeys.prototype.remove = function(shortcut) {
+    delete this._map[this.constructor.normalize(shortcut)];
+    return this;
+  };
+
+  Hotkeys.prototype.destroy = function() {
+    $(this._delegate).off(".simple-hotkeys-" + this.id);
+    this._map = {};
+    return this;
+  };
+
+  return Hotkeys;
+
+})(SimpleModule);
+
+hotkeys = function(opts) {
+  return new Hotkeys(opts);
+};
+
+return hotkeys;
+
+}));
+

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 6 - 0
admin/static/lib/simditor/scripts/hotkeys.min.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
admin/static/lib/simditor/scripts/jquery.min.js


+ 172 - 0
admin/static/lib/simditor/scripts/module.js

@@ -0,0 +1,172 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define('simple-module', ["jquery"], function (a0) {
+      return (root['Module'] = factory(a0));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"));
+  } else {
+    root['SimpleModule'] = factory(jQuery);
+  }
+}(this, function ($) {
+
+var Module,
+  slice = [].slice;
+
+Module = (function() {
+  Module.extend = function(obj) {
+    var key, ref, val;
+    if (!((obj != null) && typeof obj === 'object')) {
+      return;
+    }
+    for (key in obj) {
+      val = obj[key];
+      if (key !== 'included' && key !== 'extended') {
+        this[key] = val;
+      }
+    }
+    return (ref = obj.extended) != null ? ref.call(this) : void 0;
+  };
+
+  Module.include = function(obj) {
+    var key, ref, val;
+    if (!((obj != null) && typeof obj === 'object')) {
+      return;
+    }
+    for (key in obj) {
+      val = obj[key];
+      if (key !== 'included' && key !== 'extended') {
+        this.prototype[key] = val;
+      }
+    }
+    return (ref = obj.included) != null ? ref.call(this) : void 0;
+  };
+
+  Module.connect = function(cls) {
+    if (typeof cls !== 'function') {
+      return;
+    }
+    if (!cls.pluginName) {
+      throw new Error('Module.connect: cannot connect plugin without pluginName');
+      return;
+    }
+    cls.prototype._connected = true;
+    if (!this._connectedClasses) {
+      this._connectedClasses = [];
+    }
+    this._connectedClasses.push(cls);
+    if (cls.pluginName) {
+      return this[cls.pluginName] = cls;
+    }
+  };
+
+  Module.prototype.opts = {};
+
+  function Module(opts) {
+    var base, cls, i, instance, instances, len, name;
+    this.opts = $.extend({}, this.opts, opts);
+    (base = this.constructor)._connectedClasses || (base._connectedClasses = []);
+    instances = (function() {
+      var i, len, ref, results;
+      ref = this.constructor._connectedClasses;
+      results = [];
+      for (i = 0, len = ref.length; i < len; i++) {
+        cls = ref[i];
+        name = cls.pluginName.charAt(0).toLowerCase() + cls.pluginName.slice(1);
+        if (cls.prototype._connected) {
+          cls.prototype._module = this;
+        }
+        results.push(this[name] = new cls());
+      }
+      return results;
+    }).call(this);
+    if (this._connected) {
+      this.opts = $.extend({}, this.opts, this._module.opts);
+    } else {
+      this._init();
+      for (i = 0, len = instances.length; i < len; i++) {
+        instance = instances[i];
+        if (typeof instance._init === "function") {
+          instance._init();
+        }
+      }
+    }
+    this.trigger('initialized');
+  }
+
+  Module.prototype._init = function() {};
+
+  Module.prototype.on = function() {
+    var args, ref;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    (ref = $(this)).on.apply(ref, args);
+    return this;
+  };
+
+  Module.prototype.one = function() {
+    var args, ref;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    (ref = $(this)).one.apply(ref, args);
+    return this;
+  };
+
+  Module.prototype.off = function() {
+    var args, ref;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    (ref = $(this)).off.apply(ref, args);
+    return this;
+  };
+
+  Module.prototype.trigger = function() {
+    var args, ref;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    (ref = $(this)).trigger.apply(ref, args);
+    return this;
+  };
+
+  Module.prototype.triggerHandler = function() {
+    var args, ref;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    return (ref = $(this)).triggerHandler.apply(ref, args);
+  };
+
+  Module.prototype._t = function() {
+    var args, ref;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    return (ref = this.constructor)._t.apply(ref, args);
+  };
+
+  Module._t = function() {
+    var args, key, ref, result;
+    key = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
+    result = ((ref = this.i18n[this.locale]) != null ? ref[key] : void 0) || '';
+    if (!(args.length > 0)) {
+      return result;
+    }
+    result = result.replace(/([^%]|^)%(?:(\d+)\$)?s/g, function(p0, p, position) {
+      if (position) {
+        return p + args[parseInt(position) - 1];
+      } else {
+        return p + args.shift();
+      }
+    });
+    return result.replace(/%%s/g, '%s');
+  };
+
+  Module.i18n = {
+    'zh-CN': {}
+  };
+
+  Module.locale = 'zh-CN';
+
+  return Module;
+
+})();
+
+return Module;
+
+}));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2 - 0
admin/static/lib/simditor/scripts/module.min.js


+ 5585 - 0
admin/static/lib/simditor/scripts/simditor.js

@@ -0,0 +1,5585 @@
+/*!
+* Simditor v2.3.6
+* http://simditor.tower.im/
+* 2015-12-21
+*/
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define('simditor', ["jquery","simple-module","simple-hotkeys","simple-uploader"], function ($, SimpleModule, simpleHotkeys, simpleUploader) {
+      return (root['Simditor'] = factory($, SimpleModule, simpleHotkeys, simpleUploader));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"),require("simple-module"),require("simple-hotkeys"),require("simple-uploader"));
+  } else {
+    root['Simditor'] = factory(jQuery,SimpleModule,simple.hotkeys,simple.uploader);
+  }
+}(this, function ($, SimpleModule, simpleHotkeys, simpleUploader) {
+
+var AlignmentButton, BlockquoteButton, BoldButton, Button, Clipboard, CodeButton, CodePopover, ColorButton, FontScaleButton, Formatter, HrButton, ImageButton, ImagePopover, IndentButton, Indentation, InputManager, ItalicButton, Keystroke, LinkButton, LinkPopover, ListButton, OrderListButton, OutdentButton, Popover, Selection, Simditor, StrikethroughButton, TableButton, TitleButton, Toolbar, UnderlineButton, UndoManager, UnorderListButton, Util,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty,
+  indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+  slice = [].slice;
+
+Selection = (function(superClass) {
+  extend(Selection, superClass);
+
+  function Selection() {
+    return Selection.__super__.constructor.apply(this, arguments);
+  }
+
+  Selection.pluginName = 'Selection';
+
+  Selection.prototype._range = null;
+
+  Selection.prototype._startNodes = null;
+
+  Selection.prototype._endNodes = null;
+
+  Selection.prototype._containerNode = null;
+
+  Selection.prototype._nodes = null;
+
+  Selection.prototype._blockNodes = null;
+
+  Selection.prototype._rootNodes = null;
+
+  Selection.prototype._init = function() {
+    this.editor = this._module;
+    this._selection = document.getSelection();
+    this.editor.on('selectionchanged', (function(_this) {
+      return function(e) {
+        _this.reset();
+        return _this._range = _this._selection.getRangeAt(0);
+      };
+    })(this));
+    return this.editor.on('blur', (function(_this) {
+      return function(e) {
+        return _this.reset();
+      };
+    })(this));
+  };
+
+  Selection.prototype.reset = function() {
+    this._range = null;
+    this._startNodes = null;
+    this._endNodes = null;
+    this._containerNode = null;
+    this._nodes = null;
+    this._blockNodes = null;
+    return this._rootNodes = null;
+  };
+
+  Selection.prototype.clear = function() {
+    var e;
+    try {
+      this._selection.removeAllRanges();
+    } catch (_error) {
+      e = _error;
+    }
+    return this.reset();
+  };
+
+  Selection.prototype.range = function(range) {
+    var ffOrIE;
+    if (range) {
+      this.clear();
+      this._selection.addRange(range);
+      this._range = range;
+      ffOrIE = this.editor.util.browser.firefox || this.editor.util.browser.msie;
+      if (!this.editor.inputManager.focused && ffOrIE) {
+        this.editor.body.focus();
+      }
+    } else if (!this._range && this.editor.inputManager.focused && this._selection.rangeCount) {
+      this._range = this._selection.getRangeAt(0);
+    }
+    return this._range;
+  };
+
+  Selection.prototype.startNodes = function() {
+    if (this._range) {
+      this._startNodes || (this._startNodes = (function(_this) {
+        return function() {
+          var startNodes;
+          startNodes = $(_this._range.startContainer).parentsUntil(_this.editor.body).get();
+          startNodes.unshift(_this._range.startContainer);
+          return $(startNodes);
+        };
+      })(this)());
+    }
+    return this._startNodes;
+  };
+
+  Selection.prototype.endNodes = function() {
+    var endNodes;
+    if (this._range) {
+      this._endNodes || (this._endNodes = this._range.collapsed ? this.startNodes() : (endNodes = $(this._range.endContainer).parentsUntil(this.editor.body).get(), endNodes.unshift(this._range.endContainer), $(endNodes)));
+    }
+    return this._endNodes;
+  };
+
+  Selection.prototype.containerNode = function() {
+    if (this._range) {
+      this._containerNode || (this._containerNode = $(this._range.commonAncestorContainer));
+    }
+    return this._containerNode;
+  };
+
+  Selection.prototype.nodes = function() {
+    if (this._range) {
+      this._nodes || (this._nodes = (function(_this) {
+        return function() {
+          var nodes;
+          nodes = [];
+          if (_this.startNodes().first().is(_this.endNodes().first())) {
+            nodes = _this.startNodes().get();
+          } else {
+            _this.startNodes().each(function(i, node) {
+              var $endNode, $node, $nodes, endIndex, index, sharedIndex, startIndex;
+              $node = $(node);
+              if (_this.endNodes().index($node) > -1) {
+                return nodes.push(node);
+              } else if ($node.parent().is(_this.editor.body) || (sharedIndex = _this.endNodes().index($node.parent())) > -1) {
+                if (sharedIndex && sharedIndex > -1) {
+                  $endNode = _this.endNodes().eq(sharedIndex - 1);
+                } else {
+                  $endNode = _this.endNodes().last();
+                }
+                $nodes = $node.parent().contents();
+                startIndex = $nodes.index($node);
+                endIndex = $nodes.index($endNode);
+                return $.merge(nodes, $nodes.slice(startIndex, endIndex).get());
+              } else {
+                $nodes = $node.parent().contents();
+                index = $nodes.index($node);
+                return $.merge(nodes, $nodes.slice(index).get());
+              }
+            });
+            _this.endNodes().each(function(i, node) {
+              var $node, $nodes, index;
+              $node = $(node);
+              if ($node.parent().is(_this.editor.body) || _this.startNodes().index($node.parent()) > -1) {
+                nodes.push(node);
+                return false;
+              } else {
+                $nodes = $node.parent().contents();
+                index = $nodes.index($node);
+                return $.merge(nodes, $nodes.slice(0, index + 1));
+              }
+            });
+          }
+          return $($.unique(nodes));
+        };
+      })(this)());
+    }
+    return this._nodes;
+  };
+
+  Selection.prototype.blockNodes = function() {
+    if (!this._range) {
+      return;
+    }
+    this._blockNodes || (this._blockNodes = (function(_this) {
+      return function() {
+        return _this.nodes().filter(function(i, node) {
+          return _this.editor.util.isBlockNode(node);
+        });
+      };
+    })(this)());
+    return this._blockNodes;
+  };
+
+  Selection.prototype.rootNodes = function() {
+    if (!this._range) {
+      return;
+    }
+    this._rootNodes || (this._rootNodes = (function(_this) {
+      return function() {
+        return _this.nodes().filter(function(i, node) {
+          var $parent;
+          $parent = $(node).parent();
+          return $parent.is(_this.editor.body) || $parent.is('blockquote');
+        });
+      };
+    })(this)());
+    return this._rootNodes;
+  };
+
+  Selection.prototype.rangeAtEndOf = function(node, range) {
+    var afterLastNode, beforeLastNode, endNode, endNodeLength, lastNodeIsBr, result;
+    if (range == null) {
+      range = this.range();
+    }
+    if (!(range && range.collapsed)) {
+      return;
+    }
+    node = $(node)[0];
+    endNode = range.endContainer;
+    endNodeLength = this.editor.util.getNodeLength(endNode);
+    beforeLastNode = range.endOffset === endNodeLength - 1;
+    lastNodeIsBr = $(endNode).contents().last().is('br');
+    afterLastNode = range.endOffset === endNodeLength;
+    if (!((beforeLastNode && lastNodeIsBr) || afterLastNode)) {
+      return false;
+    }
+    if (node === endNode) {
+      return true;
+    } else if (!$.contains(node, endNode)) {
+      return false;
+    }
+    result = true;
+    $(endNode).parentsUntil(node).addBack().each(function(i, n) {
+      var $lastChild, beforeLastbr, isLastNode, nodes;
+      nodes = $(n).parent().contents().filter(function() {
+        return !(this !== n && this.nodeType === 3 && !this.nodeValue);
+      });
+      $lastChild = nodes.last();
+      isLastNode = $lastChild.get(0) === n;
+      beforeLastbr = $lastChild.is('br') && $lastChild.prev().get(0) === n;
+      if (!(isLastNode || beforeLastbr)) {
+        result = false;
+        return false;
+      }
+    });
+    return result;
+  };
+
+  Selection.prototype.rangeAtStartOf = function(node, range) {
+    var result, startNode;
+    if (range == null) {
+      range = this.range();
+    }
+    if (!(range && range.collapsed)) {
+      return;
+    }
+    node = $(node)[0];
+    startNode = range.startContainer;
+    if (range.startOffset !== 0) {
+      return false;
+    }
+    if (node === startNode) {
+      return true;
+    } else if (!$.contains(node, startNode)) {
+      return false;
+    }
+    result = true;
+    $(startNode).parentsUntil(node).addBack().each(function(i, n) {
+      var nodes;
+      nodes = $(n).parent().contents().filter(function() {
+        return !(this !== n && this.nodeType === 3 && !this.nodeValue);
+      });
+      if (nodes.first().get(0) !== n) {
+        return result = false;
+      }
+    });
+    return result;
+  };
+
+  Selection.prototype.insertNode = function(node, range) {
+    if (range == null) {
+      range = this.range();
+    }
+    if (!range) {
+      return;
+    }
+    node = $(node)[0];
+    range.insertNode(node);
+    return this.setRangeAfter(node, range);
+  };
+
+  Selection.prototype.setRangeAfter = function(node, range) {
+    if (range == null) {
+      range = this.range();
+    }
+    if (range == null) {
+      return;
+    }
+    node = $(node)[0];
+    range.setEndAfter(node);
+    range.collapse(false);
+    return this.range(range);
+  };
+
+  Selection.prototype.setRangeBefore = function(node, range) {
+    if (range == null) {
+      range = this.range();
+    }
+    if (range == null) {
+      return;
+    }
+    node = $(node)[0];
+    range.setEndBefore(node);
+    range.collapse(false);
+    return this.range(range);
+  };
+
+  Selection.prototype.setRangeAtStartOf = function(node, range) {
+    if (range == null) {
+      range = this.range();
+    }
+    node = $(node).get(0);
+    range.setEnd(node, 0);
+    range.collapse(false);
+    return this.range(range);
+  };
+
+  Selection.prototype.setRangeAtEndOf = function(node, range) {
+    var $lastNode, $node, contents, lastChild, lastChildLength, lastText, nodeLength;
+    if (range == null) {
+      range = this.range();
+    }
+    $node = $(node);
+    node = $node[0];
+    if ($node.is('pre')) {
+      contents = $node.contents();
+      if (contents.length > 0) {
+        lastChild = contents.last();
+        lastText = lastChild.text();
+        lastChildLength = this.editor.util.getNodeLength(lastChild[0]);
+        if (lastText.charAt(lastText.length - 1) === '\n') {
+          range.setEnd(lastChild[0], lastChildLength - 1);
+        } else {
+          range.setEnd(lastChild[0], lastChildLength);
+        }
+      } else {
+        range.setEnd(node, 0);
+      }
+    } else {
+      nodeLength = this.editor.util.getNodeLength(node);
+      if (node.nodeType !== 3 && nodeLength > 0) {
+        $lastNode = $(node).contents().last();
+        if ($lastNode.is('br')) {
+          nodeLength -= 1;
+        } else if ($lastNode[0].nodeType !== 3 && this.editor.util.isEmptyNode($lastNode)) {
+          $lastNode.append(this.editor.util.phBr);
+          node = $lastNode[0];
+          nodeLength = 0;
+        }
+      }
+      range.setEnd(node, nodeLength);
+    }
+    range.collapse(false);
+    return this.range(range);
+  };
+
+  Selection.prototype.deleteRangeContents = function(range) {
+    var atEndOfBody, atStartOfBody, endRange, startRange;
+    if (range == null) {
+      range = this.range();
+    }
+    startRange = range.cloneRange();
+    endRange = range.cloneRange();
+    startRange.collapse(true);
+    endRange.collapse(false);
+    atStartOfBody = this.rangeAtStartOf(this.editor.body, startRange);
+    atEndOfBody = this.rangeAtEndOf(this.editor.body, endRange);
+    if (!range.collapsed && atStartOfBody && atEndOfBody) {
+      this.editor.body.empty();
+      range.setStart(this.editor.body[0], 0);
+      range.collapse(true);
+      this.range(range);
+    } else {
+      range.deleteContents();
+    }
+    return range;
+  };
+
+  Selection.prototype.breakBlockEl = function(el, range) {
+    var $el;
+    if (range == null) {
+      range = this.range();
+    }
+    $el = $(el);
+    if (!range.collapsed) {
+      return $el;
+    }
+    range.setStartBefore($el.get(0));
+    if (range.collapsed) {
+      return $el;
+    }
+    return $el.before(range.extractContents());
+  };
+
+  Selection.prototype.save = function(range) {
+    var endCaret, endRange, startCaret;
+    if (range == null) {
+      range = this.range();
+    }
+    if (this._selectionSaved) {
+      return;
+    }
+    endRange = range.cloneRange();
+    endRange.collapse(false);
+    startCaret = $('<span/>').addClass('simditor-caret-start');
+    endCaret = $('<span/>').addClass('simditor-caret-end');
+    endRange.insertNode(endCaret[0]);
+    range.insertNode(startCaret[0]);
+    this.clear();
+    return this._selectionSaved = true;
+  };
+
+  Selection.prototype.restore = function() {
+    var endCaret, endContainer, endOffset, range, startCaret, startContainer, startOffset;
+    if (!this._selectionSaved) {
+      return false;
+    }
+    startCaret = this.editor.body.find('.simditor-caret-start');
+    endCaret = this.editor.body.find('.simditor-caret-end');
+    if (startCaret.length && endCaret.length) {
+      startContainer = startCaret.parent();
+      startOffset = startContainer.contents().index(startCaret);
+      endContainer = endCaret.parent();
+      endOffset = endContainer.contents().index(endCaret);
+      if (startContainer[0] === endContainer[0]) {
+        endOffset -= 1;
+      }
+      range = document.createRange();
+      range.setStart(startContainer.get(0), startOffset);
+      range.setEnd(endContainer.get(0), endOffset);
+      startCaret.remove();
+      endCaret.remove();
+      this.range(range);
+    } else {
+      startCaret.remove();
+      endCaret.remove();
+    }
+    this._selectionSaved = false;
+    return range;
+  };
+
+  return Selection;
+
+})(SimpleModule);
+
+Formatter = (function(superClass) {
+  extend(Formatter, superClass);
+
+  function Formatter() {
+    return Formatter.__super__.constructor.apply(this, arguments);
+  }
+
+  Formatter.pluginName = 'Formatter';
+
+  Formatter.prototype.opts = {
+    allowedTags: [],
+    allowedAttributes: {},
+    allowedStyles: {}
+  };
+
+  Formatter.prototype._init = function() {
+    this.editor = this._module;
+    this._allowedTags = $.merge(['br', 'span', 'a', 'img', 'b', 'strong', 'i', 'strike', 'u', 'font', 'p', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'hr'], this.opts.allowedTags);
+    this._allowedAttributes = $.extend({
+      img: ['src', 'alt', 'width', 'height', 'data-non-image'],
+      a: ['href', 'target'],
+      font: ['color'],
+      code: ['class']
+    }, this.opts.allowedAttributes);
+    this._allowedStyles = $.extend({
+      span: ['color', 'font-size'],
+      b: ['color'],
+      i: ['color'],
+      strong: ['color'],
+      strike: ['color'],
+      u: ['color'],
+      p: ['margin-left', 'text-align'],
+      h1: ['margin-left', 'text-align'],
+      h2: ['margin-left', 'text-align'],
+      h3: ['margin-left', 'text-align'],
+      h4: ['margin-left', 'text-align']
+    }, this.opts.allowedStyles);
+    return this.editor.body.on('click', 'a', function(e) {
+      return false;
+    });
+  };
+
+  Formatter.prototype.decorate = function($el) {
+    if ($el == null) {
+      $el = this.editor.body;
+    }
+    this.editor.trigger('decorate', [$el]);
+    return $el;
+  };
+
+  Formatter.prototype.undecorate = function($el) {
+    if ($el == null) {
+      $el = this.editor.body.clone();
+    }
+    this.editor.trigger('undecorate', [$el]);
+    return $el;
+  };
+
+  Formatter.prototype.autolink = function($el) {
+    var $link, $node, findLinkNode, k, lastIndex, len, linkNodes, match, re, replaceEls, subStr, text, uri;
+    if ($el == null) {
+      $el = this.editor.body;
+    }
+    linkNodes = [];
+    findLinkNode = function($parentNode) {
+      return $parentNode.contents().each(function(i, node) {
+        var $node, text;
+        $node = $(node);
+        if ($node.is('a') || $node.closest('a, pre', $el).length) {
+          return;
+        }
+        if (!$node.is('iframe') && $node.contents().length) {
+          return findLinkNode($node);
+        } else if ((text = $node.text()) && /https?:\/\/|www\./ig.test(text)) {
+          return linkNodes.push($node);
+        }
+      });
+    };
+    findLinkNode($el);
+    re = /(https?:\/\/|www\.)[\w\-\.\?&=\/#%:,@\!\+]+/ig;
+    for (k = 0, len = linkNodes.length; k < len; k++) {
+      $node = linkNodes[k];
+      text = $node.text();
+      replaceEls = [];
+      match = null;
+      lastIndex = 0;
+      while ((match = re.exec(text)) !== null) {
+        subStr = text.substring(lastIndex, match.index);
+        replaceEls.push(document.createTextNode(subStr));
+        lastIndex = re.lastIndex;
+        uri = /^(http(s)?:\/\/|\/)/.test(match[0]) ? match[0] : 'http://' + match[0];
+        $link = $("<a href=\"" + uri + "\" rel=\"nofollow\"></a>").text(match[0]);
+        replaceEls.push($link[0]);
+      }
+      replaceEls.push(document.createTextNode(text.substring(lastIndex)));
+      $node.replaceWith($(replaceEls));
+    }
+    return $el;
+  };
+
+  Formatter.prototype.format = function($el) {
+    var $node, blockNode, k, l, len, len1, n, node, ref, ref1;
+    if ($el == null) {
+      $el = this.editor.body;
+    }
+    if ($el.is(':empty')) {
+      $el.append('<p>' + this.editor.util.phBr + '</p>');
+      return $el;
+    }
+    ref = $el.contents();
+    for (k = 0, len = ref.length; k < len; k++) {
+      n = ref[k];
+      this.cleanNode(n, true);
+    }
+    ref1 = $el.contents();
+    for (l = 0, len1 = ref1.length; l < len1; l++) {
+      node = ref1[l];
+      $node = $(node);
+      if ($node.is('br')) {
+        if (typeof blockNode !== "undefined" && blockNode !== null) {
+          blockNode = null;
+        }
+        $node.remove();
+      } else if (this.editor.util.isBlockNode(node)) {
+        if ($node.is('li')) {
+          if (blockNode && blockNode.is('ul, ol')) {
+            blockNode.append(node);
+          } else {
+            blockNode = $('<ul/>').insertBefore(node);
+            blockNode.append(node);
+          }
+        } else {
+          blockNode = null;
+        }
+      } else {
+        if (!blockNode || blockNode.is('ul, ol')) {
+          blockNode = $('<p/>').insertBefore(node);
+        }
+        blockNode.append(node);
+        if (this.editor.util.isEmptyNode(blockNode)) {
+          blockNode.append(this.editor.util.phBr);
+        }
+      }
+    }
+    return $el;
+  };
+
+  Formatter.prototype.cleanNode = function(node, recursive) {
+    var $blockEls, $childImg, $node, $p, $td, allowedAttributes, attr, contents, isDecoration, k, l, len, len1, n, ref, ref1, text, textNode;
+    $node = $(node);
+    if (!($node.length > 0)) {
+      return;
+    }
+    if ($node[0].nodeType === 3) {
+      text = $node.text().replace(/(\r\n|\n|\r)/gm, '');
+      if (text) {
+        textNode = document.createTextNode(text);
+        $node.replaceWith(textNode);
+      } else {
+        $node.remove();
+      }
+      return;
+    }
+    contents = $node.is('iframe') ? null : $node.contents();
+    isDecoration = this.editor.util.isDecoratedNode($node);
+    if ($node.is(this._allowedTags.join(',')) || isDecoration) {
+      if ($node.is('a') && ($childImg = $node.find('img')).length > 0) {
+        $node.replaceWith($childImg);
+        $node = $childImg;
+        contents = null;
+      }
+      if ($node.is('td') && ($blockEls = $node.find(this.editor.util.blockNodes.join(','))).length > 0) {
+        $blockEls.each((function(_this) {
+          return function(i, blockEl) {
+            return $(blockEl).contents().unwrap();
+          };
+        })(this));
+        contents = $node.contents();
+      }
+      if ($node.is('img') && $node.hasClass('uploading')) {
+        $node.remove();
+      }
+      if (!isDecoration) {
+        allowedAttributes = this._allowedAttributes[$node[0].tagName.toLowerCase()];
+        ref = $.makeArray($node[0].attributes);
+        for (k = 0, len = ref.length; k < len; k++) {
+          attr = ref[k];
+          if (attr.name === 'style') {
+            continue;
+          }
+          if (!((allowedAttributes != null) && (ref1 = attr.name, indexOf.call(allowedAttributes, ref1) >= 0))) {
+            $node.removeAttr(attr.name);
+          }
+        }
+        this._cleanNodeStyles($node);
+        if ($node.is('span') && $node[0].attributes.length === 0) {
+          $node.contents().first().unwrap();
+        }
+      }
+    } else if ($node[0].nodeType === 1 && !$node.is(':empty')) {
+      if ($node.is('div, article, dl, header, footer, tr')) {
+        $node.append('<br/>');
+        contents.first().unwrap();
+      } else if ($node.is('table')) {
+        $p = $('<p/>');
+        $node.find('tr').each(function(i, tr) {
+          return $p.append($(tr).text() + '<br/>');
+        });
+        $node.replaceWith($p);
+        contents = null;
+      } else if ($node.is('thead, tfoot')) {
+        $node.remove();
+        contents = null;
+      } else if ($node.is('th')) {
+        $td = $('<td/>').append($node.contents());
+        $node.replaceWith($td);
+      } else {
+        contents.first().unwrap();
+      }
+    } else {
+      $node.remove();
+      contents = null;
+    }
+    if (recursive && (contents != null) && !$node.is('pre')) {
+      for (l = 0, len1 = contents.length; l < len1; l++) {
+        n = contents[l];
+        this.cleanNode(n, true);
+      }
+    }
+    return null;
+  };
+
+  Formatter.prototype._cleanNodeStyles = function($node) {
+    var allowedStyles, k, len, pair, ref, ref1, style, styleStr, styles;
+    styleStr = $node.attr('style');
+    if (!styleStr) {
+      return;
+    }
+    $node.removeAttr('style');
+    allowedStyles = this._allowedStyles[$node[0].tagName.toLowerCase()];
+    if (!(allowedStyles && allowedStyles.length > 0)) {
+      return $node;
+    }
+    styles = {};
+    ref = styleStr.split(';');
+    for (k = 0, len = ref.length; k < len; k++) {
+      style = ref[k];
+      style = $.trim(style);
+      pair = style.split(':');
+      if (!(pair.length = 2)) {
+        continue;
+      }
+      if (ref1 = pair[0], indexOf.call(allowedStyles, ref1) >= 0) {
+        styles[$.trim(pair[0])] = $.trim(pair[1]);
+      }
+    }
+    if (Object.keys(styles).length > 0) {
+      $node.css(styles);
+    }
+    return $node;
+  };
+
+  Formatter.prototype.clearHtml = function(html, lineBreak) {
+    var container, contents, result;
+    if (lineBreak == null) {
+      lineBreak = true;
+    }
+    container = $('<div/>').append(html);
+    contents = container.contents();
+    result = '';
+    contents.each((function(_this) {
+      return function(i, node) {
+        var $node, children;
+        if (node.nodeType === 3) {
+          return result += node.nodeValue;
+        } else if (node.nodeType === 1) {
+          $node = $(node);
+          children = $node.is('iframe') ? null : $node.contents();
+          if (children && children.length > 0) {
+            result += _this.clearHtml(children);
+          }
+          if (lineBreak && i < contents.length - 1 && $node.is('br, p, div, li,tr, pre, address, artticle, aside, dl, figcaption, footer, h1, h2,h3, h4, header')) {
+            return result += '\n';
+          }
+        }
+      };
+    })(this));
+    return result;
+  };
+
+  Formatter.prototype.beautify = function($contents) {
+    var uselessP;
+    uselessP = function($el) {
+      return !!($el.is('p') && !$el.text() && $el.children(':not(br)').length < 1);
+    };
+    return $contents.each(function(i, el) {
+      var $el, invalid;
+      $el = $(el);
+      invalid = $el.is(':not(img, br, col, td, hr, [class^="simditor-"]):empty');
+      if (invalid || uselessP($el)) {
+        $el.remove();
+      }
+      return $el.find(':not(img, br, col, td, hr, [class^="simditor-"]):empty').remove();
+    });
+  };
+
+  return Formatter;
+
+})(SimpleModule);
+
+InputManager = (function(superClass) {
+  extend(InputManager, superClass);
+
+  function InputManager() {
+    return InputManager.__super__.constructor.apply(this, arguments);
+  }
+
+  InputManager.pluginName = 'InputManager';
+
+  InputManager.prototype._modifierKeys = [16, 17, 18, 91, 93, 224];
+
+  InputManager.prototype._arrowKeys = [37, 38, 39, 40];
+
+  InputManager.prototype._init = function() {
+    var selectAllKey, submitKey;
+    this.editor = this._module;
+    this.throttledValueChanged = this.editor.util.throttle((function(_this) {
+      return function(params) {
+        return setTimeout(function() {
+          return _this.editor.trigger('valuechanged', params);
+        }, 10);
+      };
+    })(this), 300);
+    this.throttledSelectionChanged = this.editor.util.throttle((function(_this) {
+      return function() {
+        return _this.editor.trigger('selectionchanged');
+      };
+    })(this), 50);
+    $(document).on('selectionchange.simditor' + this.editor.id, (function(_this) {
+      return function(e) {
+        var triggerEvent;
+        if (!(_this.focused && !_this.editor.clipboard.pasting)) {
+          return;
+        }
+        triggerEvent = function() {
+          if (_this._selectionTimer) {
+            clearTimeout(_this._selectionTimer);
+            _this._selectionTimer = null;
+          }
+          if (_this.editor.selection._selection.rangeCount > 0) {
+            return _this.throttledSelectionChanged();
+          } else {
+            return _this._selectionTimer = setTimeout(function() {
+              _this._selectionTimer = null;
+              if (_this.focused) {
+                return triggerEvent();
+              }
+            }, 10);
+          }
+        };
+        return triggerEvent();
+      };
+    })(this));
+    this.editor.on('valuechanged', (function(_this) {
+      return function() {
+        var $rootBlocks;
+        _this.lastCaretPosition = null;
+        $rootBlocks = _this.editor.body.children().filter(function(i, node) {
+          return _this.editor.util.isBlockNode(node);
+        });
+        if (_this.focused && $rootBlocks.length === 0) {
+          _this.editor.selection.save();
+          _this.editor.formatter.format();
+          _this.editor.selection.restore();
+        }
+        _this.editor.body.find('hr, pre, .simditor-table').each(function(i, el) {
+          var $el, formatted;
+          $el = $(el);
+          if ($el.parent().is('blockquote') || $el.parent()[0] === _this.editor.body[0]) {
+            formatted = false;
+            if ($el.next().length === 0) {
+              $('<p/>').append(_this.editor.util.phBr).insertAfter($el);
+              formatted = true;
+            }
+            if ($el.prev().length === 0) {
+              $('<p/>').append(_this.editor.util.phBr).insertBefore($el);
+              formatted = true;
+            }
+            if (formatted) {
+              return _this.throttledValueChanged();
+            }
+          }
+        });
+        _this.editor.body.find('pre:empty').append(_this.editor.util.phBr);
+        if (!_this.editor.util.support.onselectionchange && _this.focused) {
+          return _this.throttledSelectionChanged();
+        }
+      };
+    })(this));
+    this.editor.body.on('keydown', $.proxy(this._onKeyDown, this)).on('keypress', $.proxy(this._onKeyPress, this)).on('keyup', $.proxy(this._onKeyUp, this)).on('mouseup', $.proxy(this._onMouseUp, this)).on('focus', $.proxy(this._onFocus, this)).on('blur', $.proxy(this._onBlur, this)).on('drop', $.proxy(this._onDrop, this)).on('input', $.proxy(this._onInput, this));
+    if (this.editor.util.browser.firefox) {
+      this.editor.hotkeys.add('cmd+left', (function(_this) {
+        return function(e) {
+          e.preventDefault();
+          _this.editor.selection._selection.modify('move', 'backward', 'lineboundary');
+          return false;
+        };
+      })(this));
+      this.editor.hotkeys.add('cmd+right', (function(_this) {
+        return function(e) {
+          e.preventDefault();
+          _this.editor.selection._selection.modify('move', 'forward', 'lineboundary');
+          return false;
+        };
+      })(this));
+      selectAllKey = this.editor.util.os.mac ? 'cmd+a' : 'ctrl+a';
+      this.editor.hotkeys.add(selectAllKey, (function(_this) {
+        return function(e) {
+          var $children, firstBlock, lastBlock, range;
+          $children = _this.editor.body.children();
+          if (!($children.length > 0)) {
+            return;
+          }
+          firstBlock = $children.first().get(0);
+          lastBlock = $children.last().get(0);
+          range = document.createRange();
+          range.setStart(firstBlock, 0);
+          range.setEnd(lastBlock, _this.editor.util.getNodeLength(lastBlock));
+          _this.editor.selection.range(range);
+          return false;
+        };
+      })(this));
+    }
+    submitKey = this.editor.util.os.mac ? 'cmd+enter' : 'ctrl+enter';
+    return this.editor.hotkeys.add(submitKey, (function(_this) {
+      return function(e) {
+        _this.editor.el.closest('form').find('button:submit').click();
+        return false;
+      };
+    })(this));
+  };
+
+  InputManager.prototype._onFocus = function(e) {
+    if (this.editor.clipboard.pasting) {
+      return;
+    }
+    this.editor.el.addClass('focus').removeClass('error');
+    this.focused = true;
+    return setTimeout((function(_this) {
+      return function() {
+        var $blockEl, range;
+        range = _this.editor.selection._selection.getRangeAt(0);
+        if (range.startContainer === _this.editor.body[0]) {
+          if (_this.lastCaretPosition) {
+            _this.editor.undoManager.caretPosition(_this.lastCaretPosition);
+          } else {
+            $blockEl = _this.editor.body.children().first();
+            range = document.createRange();
+            _this.editor.selection.setRangeAtStartOf($blockEl, range);
+          }
+        }
+        _this.lastCaretPosition = null;
+        _this.editor.triggerHandler('focus');
+        if (!_this.editor.util.support.onselectionchange) {
+          return _this.throttledSelectionChanged();
+        }
+      };
+    })(this), 0);
+  };
+
+  InputManager.prototype._onBlur = function(e) {
+    var ref;
+    if (this.editor.clipboard.pasting) {
+      return;
+    }
+    this.editor.el.removeClass('focus');
+    this.editor.sync();
+    this.focused = false;
+    this.lastCaretPosition = (ref = this.editor.undoManager.currentState()) != null ? ref.caret : void 0;
+    return this.editor.triggerHandler('blur');
+  };
+
+  InputManager.prototype._onMouseUp = function(e) {
+    if (!this.editor.util.support.onselectionchange) {
+      return this.throttledSelectionChanged();
+    }
+  };
+
+  InputManager.prototype._onKeyDown = function(e) {
+    var ref, ref1;
+    if (this.editor.triggerHandler(e) === false) {
+      return false;
+    }
+    if (this.editor.hotkeys.respondTo(e)) {
+      return;
+    }
+    if (this.editor.keystroke.respondTo(e)) {
+      this.throttledValueChanged();
+      return false;
+    }
+    if ((ref = e.which, indexOf.call(this._modifierKeys, ref) >= 0) || (ref1 = e.which, indexOf.call(this._arrowKeys, ref1) >= 0)) {
+      return;
+    }
+    if (this.editor.util.metaKey(e) && e.which === 86) {
+      return;
+    }
+    if (!this.editor.util.support.oninput) {
+      this.throttledValueChanged(['typing']);
+    }
+    return null;
+  };
+
+  InputManager.prototype._onKeyPress = function(e) {
+    if (this.editor.triggerHandler(e) === false) {
+      return false;
+    }
+  };
+
+  InputManager.prototype._onKeyUp = function(e) {
+    var p, ref;
+    if (this.editor.triggerHandler(e) === false) {
+      return false;
+    }
+    if (!this.editor.util.support.onselectionchange && (ref = e.which, indexOf.call(this._arrowKeys, ref) >= 0)) {
+      this.throttledValueChanged();
+      return;
+    }
+    if ((e.which === 8 || e.which === 46) && this.editor.util.isEmptyNode(this.editor.body)) {
+      this.editor.body.empty();
+      p = $('<p/>').append(this.editor.util.phBr).appendTo(this.editor.body);
+      this.editor.selection.setRangeAtStartOf(p);
+    }
+  };
+
+  InputManager.prototype._onDrop = function(e) {
+    if (this.editor.triggerHandler(e) === false) {
+      return false;
+    }
+    return this.throttledValueChanged();
+  };
+
+  InputManager.prototype._onInput = function(e) {
+    return this.throttledValueChanged(['oninput']);
+  };
+
+  return InputManager;
+
+})(SimpleModule);
+
+Keystroke = (function(superClass) {
+  extend(Keystroke, superClass);
+
+  function Keystroke() {
+    return Keystroke.__super__.constructor.apply(this, arguments);
+  }
+
+  Keystroke.pluginName = 'Keystroke';
+
+  Keystroke.prototype._init = function() {
+    this.editor = this._module;
+    this._keystrokeHandlers = {};
+    return this._initKeystrokeHandlers();
+  };
+
+  Keystroke.prototype.add = function(key, node, handler) {
+    key = key.toLowerCase();
+    key = this.editor.hotkeys.constructor.aliases[key] || key;
+    if (!this._keystrokeHandlers[key]) {
+      this._keystrokeHandlers[key] = {};
+    }
+    return this._keystrokeHandlers[key][node] = handler;
+  };
+
+  Keystroke.prototype.respondTo = function(e) {
+    var base, key, ref, result;
+    key = (ref = this.editor.hotkeys.constructor.keyNameMap[e.which]) != null ? ref.toLowerCase() : void 0;
+    if (!key) {
+      return;
+    }
+    if (key in this._keystrokeHandlers) {
+      result = typeof (base = this._keystrokeHandlers[key])['*'] === "function" ? base['*'](e) : void 0;
+      if (!result) {
+        this.editor.selection.startNodes().each((function(_this) {
+          return function(i, node) {
+            var handler, ref1;
+            if (node.nodeType !== Node.ELEMENT_NODE) {
+              return;
+            }
+            handler = (ref1 = _this._keystrokeHandlers[key]) != null ? ref1[node.tagName.toLowerCase()] : void 0;
+            result = typeof handler === "function" ? handler(e, $(node)) : void 0;
+            if (result === true || result === false) {
+              return false;
+            }
+          };
+        })(this));
+      }
+      if (result) {
+        return true;
+      }
+    }
+  };
+
+  Keystroke.prototype._initKeystrokeHandlers = function() {
+    var titleEnterHandler;
+    if (this.editor.util.browser.safari) {
+      this.add('enter', '*', (function(_this) {
+        return function(e) {
+          var $blockEl, $br;
+          if (!e.shiftKey) {
+            return;
+          }
+          $blockEl = _this.editor.selection.blockNodes().last();
+          if ($blockEl.is('pre')) {
+            return;
+          }
+          $br = $('<br/>');
+          if (_this.editor.selection.rangeAtEndOf($blockEl)) {
+            _this.editor.selection.insertNode($br);
+            _this.editor.selection.insertNode($('<br/>'));
+            _this.editor.selection.setRangeBefore($br);
+          } else {
+            _this.editor.selection.insertNode($br);
+          }
+          return true;
+        };
+      })(this));
+    }
+    if (this.editor.util.browser.webkit || this.editor.util.browser.msie) {
+      titleEnterHandler = (function(_this) {
+        return function(e, $node) {
+          var $p;
+          if (!_this.editor.selection.rangeAtEndOf($node)) {
+            return;
+          }
+          $p = $('<p/>').append(_this.editor.util.phBr).insertAfter($node);
+          _this.editor.selection.setRangeAtStartOf($p);
+          return true;
+        };
+      })(this);
+      this.add('enter', 'h1', titleEnterHandler);
+      this.add('enter', 'h2', titleEnterHandler);
+      this.add('enter', 'h3', titleEnterHandler);
+      this.add('enter', 'h4', titleEnterHandler);
+      this.add('enter', 'h5', titleEnterHandler);
+      this.add('enter', 'h6', titleEnterHandler);
+    }
+    this.add('backspace', '*', (function(_this) {
+      return function(e) {
+        var $blockEl, $prevBlockEl, $rootBlock, isWebkit;
+        $rootBlock = _this.editor.selection.rootNodes().first();
+        $prevBlockEl = $rootBlock.prev();
+        if ($prevBlockEl.is('hr') && _this.editor.selection.rangeAtStartOf($rootBlock)) {
+          _this.editor.selection.save();
+          $prevBlockEl.remove();
+          _this.editor.selection.restore();
+          return true;
+        }
+        $blockEl = _this.editor.selection.blockNodes().last();
+        isWebkit = _this.editor.util.browser.webkit;
+        if (isWebkit && _this.editor.selection.rangeAtStartOf($blockEl)) {
+          _this.editor.selection.save();
+          _this.editor.formatter.cleanNode($blockEl, true);
+          _this.editor.selection.restore();
+          return null;
+        }
+      };
+    })(this));
+    this.add('enter', 'li', (function(_this) {
+      return function(e, $node) {
+        var $cloneNode, listEl, newBlockEl, newListEl;
+        $cloneNode = $node.clone();
+        $cloneNode.find('ul, ol').remove();
+        if (!(_this.editor.util.isEmptyNode($cloneNode) && $node.is(_this.editor.selection.blockNodes().last()))) {
+          return;
+        }
+        listEl = $node.parent();
+        if ($node.next('li').length > 0) {
+          if (!_this.editor.util.isEmptyNode($node)) {
+            return;
+          }
+          if (listEl.parent('li').length > 0) {
+            newBlockEl = $('<li/>').append(_this.editor.util.phBr).insertAfter(listEl.parent('li'));
+            newListEl = $('<' + listEl[0].tagName + '/>').append($node.nextAll('li'));
+            newBlockEl.append(newListEl);
+          } else {
+            newBlockEl = $('<p/>').append(_this.editor.util.phBr).insertAfter(listEl);
+            newListEl = $('<' + listEl[0].tagName + '/>').append($node.nextAll('li'));
+            newBlockEl.after(newListEl);
+          }
+        } else {
+          if (listEl.parent('li').length > 0) {
+            newBlockEl = $('<li/>').insertAfter(listEl.parent('li'));
+            if ($node.contents().length > 0) {
+              newBlockEl.append($node.contents());
+            } else {
+              newBlockEl.append(_this.editor.util.phBr);
+            }
+          } else {
+            newBlockEl = $('<p/>').append(_this.editor.util.phBr).insertAfter(listEl);
+            if ($node.children('ul, ol').length > 0) {
+              newBlockEl.after($node.children('ul, ol'));
+            }
+          }
+        }
+        if ($node.prev('li').length) {
+          $node.remove();
+        } else {
+          listEl.remove();
+        }
+        _this.editor.selection.setRangeAtStartOf(newBlockEl);
+        return true;
+      };
+    })(this));
+    this.add('enter', 'pre', (function(_this) {
+      return function(e, $node) {
+        var $p, breakNode, range;
+        e.preventDefault();
+        if (e.shiftKey) {
+          $p = $('<p/>').append(_this.editor.util.phBr).insertAfter($node);
+          _this.editor.selection.setRangeAtStartOf($p);
+          return true;
+        }
+        range = _this.editor.selection.range();
+        breakNode = null;
+        range.deleteContents();
+        if (!_this.editor.util.browser.msie && _this.editor.selection.rangeAtEndOf($node)) {
+          breakNode = document.createTextNode('\n\n');
+          range.insertNode(breakNode);
+          range.setEnd(breakNode, 1);
+        } else {
+          breakNode = document.createTextNode('\n');
+          range.insertNode(breakNode);
+          range.setStartAfter(breakNode);
+        }
+        range.collapse(false);
+        _this.editor.selection.range(range);
+        return true;
+      };
+    })(this));
+    this.add('enter', 'blockquote', (function(_this) {
+      return function(e, $node) {
+        var $closestBlock, range;
+        $closestBlock = _this.editor.selection.blockNodes().last();
+        if (!($closestBlock.is('p') && !$closestBlock.next().length && _this.editor.util.isEmptyNode($closestBlock))) {
+          return;
+        }
+        $node.after($closestBlock);
+        range = document.createRange();
+        _this.editor.selection.setRangeAtStartOf($closestBlock, range);
+        return true;
+      };
+    })(this));
+    this.add('backspace', 'li', (function(_this) {
+      return function(e, $node) {
+        var $br, $childList, $newLi, $prevChildList, $prevNode, $textNode, isFF, range, text;
+        $childList = $node.children('ul, ol');
+        $prevNode = $node.prev('li');
+        if (!($childList.length > 0 && $prevNode.length > 0)) {
+          return false;
+        }
+        text = '';
+        $textNode = null;
+        $node.contents().each(function(i, n) {
+          if (n.nodeType === 1 && /UL|OL/.test(n.nodeName)) {
+            return false;
+          }
+          if (n.nodeType === 1 && /BR/.test(n.nodeName)) {
+            return;
+          }
+          if (n.nodeType === 3 && n.nodeValue) {
+            text += n.nodeValue;
+          } else if (n.nodeType === 1) {
+            text += $(n).text();
+          }
+          return $textNode = $(n);
+        });
+        isFF = _this.editor.util.browser.firefox && !$textNode.next('br').length;
+        if ($textNode && text.length === 1 && isFF) {
+          $br = $(_this.editor.util.phBr).insertAfter($textNode);
+          $textNode.remove();
+          _this.editor.selection.setRangeBefore($br);
+          return true;
+        } else if (text.length > 0) {
+          return false;
+        }
+        range = document.createRange();
+        $prevChildList = $prevNode.children('ul, ol');
+        if ($prevChildList.length > 0) {
+          $newLi = $('<li/>').append(_this.editor.util.phBr).appendTo($prevChildList);
+          $prevChildList.append($childList.children('li'));
+          $node.remove();
+          _this.editor.selection.setRangeAtEndOf($newLi, range);
+        } else {
+          _this.editor.selection.setRangeAtEndOf($prevNode, range);
+          $prevNode.append($childList);
+          $node.remove();
+          _this.editor.selection.range(range);
+        }
+        return true;
+      };
+    })(this));
+    this.add('backspace', 'pre', (function(_this) {
+      return function(e, $node) {
+        var $newNode, codeStr, range;
+        if (!_this.editor.selection.rangeAtStartOf($node)) {
+          return;
+        }
+        codeStr = $node.html().replace('\n', '<br/>') || _this.editor.util.phBr;
+        $newNode = $('<p/>').append(codeStr).insertAfter($node);
+        $node.remove();
+        range = document.createRange();
+        _this.editor.selection.setRangeAtStartOf($newNode, range);
+        return true;
+      };
+    })(this));
+    return this.add('backspace', 'blockquote', (function(_this) {
+      return function(e, $node) {
+        var $firstChild, range;
+        if (!_this.editor.selection.rangeAtStartOf($node)) {
+          return;
+        }
+        $firstChild = $node.children().first().unwrap();
+        range = document.createRange();
+        _this.editor.selection.setRangeAtStartOf($firstChild, range);
+        return true;
+      };
+    })(this));
+  };
+
+  return Keystroke;
+
+})(SimpleModule);
+
+UndoManager = (function(superClass) {
+  extend(UndoManager, superClass);
+
+  function UndoManager() {
+    return UndoManager.__super__.constructor.apply(this, arguments);
+  }
+
+  UndoManager.pluginName = 'UndoManager';
+
+  UndoManager.prototype._index = -1;
+
+  UndoManager.prototype._capacity = 20;
+
+  UndoManager.prototype._startPosition = null;
+
+  UndoManager.prototype._endPosition = null;
+
+  UndoManager.prototype._init = function() {
+    var redoShortcut, undoShortcut;
+    this.editor = this._module;
+    this._stack = [];
+    if (this.editor.util.os.mac) {
+      undoShortcut = 'cmd+z';
+      redoShortcut = 'shift+cmd+z';
+    } else if (this.editor.util.os.win) {
+      undoShortcut = 'ctrl+z';
+      redoShortcut = 'ctrl+y';
+    } else {
+      undoShortcut = 'ctrl+z';
+      redoShortcut = 'shift+ctrl+z';
+    }
+    this.editor.hotkeys.add(undoShortcut, (function(_this) {
+      return function(e) {
+        e.preventDefault();
+        _this.undo();
+        return false;
+      };
+    })(this));
+    this.editor.hotkeys.add(redoShortcut, (function(_this) {
+      return function(e) {
+        e.preventDefault();
+        _this.redo();
+        return false;
+      };
+    })(this));
+    this.throttledPushState = this.editor.util.throttle((function(_this) {
+      return function() {
+        return _this._pushUndoState();
+      };
+    })(this), 2000);
+    this.editor.on('valuechanged', (function(_this) {
+      return function(e, src) {
+        if (src === 'undo' || src === 'redo') {
+          return;
+        }
+        return _this.throttledPushState();
+      };
+    })(this));
+    this.editor.on('selectionchanged', (function(_this) {
+      return function(e) {
+        _this.resetCaretPosition();
+        return _this.update();
+      };
+    })(this));
+    this.editor.on('focus', (function(_this) {
+      return function(e) {
+        if (_this._stack.length === 0) {
+          return _this._pushUndoState();
+        }
+      };
+    })(this));
+    return this.editor.on('blur', (function(_this) {
+      return function(e) {
+        return _this.resetCaretPosition();
+      };
+    })(this));
+  };
+
+  UndoManager.prototype.resetCaretPosition = function() {
+    this._startPosition = null;
+    return this._endPosition = null;
+  };
+
+  UndoManager.prototype.startPosition = function() {
+    if (this.editor.selection._range) {
+      this._startPosition || (this._startPosition = this._getPosition('start'));
+    }
+    return this._startPosition;
+  };
+
+  UndoManager.prototype.endPosition = function() {
+    if (this.editor.selection._range) {
+      this._endPosition || (this._endPosition = (function(_this) {
+        return function() {
+          var range;
+          range = _this.editor.selection.range();
+          if (range.collapsed) {
+            return _this._startPosition;
+          }
+          return _this._getPosition('end');
+        };
+      })(this)());
+    }
+    return this._endPosition;
+  };
+
+  UndoManager.prototype._pushUndoState = function() {
+    var caret;
+    if (this.editor.triggerHandler('pushundostate') === false) {
+      return;
+    }
+    caret = this.caretPosition();
+    if (!caret.start) {
+      return;
+    }
+    this._index += 1;
+    this._stack.length = this._index;
+    this._stack.push({
+      html: this.editor.body.html(),
+      caret: this.caretPosition()
+    });
+    if (this._stack.length > this._capacity) {
+      this._stack.shift();
+      return this._index -= 1;
+    }
+  };
+
+  UndoManager.prototype.currentState = function() {
+    if (this._stack.length && this._index > -1) {
+      return this._stack[this._index];
+    } else {
+      return null;
+    }
+  };
+
+  UndoManager.prototype.undo = function() {
+    var state;
+    if (this._index < 1 || this._stack.length < 2) {
+      return;
+    }
+    this.editor.hidePopover();
+    this._index -= 1;
+    state = this._stack[this._index];
+    this.editor.body.get(0).innerHTML = state.html;
+    this.caretPosition(state.caret);
+    this.editor.body.find('.selected').removeClass('selected');
+    this.editor.sync();
+    return this.editor.trigger('valuechanged', ['undo']);
+  };
+
+  UndoManager.prototype.redo = function() {
+    var state;
+    if (this._index < 0 || this._stack.length < this._index + 2) {
+      return;
+    }
+    this.editor.hidePopover();
+    this._index += 1;
+    state = this._stack[this._index];
+    this.editor.body.get(0).innerHTML = state.html;
+    this.caretPosition(state.caret);
+    this.editor.body.find('.selected').removeClass('selected');
+    this.editor.sync();
+    return this.editor.trigger('valuechanged', ['redo']);
+  };
+
+  UndoManager.prototype.update = function() {
+    var currentState;
+    currentState = this.currentState();
+    if (!currentState) {
+      return;
+    }
+    currentState.html = this.editor.body.html();
+    return currentState.caret = this.caretPosition();
+  };
+
+  UndoManager.prototype._getNodeOffset = function(node, index) {
+    var $parent, merging, offset;
+    if ($.isNumeric(index)) {
+      $parent = $(node);
+    } else {
+      $parent = $(node).parent();
+    }
+    offset = 0;
+    merging = false;
+    $parent.contents().each(function(i, child) {
+      if (node === child || (index === i && i === 0)) {
+        return false;
+      }
+      if (child.nodeType === Node.TEXT_NODE) {
+        if (!merging && child.nodeValue.length > 0) {
+          offset += 1;
+          merging = true;
+        }
+      } else {
+        offset += 1;
+        merging = false;
+      }
+      if (index - 1 === i) {
+        return false;
+      }
+      return null;
+    });
+    return offset;
+  };
+
+  UndoManager.prototype._getPosition = function(type) {
+    var $nodes, node, nodes, offset, position, prevNode, range;
+    if (type == null) {
+      type = 'start';
+    }
+    range = this.editor.selection.range();
+    offset = range[type + "Offset"];
+    $nodes = this.editor.selection[type + "Nodes"]();
+    node = $nodes.first()[0];
+    if (node.nodeType === Node.TEXT_NODE) {
+      prevNode = node.previousSibling;
+      while (prevNode && prevNode.nodeType === Node.TEXT_NODE) {
+        node = prevNode;
+        offset += this.editor.util.getNodeLength(prevNode);
+        prevNode = prevNode.previousSibling;
+      }
+      nodes = $nodes.get();
+      nodes[0] = node;
+      $nodes = $(nodes);
+    } else {
+      offset = this._getNodeOffset(node, offset);
+    }
+    position = [offset];
+    $nodes.each((function(_this) {
+      return function(i, node) {
+        return position.unshift(_this._getNodeOffset(node));
+      };
+    })(this));
+    return position;
+  };
+
+  UndoManager.prototype._getNodeByPosition = function(position) {
+    var child, childNodes, i, k, len, node, offset, ref;
+    node = this.editor.body[0];
+    ref = position.slice(0, position.length - 1);
+    for (i = k = 0, len = ref.length; k < len; i = ++k) {
+      offset = ref[i];
+      childNodes = node.childNodes;
+      if (offset > childNodes.length - 1) {
+        if (i === position.length - 2 && $(node).is('pre:empty')) {
+          child = document.createTextNode('');
+          node.appendChild(child);
+          childNodes = node.childNodes;
+        } else {
+          node = null;
+          break;
+        }
+      }
+      node = childNodes[offset];
+    }
+    return node;
+  };
+
+  UndoManager.prototype.caretPosition = function(caret) {
+    var endContainer, endOffset, range, startContainer, startOffset;
+    if (!caret) {
+      range = this.editor.selection.range();
+      caret = this.editor.inputManager.focused && (range != null) ? {
+        start: this.startPosition(),
+        end: this.endPosition(),
+        collapsed: range.collapsed
+      } : {};
+      return caret;
+    } else {
+      if (!caret.start) {
+        return;
+      }
+      startContainer = this._getNodeByPosition(caret.start);
+      startOffset = caret.start[caret.start.length - 1];
+      if (caret.collapsed) {
+        endContainer = startContainer;
+        endOffset = startOffset;
+      } else {
+        endContainer = this._getNodeByPosition(caret.end);
+        endOffset = caret.start[caret.start.length - 1];
+      }
+      if (!startContainer || !endContainer) {
+        if (typeof console !== "undefined" && console !== null) {
+          if (typeof console.warn === "function") {
+            console.warn('simditor: invalid caret state');
+          }
+        }
+        return;
+      }
+      range = document.createRange();
+      range.setStart(startContainer, startOffset);
+      range.setEnd(endContainer, endOffset);
+      return this.editor.selection.range(range);
+    }
+  };
+
+  return UndoManager;
+
+})(SimpleModule);
+
+Util = (function(superClass) {
+  extend(Util, superClass);
+
+  function Util() {
+    return Util.__super__.constructor.apply(this, arguments);
+  }
+
+  Util.pluginName = 'Util';
+
+  Util.prototype._init = function() {
+    this.editor = this._module;
+    if (this.browser.msie && this.browser.version < 11) {
+      return this.phBr = '';
+    }
+  };
+
+  Util.prototype.phBr = '<br/>';
+
+  Util.prototype.os = (function() {
+    var os;
+    os = {};
+    if (/Mac/.test(navigator.appVersion)) {
+      os.mac = true;
+    } else if (/Linux/.test(navigator.appVersion)) {
+      os.linux = true;
+    } else if (/Win/.test(navigator.appVersion)) {
+      os.win = true;
+    } else if (/X11/.test(navigator.appVersion)) {
+      os.unix = true;
+    }
+    if (/Mobi/.test(navigator.appVersion)) {
+      os.mobile = true;
+    }
+    return os;
+  })();
+
+  Util.prototype.browser = (function() {
+    var chrome, edge, firefox, ie, ref, ref1, ref2, ref3, ref4, safari, ua;
+    ua = navigator.userAgent;
+    ie = /(msie|trident)/i.test(ua);
+    chrome = /chrome|crios/i.test(ua);
+    safari = /safari/i.test(ua) && !chrome;
+    firefox = /firefox/i.test(ua);
+    edge = /edge/i.test(ua);
+    if (ie) {
+      return {
+        msie: true,
+        version: ((ref = ua.match(/(msie |rv:)(\d+(\.\d+)?)/i)) != null ? ref[2] : void 0) * 1
+      };
+    } else if (edge) {
+      return {
+        edge: true,
+        webkit: true,
+        version: ((ref1 = ua.match(/edge\/(\d+(\.\d+)?)/i)) != null ? ref1[1] : void 0) * 1
+      };
+    } else if (chrome) {
+      return {
+        webkit: true,
+        chrome: true,
+        version: ((ref2 = ua.match(/(?:chrome|crios)\/(\d+(\.\d+)?)/i)) != null ? ref2[1] : void 0) * 1
+      };
+    } else if (safari) {
+      return {
+        webkit: true,
+        safari: true,
+        version: ((ref3 = ua.match(/version\/(\d+(\.\d+)?)/i)) != null ? ref3[1] : void 0) * 1
+      };
+    } else if (firefox) {
+      return {
+        mozilla: true,
+        firefox: true,
+        version: ((ref4 = ua.match(/firefox\/(\d+(\.\d+)?)/i)) != null ? ref4[1] : void 0) * 1
+      };
+    } else {
+      return {};
+    }
+  })();
+
+  Util.prototype.support = (function() {
+    return {
+      onselectionchange: (function() {
+        var e, onselectionchange;
+        onselectionchange = document.onselectionchange;
+        if (onselectionchange !== void 0) {
+          try {
+            document.onselectionchange = 0;
+            return document.onselectionchange === null;
+          } catch (_error) {
+            e = _error;
+          } finally {
+            document.onselectionchange = onselectionchange;
+          }
+        }
+        return false;
+      })(),
+      oninput: (function() {
+        return !/(msie|trident)/i.test(navigator.userAgent);
+      })()
+    };
+  })();
+
+  Util.prototype.reflow = function(el) {
+    if (el == null) {
+      el = document;
+    }
+    return $(el)[0].offsetHeight;
+  };
+
+  Util.prototype.metaKey = function(e) {
+    var isMac;
+    isMac = /Mac/.test(navigator.userAgent);
+    if (isMac) {
+      return e.metaKey;
+    } else {
+      return e.ctrlKey;
+    }
+  };
+
+  Util.prototype.isEmptyNode = function(node) {
+    var $node;
+    $node = $(node);
+    return $node.is(':empty') || (!$node.text() && !$node.find(':not(br, span, div)').length);
+  };
+
+  Util.prototype.isDecoratedNode = function(node) {
+    return $(node).is('[class^="simditor-"]');
+  };
+
+  Util.prototype.blockNodes = ["div", "p", "ul", "ol", "li", "blockquote", "hr", "pre", "h1", "h2", "h3", "h4", "h5", "table"];
+
+  Util.prototype.isBlockNode = function(node) {
+    node = $(node)[0];
+    if (!node || node.nodeType === 3) {
+      return false;
+    }
+    return new RegExp("^(" + (this.blockNodes.join('|')) + ")$").test(node.nodeName.toLowerCase());
+  };
+
+  Util.prototype.getNodeLength = function(node) {
+    node = $(node)[0];
+    switch (node.nodeType) {
+      case 7:
+      case 10:
+        return 0;
+      case 3:
+      case 8:
+        return node.length;
+      default:
+        return node.childNodes.length;
+    }
+  };
+
+  Util.prototype.dataURLtoBlob = function(dataURL) {
+    var BlobBuilder, arrayBuffer, bb, blobArray, byteString, hasArrayBufferViewSupport, hasBlobConstructor, i, intArray, k, mimeString, ref, supportBlob;
+    hasBlobConstructor = window.Blob && (function() {
+      var e;
+      try {
+        return Boolean(new Blob());
+      } catch (_error) {
+        e = _error;
+        return false;
+      }
+    })();
+    hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && (function() {
+      var e;
+      try {
+        return new Blob([new Uint8Array(100)]).size === 100;
+      } catch (_error) {
+        e = _error;
+        return false;
+      }
+    })();
+    BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
+    supportBlob = hasBlobConstructor || BlobBuilder;
+    if (!(supportBlob && window.atob && window.ArrayBuffer && window.Uint8Array)) {
+      return false;
+    }
+    if (dataURL.split(',')[0].indexOf('base64') >= 0) {
+      byteString = atob(dataURL.split(',')[1]);
+    } else {
+      byteString = decodeURIComponent(dataURL.split(',')[1]);
+    }
+    arrayBuffer = new ArrayBuffer(byteString.length);
+    intArray = new Uint8Array(arrayBuffer);
+    for (i = k = 0, ref = byteString.length; 0 <= ref ? k <= ref : k >= ref; i = 0 <= ref ? ++k : --k) {
+      intArray[i] = byteString.charCodeAt(i);
+    }
+    mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];
+    if (hasBlobConstructor) {
+      blobArray = hasArrayBufferViewSupport ? intArray : arrayBuffer;
+      return new Blob([blobArray], {
+        type: mimeString
+      });
+    }
+    bb = new BlobBuilder();
+    bb.append(arrayBuffer);
+    return bb.getBlob(mimeString);
+  };
+
+  Util.prototype.throttle = function(func, wait) {
+    var args, call, ctx, last, rtn, throttled, timeoutID;
+    last = 0;
+    timeoutID = 0;
+    ctx = args = rtn = null;
+    call = function() {
+      timeoutID = 0;
+      last = +new Date();
+      rtn = func.apply(ctx, args);
+      ctx = null;
+      return args = null;
+    };
+    throttled = function() {
+      var delta;
+      ctx = this;
+      args = arguments;
+      delta = new Date() - last;
+      if (!timeoutID) {
+        if (delta >= wait) {
+          call();
+        } else {
+          timeoutID = setTimeout(call, wait - delta);
+        }
+      }
+      return rtn;
+    };
+    throttled.clear = function() {
+      if (!timeoutID) {
+        return;
+      }
+      clearTimeout(timeoutID);
+      return call();
+    };
+    return throttled;
+  };
+
+  Util.prototype.formatHTML = function(html) {
+    var cursor, indentString, lastMatch, level, match, re, repeatString, result, str;
+    re = /<(\/?)(.+?)(\/?)>/g;
+    result = '';
+    level = 0;
+    lastMatch = null;
+    indentString = '  ';
+    repeatString = function(str, n) {
+      return new Array(n + 1).join(str);
+    };
+    while ((match = re.exec(html)) !== null) {
+      match.isBlockNode = $.inArray(match[2], this.blockNodes) > -1;
+      match.isStartTag = match[1] !== '/' && match[3] !== '/';
+      match.isEndTag = match[1] === '/' || match[3] === '/';
+      cursor = lastMatch ? lastMatch.index + lastMatch[0].length : 0;
+      if ((str = html.substring(cursor, match.index)).length > 0 && $.trim(str)) {
+        result += str;
+      }
+      if (match.isBlockNode && match.isEndTag && !match.isStartTag) {
+        level -= 1;
+      }
+      if (match.isBlockNode && match.isStartTag) {
+        if (!(lastMatch && lastMatch.isBlockNode && lastMatch.isEndTag)) {
+          result += '\n';
+        }
+        result += repeatString(indentString, level);
+      }
+      result += match[0];
+      if (match.isBlockNode && match.isEndTag) {
+        result += '\n';
+      }
+      if (match.isBlockNode && match.isStartTag) {
+        level += 1;
+      }
+      lastMatch = match;
+    }
+    return $.trim(result);
+  };
+
+  return Util;
+
+})(SimpleModule);
+
+Toolbar = (function(superClass) {
+  extend(Toolbar, superClass);
+
+  function Toolbar() {
+    return Toolbar.__super__.constructor.apply(this, arguments);
+  }
+
+  Toolbar.pluginName = 'Toolbar';
+
+  Toolbar.prototype.opts = {
+    toolbar: true,
+    toolbarFloat: true,
+    toolbarHidden: false,
+    toolbarFloatOffset: 0
+  };
+
+  Toolbar.prototype._tpl = {
+    wrapper: '<div class="simditor-toolbar"><ul></ul></div>',
+    separator: '<li><span class="separator"></span></li>'
+  };
+
+  Toolbar.prototype._init = function() {
+    var floatInitialized, initToolbarFloat, toolbarHeight;
+    this.editor = this._module;
+    if (!this.opts.toolbar) {
+      return;
+    }
+    if (!$.isArray(this.opts.toolbar)) {
+      this.opts.toolbar = ['bold', 'italic', 'underline', 'strikethrough', '|', 'ol', 'ul', 'blockquote', 'code', '|', 'link', 'image', '|', 'indent', 'outdent'];
+    }
+    this._render();
+    this.list.on('click', function(e) {
+      return false;
+    });
+    this.wrapper.on('mousedown', (function(_this) {
+      return function(e) {
+        return _this.list.find('.menu-on').removeClass('.menu-on');
+      };
+    })(this));
+    $(document).on('mousedown.simditor' + this.editor.id, (function(_this) {
+      return function(e) {
+        return _this.list.find('.menu-on').removeClass('.menu-on');
+      };
+    })(this));
+    if (!this.opts.toolbarHidden && this.opts.toolbarFloat) {
+      this.wrapper.css('top', this.opts.toolbarFloatOffset);
+      toolbarHeight = 0;
+      initToolbarFloat = (function(_this) {
+        return function() {
+          _this.wrapper.css('position', 'static');
+          _this.wrapper.width('auto');
+          _this.editor.util.reflow(_this.wrapper);
+          _this.wrapper.width(_this.wrapper.outerWidth());
+          _this.wrapper.css('left', _this.editor.util.os.mobile ? _this.wrapper.position().left : _this.wrapper.offset().left);
+          _this.wrapper.css('position', '');
+          toolbarHeight = _this.wrapper.outerHeight();
+          _this.editor.placeholderEl.css('top', toolbarHeight);
+          return true;
+        };
+      })(this);
+      floatInitialized = null;
+      $(window).on('resize.simditor-' + this.editor.id, function(e) {
+        return floatInitialized = initToolbarFloat();
+      });
+      $(window).on('scroll.simditor-' + this.editor.id, (function(_this) {
+        return function(e) {
+          var bottomEdge, scrollTop, topEdge;
+          if (!_this.wrapper.is(':visible')) {
+            return;
+          }
+          topEdge = _this.editor.wrapper.offset().top;
+          bottomEdge = topEdge + _this.editor.wrapper.outerHeight() - 80;
+          scrollTop = $(document).scrollTop() + _this.opts.toolbarFloatOffset;
+          if (scrollTop <= topEdge || scrollTop >= bottomEdge) {
+            _this.editor.wrapper.removeClass('toolbar-floating').css('padding-top', '');
+            if (_this.editor.util.os.mobile) {
+              return _this.wrapper.css('top', _this.opts.toolbarFloatOffset);
+            }
+          } else {
+            floatInitialized || (floatInitialized = initToolbarFloat());
+            _this.editor.wrapper.addClass('toolbar-floating').css('padding-top', toolbarHeight);
+            if (_this.editor.util.os.mobile) {
+              return _this.wrapper.css('top', scrollTop - topEdge + _this.opts.toolbarFloatOffset);
+            }
+          }
+        };
+      })(this));
+    }
+    this.editor.on('destroy', (function(_this) {
+      return function() {
+        return _this.buttons.length = 0;
+      };
+    })(this));
+    return $(document).on("mousedown.simditor-" + this.editor.id, (function(_this) {
+      return function(e) {
+        return _this.list.find('li.menu-on').removeClass('menu-on');
+      };
+    })(this));
+  };
+
+  Toolbar.prototype._render = function() {
+    var k, len, name, ref;
+    this.buttons = [];
+    this.wrapper = $(this._tpl.wrapper).prependTo(this.editor.wrapper);
+    this.list = this.wrapper.find('ul');
+    ref = this.opts.toolbar;
+    for (k = 0, len = ref.length; k < len; k++) {
+      name = ref[k];
+      if (name === '|') {
+        $(this._tpl.separator).appendTo(this.list);
+        continue;
+      }
+      if (!this.constructor.buttons[name]) {
+        throw new Error("simditor: invalid toolbar button " + name);
+        continue;
+      }
+      this.buttons.push(new this.constructor.buttons[name]({
+        editor: this.editor
+      }));
+    }
+    if (this.opts.toolbarHidden) {
+      return this.wrapper.hide();
+    }
+  };
+
+  Toolbar.prototype.findButton = function(name) {
+    var button;
+    button = this.list.find('.toolbar-item-' + name).data('button');
+    return button != null ? button : null;
+  };
+
+  Toolbar.addButton = function(btn) {
+    return this.buttons[btn.prototype.name] = btn;
+  };
+
+  Toolbar.buttons = {};
+
+  return Toolbar;
+
+})(SimpleModule);
+
+Indentation = (function(superClass) {
+  extend(Indentation, superClass);
+
+  function Indentation() {
+    return Indentation.__super__.constructor.apply(this, arguments);
+  }
+
+  Indentation.pluginName = 'Indentation';
+
+  Indentation.prototype.opts = {
+    tabIndent: true
+  };
+
+  Indentation.prototype._init = function() {
+    this.editor = this._module;
+    return this.editor.keystroke.add('tab', '*', (function(_this) {
+      return function(e) {
+        var codeButton;
+        codeButton = _this.editor.toolbar.findButton('code');
+        if (!(_this.opts.tabIndent || (codeButton && codeButton.active))) {
+          return;
+        }
+        return _this.indent(e.shiftKey);
+      };
+    })(this));
+  };
+
+  Indentation.prototype.indent = function(isBackward) {
+    var $blockNodes, $endNodes, $startNodes, nodes, result;
+    $startNodes = this.editor.selection.startNodes();
+    $endNodes = this.editor.selection.endNodes();
+    $blockNodes = this.editor.selection.blockNodes();
+    nodes = [];
+    $blockNodes = $blockNodes.each(function(i, node) {
+      var include, j, k, len, n;
+      include = true;
+      for (j = k = 0, len = nodes.length; k < len; j = ++k) {
+        n = nodes[j];
+        if ($.contains(node, n)) {
+          include = false;
+          break;
+        } else if ($.contains(n, node)) {
+          nodes.splice(j, 1, node);
+          include = false;
+          break;
+        }
+      }
+      if (include) {
+        return nodes.push(node);
+      }
+    });
+    $blockNodes = $(nodes);
+    result = false;
+    $blockNodes.each((function(_this) {
+      return function(i, blockEl) {
+        var r;
+        r = isBackward ? _this.outdentBlock(blockEl) : _this.indentBlock(blockEl);
+        if (r) {
+          return result = r;
+        }
+      };
+    })(this));
+    return result;
+  };
+
+  Indentation.prototype.indentBlock = function(blockEl) {
+    var $blockEl, $childList, $nextTd, $nextTr, $parentLi, $pre, $td, $tr, marginLeft, tagName;
+    $blockEl = $(blockEl);
+    if (!$blockEl.length) {
+      return;
+    }
+    if ($blockEl.is('pre')) {
+      $pre = this.editor.selection.containerNode();
+      if (!($pre.is($blockEl) || $pre.closest('pre').is($blockEl))) {
+        return;
+      }
+      this.indentText(this.editor.selection.range());
+    } else if ($blockEl.is('li')) {
+      $parentLi = $blockEl.prev('li');
+      if ($parentLi.length < 1) {
+        return;
+      }
+      this.editor.selection.save();
+      tagName = $blockEl.parent()[0].tagName;
+      $childList = $parentLi.children('ul, ol');
+      if ($childList.length > 0) {
+        $childList.append($blockEl);
+      } else {
+        $('<' + tagName + '/>').append($blockEl).appendTo($parentLi);
+      }
+      this.editor.selection.restore();
+    } else if ($blockEl.is('p, h1, h2, h3, h4')) {
+      marginLeft = parseInt($blockEl.css('margin-left')) || 0;
+      marginLeft = (Math.round(marginLeft / this.opts.indentWidth) + 1) * this.opts.indentWidth;
+      $blockEl.css('margin-left', marginLeft);
+    } else if ($blockEl.is('table') || $blockEl.is('.simditor-table')) {
+      $td = this.editor.selection.containerNode().closest('td, th');
+      $nextTd = $td.next('td, th');
+      if (!($nextTd.length > 0)) {
+        $tr = $td.parent('tr');
+        $nextTr = $tr.next('tr');
+        if ($nextTr.length < 1 && $tr.parent().is('thead')) {
+          $nextTr = $tr.parent('thead').next('tbody').find('tr:first');
+        }
+        $nextTd = $nextTr.find('td:first, th:first');
+      }
+      if (!($td.length > 0 && $nextTd.length > 0)) {
+        return;
+      }
+      this.editor.selection.setRangeAtEndOf($nextTd);
+    } else {
+      return false;
+    }
+    return true;
+  };
+
+  Indentation.prototype.indentText = function(range) {
+    var text, textNode;
+    text = range.toString().replace(/^(?=.+)/mg, '\u00A0\u00A0');
+    textNode = document.createTextNode(text || '\u00A0\u00A0');
+    range.deleteContents();
+    range.insertNode(textNode);
+    if (text) {
+      range.selectNode(textNode);
+      return this.editor.selection.range(range);
+    } else {
+      return this.editor.selection.setRangeAfter(textNode);
+    }
+  };
+
+  Indentation.prototype.outdentBlock = function(blockEl) {
+    var $blockEl, $parent, $parentLi, $pre, $prevTd, $prevTr, $td, $tr, marginLeft, range;
+    $blockEl = $(blockEl);
+    if (!($blockEl && $blockEl.length > 0)) {
+      return;
+    }
+    if ($blockEl.is('pre')) {
+      $pre = this.editor.selection.containerNode();
+      if (!($pre.is($blockEl) || $pre.closest('pre').is($blockEl))) {
+        return;
+      }
+      this.outdentText(range);
+    } else if ($blockEl.is('li')) {
+      $parent = $blockEl.parent();
+      $parentLi = $parent.parent('li');
+      this.editor.selection.save();
+      if ($parentLi.length < 1) {
+        range = document.createRange();
+        range.setStartBefore($parent[0]);
+        range.setEndBefore($blockEl[0]);
+        $parent.before(range.extractContents());
+        $('<p/>').insertBefore($parent).after($blockEl.children('ul, ol')).append($blockEl.contents());
+        $blockEl.remove();
+      } else {
+        if ($blockEl.next('li').length > 0) {
+          $('<' + $parent[0].tagName + '/>').append($blockEl.nextAll('li')).appendTo($blockEl);
+        }
+        $blockEl.insertAfter($parentLi);
+        if ($parent.children('li').length < 1) {
+          $parent.remove();
+        }
+      }
+      this.editor.selection.restore();
+    } else if ($blockEl.is('p, h1, h2, h3, h4')) {
+      marginLeft = parseInt($blockEl.css('margin-left')) || 0;
+      marginLeft = Math.max(Math.round(marginLeft / this.opts.indentWidth) - 1, 0) * this.opts.indentWidth;
+      $blockEl.css('margin-left', marginLeft === 0 ? '' : marginLeft);
+    } else if ($blockEl.is('table') || $blockEl.is('.simditor-table')) {
+      $td = this.editor.selection.containerNode().closest('td, th');
+      $prevTd = $td.prev('td, th');
+      if (!($prevTd.length > 0)) {
+        $tr = $td.parent('tr');
+        $prevTr = $tr.prev('tr');
+        if ($prevTr.length < 1 && $tr.parent().is('tbody')) {
+          $prevTr = $tr.parent('tbody').prev('thead').find('tr:first');
+        }
+        $prevTd = $prevTr.find('td:last, th:last');
+      }
+      if (!($td.length > 0 && $prevTd.length > 0)) {
+        return;
+      }
+      this.editor.selection.setRangeAtEndOf($prevTd);
+    } else {
+      return false;
+    }
+    return true;
+  };
+
+  Indentation.prototype.outdentText = function(range) {};
+
+  return Indentation;
+
+})(SimpleModule);
+
+Clipboard = (function(superClass) {
+  extend(Clipboard, superClass);
+
+  function Clipboard() {
+    return Clipboard.__super__.constructor.apply(this, arguments);
+  }
+
+  Clipboard.pluginName = 'Clipboard';
+
+  Clipboard.prototype.opts = {
+    pasteImage: false,
+    cleanPaste: false
+  };
+
+  Clipboard.prototype._init = function() {
+    this.editor = this._module;
+    if (this.opts.pasteImage && typeof this.opts.pasteImage !== 'string') {
+      this.opts.pasteImage = 'inline';
+    }
+    return this.editor.body.on('paste', (function(_this) {
+      return function(e) {
+        var range;
+        if (_this.pasting || _this._pasteBin) {
+          return;
+        }
+        if (_this.editor.triggerHandler(e) === false) {
+          return false;
+        }
+        range = _this.editor.selection.deleteRangeContents();
+        if (_this.editor.body.html()) {
+          if (!range.collapsed) {
+            range.collapse(true);
+          }
+        } else {
+          _this.editor.formatter.format();
+          _this.editor.selection.setRangeAtStartOf(_this.editor.body.find('p:first'));
+        }
+        if (_this._processPasteByClipboardApi(e)) {
+          return false;
+        }
+        _this.editor.inputManager.throttledValueChanged.clear();
+        _this.editor.inputManager.throttledSelectionChanged.clear();
+        _this.editor.undoManager.throttledPushState.clear();
+        _this.editor.selection.reset();
+        _this.editor.undoManager.resetCaretPosition();
+        _this.pasting = true;
+        return _this._getPasteContent(function(pasteContent) {
+          _this._processPasteContent(pasteContent);
+          _this._pasteInBlockEl = null;
+          _this._pastePlainText = null;
+          return _this.pasting = false;
+        });
+      };
+    })(this));
+  };
+
+  Clipboard.prototype._processPasteByClipboardApi = function(e) {
+    var imageFile, pasteItem, ref, uploadOpt;
+    if (this.editor.util.browser.edge) {
+      return;
+    }
+    if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.items && e.originalEvent.clipboardData.items.length > 0) {
+      pasteItem = e.originalEvent.clipboardData.items[0];
+      if (/^image\//.test(pasteItem.type)) {
+        imageFile = pasteItem.getAsFile();
+        if (!((imageFile != null) && this.opts.pasteImage)) {
+          return;
+        }
+        if (!imageFile.name) {
+          imageFile.name = "Clipboard Image.png";
+        }
+        if (this.editor.triggerHandler('pasting', [imageFile]) === false) {
+          return;
+        }
+        uploadOpt = {};
+        uploadOpt[this.opts.pasteImage] = true;
+        if ((ref = this.editor.uploader) != null) {
+          ref.upload(imageFile, uploadOpt);
+        }
+        return true;
+      }
+    }
+  };
+
+  Clipboard.prototype._getPasteContent = function(callback) {
+    var state;
+    this._pasteBin = $('<div contenteditable="true" />').addClass('simditor-paste-bin').attr('tabIndex', '-1').appendTo(this.editor.el);
+    state = {
+      html: this.editor.body.html(),
+      caret: this.editor.undoManager.caretPosition()
+    };
+    this._pasteBin.focus();
+    return setTimeout((function(_this) {
+      return function() {
+        var pasteContent;
+        _this.editor.hidePopover();
+        _this.editor.body.get(0).innerHTML = state.html;
+        _this.editor.undoManager.caretPosition(state.caret);
+        _this.editor.body.focus();
+        _this.editor.selection.reset();
+        _this.editor.selection.range();
+        _this._pasteInBlockEl = _this.editor.selection.blockNodes().last();
+        _this._pastePlainText = _this.opts.cleanPaste || _this._pasteInBlockEl.is('pre, table');
+        if (_this._pastePlainText) {
+          pasteContent = _this.editor.formatter.clearHtml(_this._pasteBin.html(), true);
+        } else {
+          pasteContent = $('<div/>').append(_this._pasteBin.contents());
+          pasteContent.find('table colgroup').remove();
+          _this.editor.formatter.format(pasteContent);
+          _this.editor.formatter.decorate(pasteContent);
+          _this.editor.formatter.beautify(pasteContent.children());
+          pasteContent = pasteContent.contents();
+        }
+        _this._pasteBin.remove();
+        _this._pasteBin = null;
+        return callback(pasteContent);
+      };
+    })(this), 0);
+  };
+
+  Clipboard.prototype._processPasteContent = function(pasteContent) {
+    var $blockEl, $img, blob, children, insertPosition, k, l, lastLine, len, len1, len2, len3, len4, line, lines, m, node, o, q, ref, ref1, ref2, uploadOpt;
+    if (this.editor.triggerHandler('pasting', [pasteContent]) === false) {
+      return;
+    }
+    $blockEl = this._pasteInBlockEl;
+    if (!pasteContent) {
+      return;
+    } else if (this._pastePlainText) {
+      if ($blockEl.is('table')) {
+        lines = pasteContent.split('\n');
+        lastLine = lines.pop();
+        for (k = 0, len = lines.length; k < len; k++) {
+          line = lines[k];
+          this.editor.selection.insertNode(document.createTextNode(line));
+          this.editor.selection.insertNode($('<br/>'));
+        }
+        this.editor.selection.insertNode(document.createTextNode(lastLine));
+      } else {
+        pasteContent = $('<div/>').text(pasteContent);
+        ref = pasteContent.contents();
+        for (l = 0, len1 = ref.length; l < len1; l++) {
+          node = ref[l];
+          this.editor.selection.insertNode($(node)[0]);
+        }
+      }
+    } else if ($blockEl.is(this.editor.body)) {
+      for (m = 0, len2 = pasteContent.length; m < len2; m++) {
+        node = pasteContent[m];
+        this.editor.selection.insertNode(node);
+      }
+    } else if (pasteContent.length < 1) {
+      return;
+    } else if (pasteContent.length === 1) {
+      if (pasteContent.is('p')) {
+        children = pasteContent.contents();
+        if (children.length === 1 && children.is('img')) {
+          $img = children;
+          if (/^data:image/.test($img.attr('src'))) {
+            if (!this.opts.pasteImage) {
+              return;
+            }
+            blob = this.editor.util.dataURLtoBlob($img.attr("src"));
+            blob.name = "Clipboard Image.png";
+            uploadOpt = {};
+            uploadOpt[this.opts.pasteImage] = true;
+            if ((ref1 = this.editor.uploader) != null) {
+              ref1.upload(blob, uploadOpt);
+            }
+            return;
+          } else if ($img.is('img[src^="webkit-fake-url://"]')) {
+            return;
+          }
+        }
+        for (o = 0, len3 = children.length; o < len3; o++) {
+          node = children[o];
+          this.editor.selection.insertNode(node);
+        }
+      } else if ($blockEl.is('p') && this.editor.util.isEmptyNode($blockEl)) {
+        $blockEl.replaceWith(pasteContent);
+        this.editor.selection.setRangeAtEndOf(pasteContent);
+      } else if (pasteContent.is('ul, ol')) {
+        if (pasteContent.find('li').length === 1) {
+          pasteContent = $('<div/>').text(pasteContent.text());
+          ref2 = pasteContent.contents();
+          for (q = 0, len4 = ref2.length; q < len4; q++) {
+            node = ref2[q];
+            this.editor.selection.insertNode($(node)[0]);
+          }
+        } else if ($blockEl.is('li')) {
+          $blockEl.parent().after(pasteContent);
+          this.editor.selection.setRangeAtEndOf(pasteContent);
+        } else {
+          $blockEl.after(pasteContent);
+          this.editor.selection.setRangeAtEndOf(pasteContent);
+        }
+      } else {
+        $blockEl.after(pasteContent);
+        this.editor.selection.setRangeAtEndOf(pasteContent);
+      }
+    } else {
+      if ($blockEl.is('li')) {
+        $blockEl = $blockEl.parent();
+      }
+      if (this.editor.selection.rangeAtStartOf($blockEl)) {
+        insertPosition = 'before';
+      } else if (this.editor.selection.rangeAtEndOf($blockEl)) {
+        insertPosition = 'after';
+      } else {
+        this.editor.selection.breakBlockEl($blockEl);
+        insertPosition = 'before';
+      }
+      $blockEl[insertPosition](pasteContent);
+      this.editor.selection.setRangeAtEndOf(pasteContent.last());
+    }
+    return this.editor.inputManager.throttledValueChanged();
+  };
+
+  return Clipboard;
+
+})(SimpleModule);
+
+Simditor = (function(superClass) {
+  extend(Simditor, superClass);
+
+  function Simditor() {
+    return Simditor.__super__.constructor.apply(this, arguments);
+  }
+
+  Simditor.connect(Util);
+
+  Simditor.connect(InputManager);
+
+  Simditor.connect(Selection);
+
+  Simditor.connect(UndoManager);
+
+  Simditor.connect(Keystroke);
+
+  Simditor.connect(Formatter);
+
+  Simditor.connect(Toolbar);
+
+  Simditor.connect(Indentation);
+
+  Simditor.connect(Clipboard);
+
+  Simditor.count = 0;
+
+  Simditor.prototype.opts = {
+    textarea: null,
+    placeholder: '',
+    defaultImage: 'images/image.png',
+    params: {},
+    upload: false,
+    indentWidth: 40
+  };
+
+  Simditor.prototype._init = function() {
+    var e, editor, form, uploadOpts;
+    this.textarea = $(this.opts.textarea);
+    this.opts.placeholder = this.opts.placeholder || this.textarea.attr('placeholder');
+    if (!this.textarea.length) {
+      throw new Error('simditor: param textarea is required.');
+      return;
+    }
+    editor = this.textarea.data('simditor');
+    if (editor != null) {
+      editor.destroy();
+    }
+    this.id = ++Simditor.count;
+    this._render();
+    if (simpleHotkeys) {
+      this.hotkeys = simpleHotkeys({
+        el: this.body
+      });
+    } else {
+      throw new Error('simditor: simple-hotkeys is required.');
+      return;
+    }
+    if (this.opts.upload && simpleUploader) {
+      uploadOpts = typeof this.opts.upload === 'object' ? this.opts.upload : {};
+      this.uploader = simpleUploader(uploadOpts);
+    }
+    form = this.textarea.closest('form');
+    if (form.length) {
+      form.on('submit.simditor-' + this.id, (function(_this) {
+        return function() {
+          return _this.sync();
+        };
+      })(this));
+      form.on('reset.simditor-' + this.id, (function(_this) {
+        return function() {
+          return _this.setValue('');
+        };
+      })(this));
+    }
+    this.on('initialized', (function(_this) {
+      return function() {
+        if (_this.opts.placeholder) {
+          _this.on('valuechanged', function() {
+            return _this._placeholder();
+          });
+        }
+        _this.setValue(_this.textarea.val().trim() || '');
+        if (_this.textarea.attr('autofocus')) {
+          return _this.focus();
+        }
+      };
+    })(this));
+    if (this.util.browser.mozilla) {
+      this.util.reflow();
+      try {
+        document.execCommand('enableObjectResizing', false, false);
+        return document.execCommand('enableInlineTableEditing', false, false);
+      } catch (_error) {
+        e = _error;
+      }
+    }
+  };
+
+  Simditor.prototype._tpl = "<div class=\"simditor\">\n  <div class=\"simditor-wrapper\">\n    <div class=\"simditor-placeholder\"></div>\n    <div class=\"simditor-body\" contenteditable=\"true\">\n    </div>\n  </div>\n</div>";
+
+  Simditor.prototype._render = function() {
+    var key, ref, results, val;
+    this.el = $(this._tpl).insertBefore(this.textarea);
+    this.wrapper = this.el.find('.simditor-wrapper');
+    this.body = this.wrapper.find('.simditor-body');
+    this.placeholderEl = this.wrapper.find('.simditor-placeholder').append(this.opts.placeholder);
+    this.el.data('simditor', this);
+    this.wrapper.append(this.textarea);
+    this.textarea.data('simditor', this).blur();
+    this.body.attr('tabindex', this.textarea.attr('tabindex'));
+    if (this.util.os.mac) {
+      this.el.addClass('simditor-mac');
+    } else if (this.util.os.linux) {
+      this.el.addClass('simditor-linux');
+    }
+    if (this.util.os.mobile) {
+      this.el.addClass('simditor-mobile');
+    }
+    if (this.opts.params) {
+      ref = this.opts.params;
+      results = [];
+      for (key in ref) {
+        val = ref[key];
+        results.push($('<input/>', {
+          type: 'hidden',
+          name: key,
+          value: val
+        }).insertAfter(this.textarea));
+      }
+      return results;
+    }
+  };
+
+  Simditor.prototype._placeholder = function() {
+    var children;
+    children = this.body.children();
+    if (children.length === 0 || (children.length === 1 && this.util.isEmptyNode(children) && parseInt(children.css('margin-left') || 0) < this.opts.indentWidth)) {
+      return this.placeholderEl.show();
+    } else {
+      return this.placeholderEl.hide();
+    }
+  };
+
+  Simditor.prototype.setValue = function(val) {
+    this.hidePopover();
+    this.textarea.val(val);
+    this.body.get(0).innerHTML = val;
+    this.formatter.format();
+    this.formatter.decorate();
+    this.util.reflow(this.body);
+    this.inputManager.lastCaretPosition = null;
+    return this.trigger('valuechanged');
+  };
+
+  Simditor.prototype.getValue = function() {
+    return this.sync();
+  };
+
+  Simditor.prototype.sync = function() {
+    var children, cloneBody, emptyP, firstP, lastP, val;
+    cloneBody = this.body.clone();
+    this.formatter.undecorate(cloneBody);
+    this.formatter.format(cloneBody);
+    this.formatter.autolink(cloneBody);
+    children = cloneBody.children();
+    lastP = children.last('p');
+    firstP = children.first('p');
+    while (lastP.is('p') && this.util.isEmptyNode(lastP)) {
+      emptyP = lastP;
+      lastP = lastP.prev('p');
+      emptyP.remove();
+    }
+    while (firstP.is('p') && this.util.isEmptyNode(firstP)) {
+      emptyP = firstP;
+      firstP = lastP.next('p');
+      emptyP.remove();
+    }
+    cloneBody.find('img.uploading').remove();
+    val = $.trim(cloneBody.html());
+    this.textarea.val(val);
+    return val;
+  };
+
+  Simditor.prototype.focus = function() {
+    var $blockEl, range;
+    if (!(this.body.is(':visible') && this.body.is('[contenteditable]'))) {
+      this.el.find('textarea:visible').focus();
+      return;
+    }
+    if (this.inputManager.lastCaretPosition) {
+      this.undoManager.caretPosition(this.inputManager.lastCaretPosition);
+      return this.inputManager.lastCaretPosition = null;
+    } else {
+      $blockEl = this.body.children().last();
+      if (!$blockEl.is('p')) {
+        $blockEl = $('<p/>').append(this.util.phBr).appendTo(this.body);
+      }
+      range = document.createRange();
+      return this.selection.setRangeAtEndOf($blockEl, range);
+    }
+  };
+
+  Simditor.prototype.blur = function() {
+    if (this.body.is(':visible') && this.body.is('[contenteditable]')) {
+      return this.body.blur();
+    } else {
+      return this.body.find('textarea:visible').blur();
+    }
+  };
+
+  Simditor.prototype.hidePopover = function() {
+    return this.el.find('.simditor-popover').each(function(i, popover) {
+      popover = $(popover).data('popover');
+      if (popover.active) {
+        return popover.hide();
+      }
+    });
+  };
+
+  Simditor.prototype.destroy = function() {
+    this.triggerHandler('destroy');
+    this.textarea.closest('form').off('.simditor .simditor-' + this.id);
+    this.selection.clear();
+    this.inputManager.focused = false;
+    this.textarea.insertBefore(this.el).hide().val('').removeData('simditor');
+    this.el.remove();
+    $(document).off('.simditor-' + this.id);
+    $(window).off('.simditor-' + this.id);
+    return this.off();
+  };
+
+  return Simditor;
+
+})(SimpleModule);
+
+Simditor.i18n = {
+  'zh-CN': {
+    'blockquote': '引用',
+    'bold': '加粗文字',
+    'code': '插入代码',
+    'color': '文字颜色',
+    'coloredText': '彩色文字',
+    'hr': '分隔线',
+    'image': '插入图片',
+    'externalImage': '外链图片',
+    'uploadImage': '上传图片',
+    'uploadFailed': '上传失败了',
+    'uploadError': '上传出错了',
+    'imageUrl': '图片地址',
+    'imageSize': '图片尺寸',
+    'imageAlt': '图片描述',
+    'restoreImageSize': '还原图片尺寸',
+    'uploading': '正在上传',
+    'indent': '向右缩进',
+    'outdent': '向左缩进',
+    'italic': '斜体文字',
+    'link': '插入链接',
+    'linkText': '链接文字',
+    'linkUrl': '链接地址',
+    'linkTarget': '打开方式',
+    'openLinkInCurrentWindow': '在新窗口中打开',
+    'openLinkInNewWindow': '在当前窗口中打开',
+    'removeLink': '移除链接',
+    'ol': '有序列表',
+    'ul': '无序列表',
+    'strikethrough': '删除线文字',
+    'table': '表格',
+    'deleteRow': '删除行',
+    'insertRowAbove': '在上面插入行',
+    'insertRowBelow': '在下面插入行',
+    'deleteColumn': '删除列',
+    'insertColumnLeft': '在左边插入列',
+    'insertColumnRight': '在右边插入列',
+    'deleteTable': '删除表格',
+    'title': '标题',
+    'normalText': '普通文本',
+    'underline': '下划线文字',
+    'alignment': '水平对齐',
+    'alignCenter': '居中',
+    'alignLeft': '居左',
+    'alignRight': '居右',
+    'selectLanguage': '选择程序语言',
+    'fontScale': '字体大小',
+    'fontScaleXLarge': '超大字体',
+    'fontScaleLarge': '大号字体',
+    'fontScaleNormal': '正常大小',
+    'fontScaleSmall': '小号字体',
+    'fontScaleXSmall': '超小字体'
+  },
+  'en-US': {
+    'blockquote': 'Block Quote',
+    'bold': 'Bold',
+    'code': 'Code',
+    'color': 'Text Color',
+    'coloredText': 'Colored Text',
+    'hr': 'Horizontal Line',
+    'image': 'Insert Image',
+    'externalImage': 'External Image',
+    'uploadImage': 'Upload Image',
+    'uploadFailed': 'Upload failed',
+    'uploadError': 'Error occurs during upload',
+    'imageUrl': 'Url',
+    'imageSize': 'Size',
+    'imageAlt': 'Alt',
+    'restoreImageSize': 'Restore Origin Size',
+    'uploading': 'Uploading',
+    'indent': 'Indent',
+    'outdent': 'Outdent',
+    'italic': 'Italic',
+    'link': 'Insert Link',
+    'linkText': 'Text',
+    'linkUrl': 'Url',
+    'linkTarget': 'Target',
+    'openLinkInCurrentWindow': 'Open link in current window',
+    'openLinkInNewWindow': 'Open link in new window',
+    'removeLink': 'Remove Link',
+    'ol': 'Ordered List',
+    'ul': 'Unordered List',
+    'strikethrough': 'Strikethrough',
+    'table': 'Table',
+    'deleteRow': 'Delete Row',
+    'insertRowAbove': 'Insert Row Above',
+    'insertRowBelow': 'Insert Row Below',
+    'deleteColumn': 'Delete Column',
+    'insertColumnLeft': 'Insert Column Left',
+    'insertColumnRight': 'Insert Column Right',
+    'deleteTable': 'Delete Table',
+    'title': 'Title',
+    'normalText': 'Text',
+    'underline': 'Underline',
+    'alignment': 'Alignment',
+    'alignCenter': 'Align Center',
+    'alignLeft': 'Align Left',
+    'alignRight': 'Align Right',
+    'selectLanguage': 'Select Language',
+    'fontScale': 'Font Size',
+    'fontScaleXLarge': 'X Large Size',
+    'fontScaleLarge': 'Large Size',
+    'fontScaleNormal': 'Normal Size',
+    'fontScaleSmall': 'Small Size',
+    'fontScaleXSmall': 'X Small Size'
+  }
+};
+
+Button = (function(superClass) {
+  extend(Button, superClass);
+
+  Button.prototype._tpl = {
+    item: '<li><a tabindex="-1" unselectable="on" class="toolbar-item" href="javascript:;"><span></span></a></li>',
+    menuWrapper: '<div class="toolbar-menu"></div>',
+    menuItem: '<li><a tabindex="-1" unselectable="on" class="menu-item" href="javascript:;"><span></span></a></li>',
+    separator: '<li><span class="separator"></span></li>'
+  };
+
+  Button.prototype.name = '';
+
+  Button.prototype.icon = '';
+
+  Button.prototype.title = '';
+
+  Button.prototype.text = '';
+
+  Button.prototype.htmlTag = '';
+
+  Button.prototype.disableTag = '';
+
+  Button.prototype.menu = false;
+
+  Button.prototype.active = false;
+
+  Button.prototype.disabled = false;
+
+  Button.prototype.needFocus = true;
+
+  Button.prototype.shortcut = null;
+
+  function Button(opts) {
+    this.editor = opts.editor;
+    this.title = this._t(this.name);
+    Button.__super__.constructor.call(this, opts);
+  }
+
+  Button.prototype._init = function() {
+    var k, len, ref, tag;
+    this.render();
+    this.el.on('mousedown', (function(_this) {
+      return function(e) {
+        var exceed, noFocus, param;
+        e.preventDefault();
+        noFocus = _this.needFocus && !_this.editor.inputManager.focused;
+        if (_this.el.hasClass('disabled') || noFocus) {
+          return false;
+        }
+        if (_this.menu) {
+          _this.wrapper.toggleClass('menu-on').siblings('li').removeClass('menu-on');
+          if (_this.wrapper.is('.menu-on')) {
+            exceed = _this.menuWrapper.offset().left + _this.menuWrapper.outerWidth() + 5 - _this.editor.wrapper.offset().left - _this.editor.wrapper.outerWidth();
+            if (exceed > 0) {
+              _this.menuWrapper.css({
+                'left': 'auto',
+                'right': 0
+              });
+            }
+            _this.trigger('menuexpand');
+          }
+          return false;
+        }
+        param = _this.el.data('param');
+        _this.command(param);
+        return false;
+      };
+    })(this));
+    this.wrapper.on('click', 'a.menu-item', (function(_this) {
+      return function(e) {
+        var btn, noFocus, param;
+        e.preventDefault();
+        btn = $(e.currentTarget);
+        _this.wrapper.removeClass('menu-on');
+        noFocus = _this.needFocus && !_this.editor.inputManager.focused;
+        if (btn.hasClass('disabled') || noFocus) {
+          return false;
+        }
+        _this.editor.toolbar.wrapper.removeClass('menu-on');
+        param = btn.data('param');
+        _this.command(param);
+        return false;
+      };
+    })(this));
+    this.wrapper.on('mousedown', 'a.menu-item', function(e) {
+      return false;
+    });
+    this.editor.on('blur', (function(_this) {
+      return function() {
+        var editorActive;
+        editorActive = _this.editor.body.is(':visible') && _this.editor.body.is('[contenteditable]');
+        if (!(editorActive && !_this.editor.clipboard.pasting)) {
+          return;
+        }
+        _this.setActive(false);
+        return _this.setDisabled(false);
+      };
+    })(this));
+    if (this.shortcut != null) {
+      this.editor.hotkeys.add(this.shortcut, (function(_this) {
+        return function(e) {
+          _this.el.mousedown();
+          return false;
+        };
+      })(this));
+    }
+    ref = this.htmlTag.split(',');
+    for (k = 0, len = ref.length; k < len; k++) {
+      tag = ref[k];
+      tag = $.trim(tag);
+      if (tag && $.inArray(tag, this.editor.formatter._allowedTags) < 0) {
+        this.editor.formatter._allowedTags.push(tag);
+      }
+    }
+    return this.editor.on('selectionchanged', (function(_this) {
+      return function(e) {
+        if (_this.editor.inputManager.focused) {
+          return _this._status();
+        }
+      };
+    })(this));
+  };
+
+  Button.prototype.iconClassOf = function(icon) {
+    if (icon) {
+      return "simditor-icon simditor-icon-" + icon;
+    } else {
+      return '';
+    }
+  };
+
+  Button.prototype.setIcon = function(icon) {
+    return this.el.find('span').removeClass().addClass(this.iconClassOf(icon)).text(this.text);
+  };
+
+  Button.prototype.render = function() {
+    this.wrapper = $(this._tpl.item).appendTo(this.editor.toolbar.list);
+    this.el = this.wrapper.find('a.toolbar-item');
+    this.el.attr('title', this.title).addClass("toolbar-item-" + this.name).data('button', this);
+    this.setIcon(this.icon);
+    if (!this.menu) {
+      return;
+    }
+    this.menuWrapper = $(this._tpl.menuWrapper).appendTo(this.wrapper);
+    this.menuWrapper.addClass("toolbar-menu-" + this.name);
+    return this.renderMenu();
+  };
+
+  Button.prototype.renderMenu = function() {
+    var $menuBtnEl, $menuItemEl, k, len, menuItem, ref, ref1, results;
+    if (!$.isArray(this.menu)) {
+      return;
+    }
+    this.menuEl = $('<ul/>').appendTo(this.menuWrapper);
+    ref = this.menu;
+    results = [];
+    for (k = 0, len = ref.length; k < len; k++) {
+      menuItem = ref[k];
+      if (menuItem === '|') {
+        $(this._tpl.separator).appendTo(this.menuEl);
+        continue;
+      }
+      $menuItemEl = $(this._tpl.menuItem).appendTo(this.menuEl);
+      $menuBtnEl = $menuItemEl.find('a.menu-item').attr({
+        'title': (ref1 = menuItem.title) != null ? ref1 : menuItem.text,
+        'data-param': menuItem.param
+      }).addClass('menu-item-' + menuItem.name);
+      if (menuItem.icon) {
+        results.push($menuBtnEl.find('span').addClass(this.iconClassOf(menuItem.icon)));
+      } else {
+        results.push($menuBtnEl.find('span').text(menuItem.text));
+      }
+    }
+    return results;
+  };
+
+  Button.prototype.setActive = function(active) {
+    if (active === this.active) {
+      return;
+    }
+    this.active = active;
+    return this.el.toggleClass('active', this.active);
+  };
+
+  Button.prototype.setDisabled = function(disabled) {
+    if (disabled === this.disabled) {
+      return;
+    }
+    this.disabled = disabled;
+    return this.el.toggleClass('disabled', this.disabled);
+  };
+
+  Button.prototype._disableStatus = function() {
+    var disabled, endNodes, startNodes;
+    startNodes = this.editor.selection.startNodes();
+    endNodes = this.editor.selection.endNodes();
+    disabled = startNodes.filter(this.disableTag).length > 0 || endNodes.filter(this.disableTag).length > 0;
+    this.setDisabled(disabled);
+    if (this.disabled) {
+      this.setActive(false);
+    }
+    return this.disabled;
+  };
+
+  Button.prototype._activeStatus = function() {
+    var active, endNode, endNodes, startNode, startNodes;
+    startNodes = this.editor.selection.startNodes();
+    endNodes = this.editor.selection.endNodes();
+    startNode = startNodes.filter(this.htmlTag);
+    endNode = endNodes.filter(this.htmlTag);
+    active = startNode.length > 0 && endNode.length > 0 && startNode.is(endNode);
+    this.node = active ? startNode : null;
+    this.setActive(active);
+    return this.active;
+  };
+
+  Button.prototype._status = function() {
+    this._disableStatus();
+    if (this.disabled) {
+      return;
+    }
+    return this._activeStatus();
+  };
+
+  Button.prototype.command = function(param) {};
+
+  Button.prototype._t = function() {
+    var args, ref, result;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    result = Button.__super__._t.apply(this, args);
+    if (!result) {
+      result = (ref = this.editor)._t.apply(ref, args);
+    }
+    return result;
+  };
+
+  return Button;
+
+})(SimpleModule);
+
+Simditor.Button = Button;
+
+Popover = (function(superClass) {
+  extend(Popover, superClass);
+
+  Popover.prototype.offset = {
+    top: 4,
+    left: 0
+  };
+
+  Popover.prototype.target = null;
+
+  Popover.prototype.active = false;
+
+  function Popover(opts) {
+    this.button = opts.button;
+    this.editor = opts.button.editor;
+    Popover.__super__.constructor.call(this, opts);
+  }
+
+  Popover.prototype._init = function() {
+    this.el = $('<div class="simditor-popover"></div>').appendTo(this.editor.el).data('popover', this);
+    this.render();
+    this.el.on('mouseenter', (function(_this) {
+      return function(e) {
+        return _this.el.addClass('hover');
+      };
+    })(this));
+    return this.el.on('mouseleave', (function(_this) {
+      return function(e) {
+        return _this.el.removeClass('hover');
+      };
+    })(this));
+  };
+
+  Popover.prototype.render = function() {};
+
+  Popover.prototype._initLabelWidth = function() {
+    var $fields;
+    $fields = this.el.find('.settings-field');
+    if (!($fields.length > 0)) {
+      return;
+    }
+    this._labelWidth = 0;
+    $fields.each((function(_this) {
+      return function(i, field) {
+        var $field, $label;
+        $field = $(field);
+        $label = $field.find('label');
+        if (!($label.length > 0)) {
+          return;
+        }
+        return _this._labelWidth = Math.max(_this._labelWidth, $label.width());
+      };
+    })(this));
+    return $fields.find('label').width(this._labelWidth);
+  };
+
+  Popover.prototype.show = function($target, position) {
+    if (position == null) {
+      position = 'bottom';
+    }
+    if ($target == null) {
+      return;
+    }
+    this.el.siblings('.simditor-popover').each(function(i, popover) {
+      popover = $(popover).data('popover');
+      if (popover.active) {
+        return popover.hide();
+      }
+    });
+    if (this.active && this.target) {
+      this.target.removeClass('selected');
+    }
+    this.target = $target.addClass('selected');
+    if (this.active) {
+      this.refresh(position);
+      return this.trigger('popovershow');
+    } else {
+      this.active = true;
+      this.el.css({
+        left: -9999
+      }).show();
+      if (!this._labelWidth) {
+        this._initLabelWidth();
+      }
+      this.editor.util.reflow();
+      this.refresh(position);
+      return this.trigger('popovershow');
+    }
+  };
+
+  Popover.prototype.hide = function() {
+    if (!this.active) {
+      return;
+    }
+    if (this.target) {
+      this.target.removeClass('selected');
+    }
+    this.target = null;
+    this.active = false;
+    this.el.hide();
+    return this.trigger('popoverhide');
+  };
+
+  Popover.prototype.refresh = function(position) {
+    var editorOffset, left, maxLeft, targetH, targetOffset, top;
+    if (position == null) {
+      position = 'bottom';
+    }
+    if (!this.active) {
+      return;
+    }
+    editorOffset = this.editor.el.offset();
+    targetOffset = this.target.offset();
+    targetH = this.target.outerHeight();
+    if (position === 'bottom') {
+      top = targetOffset.top - editorOffset.top + targetH;
+    } else if (position === 'top') {
+      top = targetOffset.top - editorOffset.top - this.el.height();
+    }
+    maxLeft = this.editor.wrapper.width() - this.el.outerWidth() - 10;
+    left = Math.min(targetOffset.left - editorOffset.left, maxLeft);
+    return this.el.css({
+      top: top + this.offset.top,
+      left: left + this.offset.left
+    });
+  };
+
+  Popover.prototype.destroy = function() {
+    this.target = null;
+    this.active = false;
+    this.editor.off('.linkpopover');
+    return this.el.remove();
+  };
+
+  Popover.prototype._t = function() {
+    var args, ref, result;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    result = Popover.__super__._t.apply(this, args);
+    if (!result) {
+      result = (ref = this.button)._t.apply(ref, args);
+    }
+    return result;
+  };
+
+  return Popover;
+
+})(SimpleModule);
+
+Simditor.Popover = Popover;
+
+TitleButton = (function(superClass) {
+  extend(TitleButton, superClass);
+
+  function TitleButton() {
+    return TitleButton.__super__.constructor.apply(this, arguments);
+  }
+
+  TitleButton.prototype.name = 'title';
+
+  TitleButton.prototype.htmlTag = 'h1, h2, h3, h4, h5';
+
+  TitleButton.prototype.disableTag = 'pre, table';
+
+  TitleButton.prototype._init = function() {
+    this.menu = [
+      {
+        name: 'normal',
+        text: this._t('normalText'),
+        param: 'p'
+      }, '|', {
+        name: 'h1',
+        text: this._t('title') + ' 1',
+        param: 'h1'
+      }, {
+        name: 'h2',
+        text: this._t('title') + ' 2',
+        param: 'h2'
+      }, {
+        name: 'h3',
+        text: this._t('title') + ' 3',
+        param: 'h3'
+      }, {
+        name: 'h4',
+        text: this._t('title') + ' 4',
+        param: 'h4'
+      }, {
+        name: 'h5',
+        text: this._t('title') + ' 5',
+        param: 'h5'
+      }
+    ];
+    return TitleButton.__super__._init.call(this);
+  };
+
+  TitleButton.prototype.setActive = function(active, param) {
+    TitleButton.__super__.setActive.call(this, active);
+    if (active) {
+      param || (param = this.node[0].tagName.toLowerCase());
+    }
+    this.el.removeClass('active-p active-h1 active-h2 active-h3 active-h4 active-h5');
+    if (active) {
+      return this.el.addClass('active active-' + param);
+    }
+  };
+
+  TitleButton.prototype.command = function(param) {
+    var $rootNodes;
+    $rootNodes = this.editor.selection.rootNodes();
+    this.editor.selection.save();
+    $rootNodes.each((function(_this) {
+      return function(i, node) {
+        var $node;
+        $node = $(node);
+        if ($node.is('blockquote') || $node.is(param) || $node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node)) {
+          return;
+        }
+        return $('<' + param + '/>').append($node.contents()).replaceAll($node);
+      };
+    })(this));
+    this.editor.selection.restore();
+    return this.editor.trigger('valuechanged');
+  };
+
+  return TitleButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(TitleButton);
+
+FontScaleButton = (function(superClass) {
+  extend(FontScaleButton, superClass);
+
+  function FontScaleButton() {
+    return FontScaleButton.__super__.constructor.apply(this, arguments);
+  }
+
+  FontScaleButton.prototype.name = 'fontScale';
+
+  FontScaleButton.prototype.icon = 'font';
+
+  FontScaleButton.prototype.disableTag = 'pre';
+
+  FontScaleButton.prototype.htmlTag = 'span';
+
+  FontScaleButton.prototype.sizeMap = {
+    'x-large': '1.5em',
+    'large': '1.25em',
+    'small': '.75em',
+    'x-small': '.5em'
+  };
+
+  FontScaleButton.prototype._init = function() {
+    this.menu = [
+      {
+        name: '150%',
+        text: this._t('fontScaleXLarge'),
+        param: '5'
+      }, {
+        name: '125%',
+        text: this._t('fontScaleLarge'),
+        param: '4'
+      }, {
+        name: '100%',
+        text: this._t('fontScaleNormal'),
+        param: '3'
+      }, {
+        name: '75%',
+        text: this._t('fontScaleSmall'),
+        param: '2'
+      }, {
+        name: '50%',
+        text: this._t('fontScaleXSmall'),
+        param: '1'
+      }
+    ];
+    return FontScaleButton.__super__._init.call(this);
+  };
+
+  FontScaleButton.prototype._activeStatus = function() {
+    var active, endNode, endNodes, range, startNode, startNodes;
+    range = this.editor.selection.range();
+    startNodes = this.editor.selection.startNodes();
+    endNodes = this.editor.selection.endNodes();
+    startNode = startNodes.filter('span[style*="font-size"]');
+    endNode = endNodes.filter('span[style*="font-size"]');
+    active = startNodes.length > 0 && endNodes.length > 0 && startNode.is(endNode);
+    this.setActive(active);
+    return this.active;
+  };
+
+  FontScaleButton.prototype.command = function(param) {
+    var $scales, containerNode, range;
+    range = this.editor.selection.range();
+    if (range.collapsed) {
+      return;
+    }
+    document.execCommand('styleWithCSS', false, true);
+    document.execCommand('fontSize', false, param);
+    document.execCommand('styleWithCSS', false, false);
+    this.editor.selection.reset();
+    this.editor.selection.range();
+    containerNode = this.editor.selection.containerNode();
+    if (containerNode[0].nodeType === Node.TEXT_NODE) {
+      $scales = containerNode.closest('span[style*="font-size"]');
+    } else {
+      $scales = containerNode.find('span[style*="font-size"]');
+    }
+    $scales.each((function(_this) {
+      return function(i, n) {
+        var $span, size;
+        $span = $(n);
+        size = n.style.fontSize;
+        if (/large|x-large|small|x-small/.test(size)) {
+          return $span.css('fontSize', _this.sizeMap[size]);
+        } else if (size === 'medium') {
+          return $span.replaceWith($span.contents());
+        }
+      };
+    })(this));
+    return this.editor.trigger('valuechanged');
+  };
+
+  return FontScaleButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(FontScaleButton);
+
+BoldButton = (function(superClass) {
+  extend(BoldButton, superClass);
+
+  function BoldButton() {
+    return BoldButton.__super__.constructor.apply(this, arguments);
+  }
+
+  BoldButton.prototype.name = 'bold';
+
+  BoldButton.prototype.icon = 'bold';
+
+  BoldButton.prototype.htmlTag = 'b, strong';
+
+  BoldButton.prototype.disableTag = 'pre';
+
+  BoldButton.prototype.shortcut = 'cmd+b';
+
+  BoldButton.prototype._init = function() {
+    if (this.editor.util.os.mac) {
+      this.title = this.title + ' ( Cmd + b )';
+    } else {
+      this.title = this.title + ' ( Ctrl + b )';
+      this.shortcut = 'ctrl+b';
+    }
+    return BoldButton.__super__._init.call(this);
+  };
+
+  BoldButton.prototype._activeStatus = function() {
+    var active;
+    active = document.queryCommandState('bold') === true;
+    this.setActive(active);
+    return this.active;
+  };
+
+  BoldButton.prototype.command = function() {
+    document.execCommand('bold');
+    if (!this.editor.util.support.oninput) {
+      this.editor.trigger('valuechanged');
+    }
+    return $(document).trigger('selectionchange');
+  };
+
+  return BoldButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(BoldButton);
+
+ItalicButton = (function(superClass) {
+  extend(ItalicButton, superClass);
+
+  function ItalicButton() {
+    return ItalicButton.__super__.constructor.apply(this, arguments);
+  }
+
+  ItalicButton.prototype.name = 'italic';
+
+  ItalicButton.prototype.icon = 'italic';
+
+  ItalicButton.prototype.htmlTag = 'i';
+
+  ItalicButton.prototype.disableTag = 'pre';
+
+  ItalicButton.prototype.shortcut = 'cmd+i';
+
+  ItalicButton.prototype._init = function() {
+    if (this.editor.util.os.mac) {
+      this.title = this.title + " ( Cmd + i )";
+    } else {
+      this.title = this.title + " ( Ctrl + i )";
+      this.shortcut = 'ctrl+i';
+    }
+    return ItalicButton.__super__._init.call(this);
+  };
+
+  ItalicButton.prototype._activeStatus = function() {
+    var active;
+    active = document.queryCommandState('italic') === true;
+    this.setActive(active);
+    return this.active;
+  };
+
+  ItalicButton.prototype.command = function() {
+    document.execCommand('italic');
+    if (!this.editor.util.support.oninput) {
+      this.editor.trigger('valuechanged');
+    }
+    return $(document).trigger('selectionchange');
+  };
+
+  return ItalicButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(ItalicButton);
+
+UnderlineButton = (function(superClass) {
+  extend(UnderlineButton, superClass);
+
+  function UnderlineButton() {
+    return UnderlineButton.__super__.constructor.apply(this, arguments);
+  }
+
+  UnderlineButton.prototype.name = 'underline';
+
+  UnderlineButton.prototype.icon = 'underline';
+
+  UnderlineButton.prototype.htmlTag = 'u';
+
+  UnderlineButton.prototype.disableTag = 'pre';
+
+  UnderlineButton.prototype.shortcut = 'cmd+u';
+
+  UnderlineButton.prototype.render = function() {
+    if (this.editor.util.os.mac) {
+      this.title = this.title + ' ( Cmd + u )';
+    } else {
+      this.title = this.title + ' ( Ctrl + u )';
+      this.shortcut = 'ctrl+u';
+    }
+    return UnderlineButton.__super__.render.call(this);
+  };
+
+  UnderlineButton.prototype._activeStatus = function() {
+    var active;
+    active = document.queryCommandState('underline') === true;
+    this.setActive(active);
+    return this.active;
+  };
+
+  UnderlineButton.prototype.command = function() {
+    document.execCommand('underline');
+    if (!this.editor.util.support.oninput) {
+      this.editor.trigger('valuechanged');
+    }
+    return $(document).trigger('selectionchange');
+  };
+
+  return UnderlineButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(UnderlineButton);
+
+ColorButton = (function(superClass) {
+  extend(ColorButton, superClass);
+
+  function ColorButton() {
+    return ColorButton.__super__.constructor.apply(this, arguments);
+  }
+
+  ColorButton.prototype.name = 'color';
+
+  ColorButton.prototype.icon = 'tint';
+
+  ColorButton.prototype.disableTag = 'pre';
+
+  ColorButton.prototype.menu = true;
+
+  ColorButton.prototype.render = function() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    return ColorButton.__super__.render.apply(this, args);
+  };
+
+  ColorButton.prototype.renderMenu = function() {
+    $('<ul class="color-list">\n  <li><a href="javascript:;" class="font-color font-color-1"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-2"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-3"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-4"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-5"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-6"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-7"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-default"></a></li>\n</ul>').appendTo(this.menuWrapper);
+    this.menuWrapper.on('mousedown', '.color-list', function(e) {
+      return false;
+    });
+    return this.menuWrapper.on('click', '.font-color', (function(_this) {
+      return function(e) {
+        var $link, $p, hex, range, rgb, textNode;
+        _this.wrapper.removeClass('menu-on');
+        $link = $(e.currentTarget);
+        if ($link.hasClass('font-color-default')) {
+          $p = _this.editor.body.find('p, li');
+          if (!($p.length > 0)) {
+            return;
+          }
+          rgb = window.getComputedStyle($p[0], null).getPropertyValue('color');
+          hex = _this._convertRgbToHex(rgb);
+        } else {
+          rgb = window.getComputedStyle($link[0], null).getPropertyValue('background-color');
+          hex = _this._convertRgbToHex(rgb);
+        }
+        if (!hex) {
+          return;
+        }
+        range = _this.editor.selection.range();
+        if (!$link.hasClass('font-color-default') && range.collapsed) {
+          textNode = document.createTextNode(_this._t('coloredText'));
+          range.insertNode(textNode);
+          range.selectNodeContents(textNode);
+          _this.editor.selection.range(range);
+        }
+        document.execCommand('styleWithCSS', false, true);
+        document.execCommand('foreColor', false, hex);
+        document.execCommand('styleWithCSS', false, false);
+        if (!_this.editor.util.support.oninput) {
+          return _this.editor.trigger('valuechanged');
+        }
+      };
+    })(this));
+  };
+
+  ColorButton.prototype._convertRgbToHex = function(rgb) {
+    var match, re, rgbToHex;
+    re = /rgb\((\d+),\s?(\d+),\s?(\d+)\)/g;
+    match = re.exec(rgb);
+    if (!match) {
+      return '';
+    }
+    rgbToHex = function(r, g, b) {
+      var componentToHex;
+      componentToHex = function(c) {
+        var hex;
+        hex = c.toString(16);
+        if (hex.length === 1) {
+          return '0' + hex;
+        } else {
+          return hex;
+        }
+      };
+      return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
+    };
+    return rgbToHex(match[1] * 1, match[2] * 1, match[3] * 1);
+  };
+
+  return ColorButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(ColorButton);
+
+ListButton = (function(superClass) {
+  extend(ListButton, superClass);
+
+  function ListButton() {
+    return ListButton.__super__.constructor.apply(this, arguments);
+  }
+
+  ListButton.prototype.type = '';
+
+  ListButton.prototype.disableTag = 'pre, table';
+
+  ListButton.prototype.command = function(param) {
+    var $list, $rootNodes, anotherType;
+    $rootNodes = this.editor.selection.blockNodes();
+    anotherType = this.type === 'ul' ? 'ol' : 'ul';
+    this.editor.selection.save();
+    $list = null;
+    $rootNodes.each((function(_this) {
+      return function(i, node) {
+        var $node;
+        $node = $(node);
+        if ($node.is('blockquote, li') || $node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node) || !$.contains(document, node)) {
+          return;
+        }
+        if ($node.is(_this.type)) {
+          $node.children('li').each(function(i, li) {
+            var $childList, $li;
+            $li = $(li);
+            $childList = $li.children('ul, ol').insertAfter($node);
+            return $('<p/>').append($(li).html() || _this.editor.util.phBr).insertBefore($node);
+          });
+          return $node.remove();
+        } else if ($node.is(anotherType)) {
+          return $('<' + _this.type + '/>').append($node.contents()).replaceAll($node);
+        } else if ($list && $node.prev().is($list)) {
+          $('<li/>').append($node.html() || _this.editor.util.phBr).appendTo($list);
+          return $node.remove();
+        } else {
+          $list = $("<" + _this.type + "><li></li></" + _this.type + ">");
+          $list.find('li').append($node.html() || _this.editor.util.phBr);
+          return $list.replaceAll($node);
+        }
+      };
+    })(this));
+    this.editor.selection.restore();
+    return this.editor.trigger('valuechanged');
+  };
+
+  return ListButton;
+
+})(Button);
+
+OrderListButton = (function(superClass) {
+  extend(OrderListButton, superClass);
+
+  function OrderListButton() {
+    return OrderListButton.__super__.constructor.apply(this, arguments);
+  }
+
+  OrderListButton.prototype.type = 'ol';
+
+  OrderListButton.prototype.name = 'ol';
+
+  OrderListButton.prototype.icon = 'list-ol';
+
+  OrderListButton.prototype.htmlTag = 'ol';
+
+  OrderListButton.prototype.shortcut = 'cmd+/';
+
+  OrderListButton.prototype._init = function() {
+    if (this.editor.util.os.mac) {
+      this.title = this.title + ' ( Cmd + / )';
+    } else {
+      this.title = this.title + ' ( ctrl + / )';
+      this.shortcut = 'ctrl+/';
+    }
+    return OrderListButton.__super__._init.call(this);
+  };
+
+  return OrderListButton;
+
+})(ListButton);
+
+UnorderListButton = (function(superClass) {
+  extend(UnorderListButton, superClass);
+
+  function UnorderListButton() {
+    return UnorderListButton.__super__.constructor.apply(this, arguments);
+  }
+
+  UnorderListButton.prototype.type = 'ul';
+
+  UnorderListButton.prototype.name = 'ul';
+
+  UnorderListButton.prototype.icon = 'list-ul';
+
+  UnorderListButton.prototype.htmlTag = 'ul';
+
+  UnorderListButton.prototype.shortcut = 'cmd+.';
+
+  UnorderListButton.prototype._init = function() {
+    if (this.editor.util.os.mac) {
+      this.title = this.title + ' ( Cmd + . )';
+    } else {
+      this.title = this.title + ' ( Ctrl + . )';
+      this.shortcut = 'ctrl+.';
+    }
+    return UnorderListButton.__super__._init.call(this);
+  };
+
+  return UnorderListButton;
+
+})(ListButton);
+
+Simditor.Toolbar.addButton(OrderListButton);
+
+Simditor.Toolbar.addButton(UnorderListButton);
+
+BlockquoteButton = (function(superClass) {
+  extend(BlockquoteButton, superClass);
+
+  function BlockquoteButton() {
+    return BlockquoteButton.__super__.constructor.apply(this, arguments);
+  }
+
+  BlockquoteButton.prototype.name = 'blockquote';
+
+  BlockquoteButton.prototype.icon = 'quote-left';
+
+  BlockquoteButton.prototype.htmlTag = 'blockquote';
+
+  BlockquoteButton.prototype.disableTag = 'pre, table';
+
+  BlockquoteButton.prototype.command = function() {
+    var $rootNodes, clearCache, nodeCache;
+    $rootNodes = this.editor.selection.rootNodes();
+    $rootNodes = $rootNodes.filter(function(i, node) {
+      return !$(node).parent().is('blockquote');
+    });
+    this.editor.selection.save();
+    nodeCache = [];
+    clearCache = (function(_this) {
+      return function() {
+        if (nodeCache.length > 0) {
+          $("<" + _this.htmlTag + "/>").insertBefore(nodeCache[0]).append(nodeCache);
+          return nodeCache.length = 0;
+        }
+      };
+    })(this);
+    $rootNodes.each((function(_this) {
+      return function(i, node) {
+        var $node;
+        $node = $(node);
+        if (!$node.parent().is(_this.editor.body)) {
+          return;
+        }
+        if ($node.is(_this.htmlTag)) {
+          clearCache();
+          return $node.children().unwrap();
+        } else if ($node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node)) {
+          return clearCache();
+        } else {
+          return nodeCache.push(node);
+        }
+      };
+    })(this));
+    clearCache();
+    this.editor.selection.restore();
+    return this.editor.trigger('valuechanged');
+  };
+
+  return BlockquoteButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(BlockquoteButton);
+
+CodeButton = (function(superClass) {
+  extend(CodeButton, superClass);
+
+  function CodeButton() {
+    return CodeButton.__super__.constructor.apply(this, arguments);
+  }
+
+  CodeButton.prototype.name = 'code';
+
+  CodeButton.prototype.icon = 'code';
+
+  CodeButton.prototype.htmlTag = 'pre';
+
+  CodeButton.prototype.disableTag = 'ul, ol, table';
+
+  CodeButton.prototype._init = function() {
+    CodeButton.__super__._init.call(this);
+    this.editor.on('decorate', (function(_this) {
+      return function(e, $el) {
+        return $el.find('pre').each(function(i, pre) {
+          return _this.decorate($(pre));
+        });
+      };
+    })(this));
+    return this.editor.on('undecorate', (function(_this) {
+      return function(e, $el) {
+        return $el.find('pre').each(function(i, pre) {
+          return _this.undecorate($(pre));
+        });
+      };
+    })(this));
+  };
+
+  CodeButton.prototype.render = function() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    CodeButton.__super__.render.apply(this, args);
+    return this.popover = new CodePopover({
+      button: this
+    });
+  };
+
+  CodeButton.prototype._checkMode = function() {
+    var $blockNodes, range;
+    range = this.editor.selection.range();
+    if (($blockNodes = $(range.cloneContents()).find(this.editor.util.blockNodes.join(','))) > 0 || (range.collapsed && this.editor.selection.startNodes().filter('code').length === 0)) {
+      this.inlineMode = false;
+      return this.htmlTag = 'pre';
+    } else {
+      this.inlineMode = true;
+      return this.htmlTag = 'code';
+    }
+  };
+
+  CodeButton.prototype._status = function() {
+    this._checkMode();
+    CodeButton.__super__._status.call(this);
+    if (this.inlineMode) {
+      return;
+    }
+    if (this.active) {
+      return this.popover.show(this.node);
+    } else {
+      return this.popover.hide();
+    }
+  };
+
+  CodeButton.prototype.decorate = function($pre) {
+    var $code, lang, ref, ref1;
+    $code = $pre.find('> code');
+    if ($code.length > 0) {
+      lang = (ref = $code.attr('class')) != null ? (ref1 = ref.match(/lang-(\S+)/)) != null ? ref1[1] : void 0 : void 0;
+      $code.contents().unwrap();
+      if (lang) {
+        return $pre.attr('data-lang', lang);
+      }
+    }
+  };
+
+  CodeButton.prototype.undecorate = function($pre) {
+    var $code, lang;
+    lang = $pre.attr('data-lang');
+    $code = $('<code/>');
+    if (lang && lang !== -1) {
+      $code.addClass('lang-' + lang);
+    }
+    return $pre.wrapInner($code).removeAttr('data-lang');
+  };
+
+  CodeButton.prototype.command = function() {
+    if (this.inlineMode) {
+      return this._inlineCommand();
+    } else {
+      return this._blockCommand();
+    }
+  };
+
+  CodeButton.prototype._blockCommand = function() {
+    var $rootNodes, clearCache, nodeCache, resultNodes;
+    $rootNodes = this.editor.selection.rootNodes();
+    nodeCache = [];
+    resultNodes = [];
+    clearCache = (function(_this) {
+      return function() {
+        var $pre;
+        if (!(nodeCache.length > 0)) {
+          return;
+        }
+        $pre = $("<" + _this.htmlTag + "/>").insertBefore(nodeCache[0]).text(_this.editor.formatter.clearHtml(nodeCache));
+        resultNodes.push($pre[0]);
+        return nodeCache.length = 0;
+      };
+    })(this);
+    $rootNodes.each((function(_this) {
+      return function(i, node) {
+        var $node, $p;
+        $node = $(node);
+        if ($node.is(_this.htmlTag)) {
+          clearCache();
+          $p = $('<p/>').append($node.html().replace('\n', '<br/>')).replaceAll($node);
+          return resultNodes.push($p[0]);
+        } else if ($node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node) || $node.is('blockquote')) {
+          return clearCache();
+        } else {
+          return nodeCache.push(node);
+        }
+      };
+    })(this));
+    clearCache();
+    this.editor.selection.setRangeAtEndOf($(resultNodes).last());
+    return this.editor.trigger('valuechanged');
+  };
+
+  CodeButton.prototype._inlineCommand = function() {
+    var $code, $contents, range;
+    range = this.editor.selection.range();
+    if (this.active) {
+      range.selectNodeContents(this.node[0]);
+      this.editor.selection.save(range);
+      this.node.contents().unwrap();
+      this.editor.selection.restore();
+    } else {
+      $contents = $(range.extractContents());
+      $code = $("<" + this.htmlTag + "/>").append($contents.contents());
+      range.insertNode($code[0]);
+      range.selectNodeContents($code[0]);
+      this.editor.selection.range(range);
+    }
+    return this.editor.trigger('valuechanged');
+  };
+
+  return CodeButton;
+
+})(Button);
+
+CodePopover = (function(superClass) {
+  extend(CodePopover, superClass);
+
+  function CodePopover() {
+    return CodePopover.__super__.constructor.apply(this, arguments);
+  }
+
+  CodePopover.prototype.render = function() {
+    var $option, k, lang, len, ref;
+    this._tpl = "<div class=\"code-settings\">\n  <div class=\"settings-field\">\n    <select class=\"select-lang\">\n      <option value=\"-1\">" + (this._t('selectLanguage')) + "</option>\n    </select>\n  </div>\n</div>";
+    this.langs = this.editor.opts.codeLanguages || [
+      {
+        name: 'Bash',
+        value: 'bash'
+      }, {
+        name: 'C++',
+        value: 'c++'
+      }, {
+        name: 'C#',
+        value: 'cs'
+      }, {
+        name: 'CSS',
+        value: 'css'
+      }, {
+        name: 'Erlang',
+        value: 'erlang'
+      }, {
+        name: 'Less',
+        value: 'less'
+      }, {
+        name: 'Sass',
+        value: 'sass'
+      }, {
+        name: 'Diff',
+        value: 'diff'
+      }, {
+        name: 'CoffeeScript',
+        value: 'coffeescript'
+      }, {
+        name: 'HTML,XML',
+        value: 'html'
+      }, {
+        name: 'JSON',
+        value: 'json'
+      }, {
+        name: 'Java',
+        value: 'java'
+      }, {
+        name: 'JavaScript',
+        value: 'js'
+      }, {
+        name: 'Markdown',
+        value: 'markdown'
+      }, {
+        name: 'Objective C',
+        value: 'oc'
+      }, {
+        name: 'PHP',
+        value: 'php'
+      }, {
+        name: 'Perl',
+        value: 'parl'
+      }, {
+        name: 'Python',
+        value: 'python'
+      }, {
+        name: 'Ruby',
+        value: 'ruby'
+      }, {
+        name: 'SQL',
+        value: 'sql'
+      }
+    ];
+    this.el.addClass('code-popover').append(this._tpl);
+    this.selectEl = this.el.find('.select-lang');
+    ref = this.langs;
+    for (k = 0, len = ref.length; k < len; k++) {
+      lang = ref[k];
+      $option = $('<option/>', {
+        text: lang.name,
+        value: lang.value
+      }).appendTo(this.selectEl);
+    }
+    this.selectEl.on('change', (function(_this) {
+      return function(e) {
+        var selected;
+        _this.lang = _this.selectEl.val();
+        selected = _this.target.hasClass('selected');
+        _this.target.removeClass().removeAttr('data-lang');
+        if (_this.lang !== -1) {
+          _this.target.attr('data-lang', _this.lang);
+        }
+        if (selected) {
+          _this.target.addClass('selected');
+        }
+        return _this.editor.trigger('valuechanged');
+      };
+    })(this));
+    return this.editor.on('valuechanged', (function(_this) {
+      return function(e) {
+        if (_this.active) {
+          return _this.refresh();
+        }
+      };
+    })(this));
+  };
+
+  CodePopover.prototype.show = function() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    CodePopover.__super__.show.apply(this, args);
+    this.lang = this.target.attr('data-lang');
+    if (this.lang != null) {
+      return this.selectEl.val(this.lang);
+    } else {
+      return this.selectEl.val(-1);
+    }
+  };
+
+  return CodePopover;
+
+})(Popover);
+
+Simditor.Toolbar.addButton(CodeButton);
+
+LinkButton = (function(superClass) {
+  extend(LinkButton, superClass);
+
+  function LinkButton() {
+    return LinkButton.__super__.constructor.apply(this, arguments);
+  }
+
+  LinkButton.prototype.name = 'link';
+
+  LinkButton.prototype.icon = 'link';
+
+  LinkButton.prototype.htmlTag = 'a';
+
+  LinkButton.prototype.disableTag = 'pre';
+
+  LinkButton.prototype.render = function() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    LinkButton.__super__.render.apply(this, args);
+    return this.popover = new LinkPopover({
+      button: this
+    });
+  };
+
+  LinkButton.prototype._status = function() {
+    LinkButton.__super__._status.call(this);
+    if (this.active && !this.editor.selection.rangeAtEndOf(this.node)) {
+      return this.popover.show(this.node);
+    } else {
+      return this.popover.hide();
+    }
+  };
+
+  LinkButton.prototype.command = function() {
+    var $contents, $link, $newBlock, linkText, range, txtNode;
+    range = this.editor.selection.range();
+    if (this.active) {
+      txtNode = document.createTextNode(this.node.text());
+      this.node.replaceWith(txtNode);
+      range.selectNode(txtNode);
+    } else {
+      $contents = $(range.extractContents());
+      linkText = this.editor.formatter.clearHtml($contents.contents(), false);
+      $link = $('<a/>', {
+        href: 'http://www.example.com',
+        target: '_blank',
+        text: linkText || this._t('linkText')
+      });
+      if (this.editor.selection.blockNodes().length > 0) {
+        range.insertNode($link[0]);
+      } else {
+        $newBlock = $('<p/>').append($link);
+        range.insertNode($newBlock[0]);
+      }
+      range.selectNodeContents($link[0]);
+      this.popover.one('popovershow', (function(_this) {
+        return function() {
+          if (linkText) {
+            _this.popover.urlEl.focus();
+            return _this.popover.urlEl[0].select();
+          } else {
+            _this.popover.textEl.focus();
+            return _this.popover.textEl[0].select();
+          }
+        };
+      })(this));
+    }
+    this.editor.selection.range(range);
+    return this.editor.trigger('valuechanged');
+  };
+
+  return LinkButton;
+
+})(Button);
+
+LinkPopover = (function(superClass) {
+  extend(LinkPopover, superClass);
+
+  function LinkPopover() {
+    return LinkPopover.__super__.constructor.apply(this, arguments);
+  }
+
+  LinkPopover.prototype.render = function() {
+    var tpl;
+    tpl = "<div class=\"link-settings\">\n  <div class=\"settings-field\">\n    <label>" + (this._t('linkText')) + "</label>\n    <input class=\"link-text\" type=\"text\"/>\n    <a class=\"btn-unlink\" href=\"javascript:;\" title=\"" + (this._t('removeLink')) + "\"\n      tabindex=\"-1\">\n      <span class=\"simditor-icon simditor-icon-unlink\"></span>\n    </a>\n  </div>\n  <div class=\"settings-field\">\n    <label>" + (this._t('linkUrl')) + "</label>\n    <input class=\"link-url\" type=\"text\"/>\n  </div>\n  <div class=\"settings-field\">\n    <label>" + (this._t('linkTarget')) + "</label>\n    <select class=\"link-target\">\n      <option value=\"_blank\">" + (this._t('openLinkInNewWindow')) + " (_blank)</option>\n      <option value=\"_self\">" + (this._t('openLinkInCurrentWindow')) + " (_self)</option>\n    </select>\n  </div>\n</div>";
+    this.el.addClass('link-popover').append(tpl);
+    this.textEl = this.el.find('.link-text');
+    this.urlEl = this.el.find('.link-url');
+    this.unlinkEl = this.el.find('.btn-unlink');
+    this.selectTarget = this.el.find('.link-target');
+    this.textEl.on('keyup', (function(_this) {
+      return function(e) {
+        if (e.which === 13) {
+          return;
+        }
+        _this.target.text(_this.textEl.val());
+        return _this.editor.inputManager.throttledValueChanged();
+      };
+    })(this));
+    this.urlEl.on('keyup', (function(_this) {
+      return function(e) {
+        var val;
+        if (e.which === 13) {
+          return;
+        }
+        val = _this.urlEl.val();
+        if (!(/https?:\/\/|^\//ig.test(val) || !val)) {
+          val = 'http://' + val;
+        }
+        _this.target.attr('href', val);
+        return _this.editor.inputManager.throttledValueChanged();
+      };
+    })(this));
+    $([this.urlEl[0], this.textEl[0]]).on('keydown', (function(_this) {
+      return function(e) {
+        var range;
+        if (e.which === 13 || e.which === 27 || (!e.shiftKey && e.which === 9 && $(e.target).hasClass('link-url'))) {
+          e.preventDefault();
+          range = document.createRange();
+          _this.editor.selection.setRangeAfter(_this.target, range);
+          _this.hide();
+          return _this.editor.inputManager.throttledValueChanged();
+        }
+      };
+    })(this));
+    this.unlinkEl.on('click', (function(_this) {
+      return function(e) {
+        var range, txtNode;
+        txtNode = document.createTextNode(_this.target.text());
+        _this.target.replaceWith(txtNode);
+        _this.hide();
+        range = document.createRange();
+        _this.editor.selection.setRangeAfter(txtNode, range);
+        return _this.editor.inputManager.throttledValueChanged();
+      };
+    })(this));
+    return this.selectTarget.on('change', (function(_this) {
+      return function(e) {
+        _this.target.attr('target', _this.selectTarget.val());
+        return _this.editor.inputManager.throttledValueChanged();
+      };
+    })(this));
+  };
+
+  LinkPopover.prototype.show = function() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    LinkPopover.__super__.show.apply(this, args);
+    this.textEl.val(this.target.text());
+    return this.urlEl.val(this.target.attr('href'));
+  };
+
+  return LinkPopover;
+
+})(Popover);
+
+Simditor.Toolbar.addButton(LinkButton);
+
+ImageButton = (function(superClass) {
+  extend(ImageButton, superClass);
+
+  function ImageButton() {
+    return ImageButton.__super__.constructor.apply(this, arguments);
+  }
+
+  ImageButton.prototype.name = 'image';
+
+  ImageButton.prototype.icon = 'picture-o';
+
+  ImageButton.prototype.htmlTag = 'img';
+
+  ImageButton.prototype.disableTag = 'pre, table';
+
+  ImageButton.prototype.defaultImage = '';
+
+  ImageButton.prototype.needFocus = false;
+
+  ImageButton.prototype._init = function() {
+    var item, k, len, ref;
+    if (this.editor.opts.imageButton) {
+      if (Array.isArray(this.editor.opts.imageButton)) {
+        this.menu = [];
+        ref = this.editor.opts.imageButton;
+        for (k = 0, len = ref.length; k < len; k++) {
+          item = ref[k];
+          this.menu.push({
+            name: item + '-image',
+            text: this._t(item + 'Image')
+          });
+        }
+      } else {
+        this.menu = false;
+      }
+    } else {
+      if (this.editor.uploader != null) {
+        this.menu = [
+          {
+            name: 'upload-image',
+            text: this._t('uploadImage')
+          }, {
+            name: 'external-image',
+            text: this._t('externalImage')
+          }
+        ];
+      } else {
+        this.menu = false;
+      }
+    }
+    this.defaultImage = this.editor.opts.defaultImage;
+    this.editor.body.on('click', 'img:not([data-non-image])', (function(_this) {
+      return function(e) {
+        var $img, range;
+        $img = $(e.currentTarget);
+        range = document.createRange();
+        range.selectNode($img[0]);
+        _this.editor.selection.range(range);
+        if (!_this.editor.util.support.onselectionchange) {
+          _this.editor.trigger('selectionchanged');
+        }
+        return false;
+      };
+    })(this));
+    this.editor.body.on('mouseup', 'img:not([data-non-image])', function(e) {
+      return false;
+    });
+    this.editor.on('selectionchanged.image', (function(_this) {
+      return function() {
+        var $contents, $img, range;
+        range = _this.editor.selection.range();
+        if (range == null) {
+          return;
+        }
+        $contents = $(range.cloneContents()).contents();
+        if ($contents.length === 1 && $contents.is('img:not([data-non-image])')) {
+          $img = $(range.startContainer).contents().eq(range.startOffset);
+          return _this.popover.show($img);
+        } else {
+          return _this.popover.hide();
+        }
+      };
+    })(this));
+    this.editor.on('valuechanged.image', (function(_this) {
+      return function() {
+        var $masks;
+        $masks = _this.editor.wrapper.find('.simditor-image-loading');
+        if (!($masks.length > 0)) {
+          return;
+        }
+        return $masks.each(function(i, mask) {
+          var $img, $mask, file;
+          $mask = $(mask);
+          $img = $mask.data('img');
+          if (!($img && $img.parent().length > 0)) {
+            $mask.remove();
+            if ($img) {
+              file = $img.data('file');
+              if (file) {
+                _this.editor.uploader.cancel(file);
+                if (_this.editor.body.find('img.uploading').length < 1) {
+                  return _this.editor.uploader.trigger('uploadready', [file]);
+                }
+              }
+            }
+          }
+        });
+      };
+    })(this));
+    return ImageButton.__super__._init.call(this);
+  };
+
+  ImageButton.prototype.render = function() {
+    var args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    ImageButton.__super__.render.apply(this, args);
+    this.popover = new ImagePopover({
+      button: this
+    });
+    if (this.editor.opts.imageButton === 'upload') {
+      return this._initUploader(this.el);
+    }
+  };
+
+  ImageButton.prototype.renderMenu = function() {
+    ImageButton.__super__.renderMenu.call(this);
+    return this._initUploader();
+  };
+
+  ImageButton.prototype._initUploader = function($uploadItem) {
+    var $input, createInput, uploadProgress;
+    if ($uploadItem == null) {
+      $uploadItem = this.menuEl.find('.menu-item-upload-image');
+    }
+    if (this.editor.uploader == null) {
+      this.el.find('.btn-upload').remove();
+      return;
+    }
+    $input = null;
+    createInput = (function(_this) {
+      return function() {
+        if ($input) {
+          $input.remove();
+        }
+        return $input = $('<input/>', {
+          type: 'file',
+          title: _this._t('uploadImage'),
+          multiple: true,
+          accept: 'image/*'
+        }).appendTo($uploadItem);
+      };
+    })(this);
+    createInput();
+    $uploadItem.on('click mousedown', 'input[type=file]', function(e) {
+      return e.stopPropagation();
+    });
+    $uploadItem.on('change', 'input[type=file]', (function(_this) {
+      return function(e) {
+        if (_this.editor.inputManager.focused) {
+          _this.editor.uploader.upload($input, {
+            inline: true
+          });
+          createInput();
+        } else {
+          _this.editor.one('focus', function(e) {
+            _this.editor.uploader.upload($input, {
+              inline: true
+            });
+            return createInput();
+          });
+          _this.editor.focus();
+        }
+        return _this.wrapper.removeClass('menu-on');
+      };
+    })(this));
+    this.editor.uploader.on('beforeupload', (function(_this) {
+      return function(e, file) {
+        var $img;
+        if (!file.inline) {
+          return;
+        }
+        if (file.img) {
+          $img = $(file.img);
+        } else {
+          $img = _this.createImage(file.name);
+          file.img = $img;
+        }
+        $img.addClass('uploading');
+        $img.data('file', file);
+        return _this.editor.uploader.readImageFile(file.obj, function(img) {
+          var src;
+          if (!$img.hasClass('uploading')) {
+            return;
+          }
+          src = img ? img.src : _this.defaultImage;
+          return _this.loadImage($img, src, function() {
+            if (_this.popover.active) {
+              _this.popover.refresh();
+              return _this.popover.srcEl.val(_this._t('uploading')).prop('disabled', true);
+            }
+          });
+        });
+      };
+    })(this));
+    uploadProgress = $.proxy(this.editor.util.throttle(function(e, file, loaded, total) {
+      var $img, $mask, percent;
+      if (!file.inline) {
+        return;
+      }
+      $mask = file.img.data('mask');
+      if (!$mask) {
+        return;
+      }
+      $img = $mask.data('img');
+      if (!($img.hasClass('uploading') && $img.parent().length > 0)) {
+        $mask.remove();
+        return;
+      }
+      percent = loaded / total;
+      percent = (percent * 100).toFixed(0);
+      if (percent > 99) {
+        percent = 99;
+      }
+      return $mask.find('.progress').height((100 - percent) + "%");
+    }, 500), this);
+    this.editor.uploader.on('uploadprogress', uploadProgress);
+    this.editor.uploader.on('uploadsuccess', (function(_this) {
+      return function(e, file, result) {
+        var $img, img_path, msg;
+        if (!file.inline) {
+          return;
+        }
+        $img = file.img;
+        if (!($img.hasClass('uploading') && $img.parent().length > 0)) {
+          return;
+        }
+        if (typeof result !== 'object') {
+          try {
+            result = $.parseJSON(result);
+          } catch (_error) {
+            e = _error;
+            result = {
+              success: false
+            };
+          }
+        }
+        if (result.success === false) {
+          msg = result.msg || _this._t('uploadFailed');
+          alert(msg);
+          img_path = _this.defaultImage;
+        } else {
+          img_path = result.file_path;
+        }
+        _this.loadImage($img, img_path, function() {
+          var $mask;
+          $img.removeData('file');
+          $img.removeClass('uploading').removeClass('loading');
+          $mask = $img.data('mask');
+          if ($mask) {
+            $mask.remove();
+          }
+          $img.removeData('mask');
+          _this.editor.trigger('valuechanged');
+          if (_this.editor.body.find('img.uploading').length < 1) {
+            return _this.editor.uploader.trigger('uploadready', [file, result]);
+          }
+        });
+        if (_this.popover.active) {
+          _this.popover.srcEl.prop('disabled', false);
+          return _this.popover.srcEl.val(result.file_path);
+        }
+      };
+    })(this));
+    return this.editor.uploader.on('uploaderror', (function(_this) {
+      return function(e, file, xhr) {
+        var $img, msg, result;
+        if (!file.inline) {
+          return;
+        }
+        if (xhr.statusText === 'abort') {
+          return;
+        }
+        if (xhr.responseText) {
+          try {
+            result = $.parseJSON(xhr.responseText);
+            msg = result.msg;
+          } catch (_error) {
+            e = _error;
+            msg = _this._t('uploadError');
+          }
+          alert(msg);
+        }
+        $img = file.img;
+        if (!($img.hasClass('uploading') && $img.parent().length > 0)) {
+          return;
+        }
+        _this.loadImage($img, _this.defaultImage, function() {
+          var $mask;
+          $img.removeData('file');
+          $img.removeClass('uploading').removeClass('loading');
+          $mask = $img.data('mask');
+          if ($mask) {
+            $mask.remove();
+          }
+          return $img.removeData('mask');
+        });
+        if (_this.popover.active) {
+          _this.popover.srcEl.prop('disabled', false);
+          _this.popover.srcEl.val(_this.defaultImage);
+        }
+        _this.editor.trigger('valuechanged');
+        if (_this.editor.body.find('img.uploading').length < 1) {
+          return _this.editor.uploader.trigger('uploadready', [file, result]);
+        }
+      };
+    })(this));
+  };
+
+  ImageButton.prototype._status = function() {
+    return this._disableStatus();
+  };
+
+  ImageButton.prototype.loadImage = function($img, src, callback) {
+    var $mask, img, positionMask;
+    positionMask = (function(_this) {
+      return function() {
+        var imgOffset, wrapperOffset;
+        imgOffset = $img.offset();
+        wrapperOffset = _this.editor.wrapper.offset();
+        return $mask.css({
+          top: imgOffset.top - wrapperOffset.top,
+          left: imgOffset.left - wrapperOffset.left,
+          width: $img.width(),
+          height: $img.height()
+        }).show();
+      };
+    })(this);
+    $img.addClass('loading');
+    $mask = $img.data('mask');
+    if (!$mask) {
+      $mask = $('<div class="simditor-image-loading">\n  <div class="progress"></div>\n</div>').hide().appendTo(this.editor.wrapper);
+      positionMask();
+      $img.data('mask', $mask);
+      $mask.data('img', $img);
+    }
+    img = new Image();
+    img.onload = (function(_this) {
+      return function() {
+        var height, width;
+        if (!$img.hasClass('loading') && !$img.hasClass('uploading')) {
+          return;
+        }
+        width = img.width;
+        height = img.height;
+        $img.attr({
+          src: src,
+          width: width,
+          height: height,
+          'data-image-size': width + ',' + height
+        }).removeClass('loading');
+        if ($img.hasClass('uploading')) {
+          _this.editor.util.reflow(_this.editor.body);
+          positionMask();
+        } else {
+          $mask.remove();
+          $img.removeData('mask');
+        }
+        if ($.isFunction(callback)) {
+          return callback(img);
+        }
+      };
+    })(this);
+    img.onerror = function() {
+      if ($.isFunction(callback)) {
+        callback(false);
+      }
+      $mask.remove();
+      return $img.removeData('mask').removeClass('loading');
+    };
+    return img.src = src;
+  };
+
+  ImageButton.prototype.createImage = function(name) {
+    var $img, range;
+    if (name == null) {
+      name = 'Image';
+    }
+    if (!this.editor.inputManager.focused) {
+      this.editor.focus();
+    }
+    range = this.editor.selection.range();
+    range.deleteContents();
+    this.editor.selection.range(range);
+    $img = $('<img/>').attr('alt', name);
+    range.insertNode($img[0]);
+    this.editor.selection.setRangeAfter($img, range);
+    this.editor.trigger('valuechanged');
+    return $img;
+  };
+
+  ImageButton.prototype.command = function(src) {
+    var $img;
+    $img = this.createImage();
+    return this.loadImage($img, src || this.defaultImage, (function(_this) {
+      return function() {
+        _this.editor.trigger('valuechanged');
+        _this.editor.util.reflow($img);
+        $img.click();
+        return _this.popover.one('popovershow', function() {
+          _this.popover.srcEl.focus();
+          return _this.popover.srcEl[0].select();
+        });
+      };
+    })(this));
+  };
+
+  return ImageButton;
+
+})(Button);
+
+ImagePopover = (function(superClass) {
+  extend(ImagePopover, superClass);
+
+  function ImagePopover() {
+    return ImagePopover.__super__.constructor.apply(this, arguments);
+  }
+
+  ImagePopover.prototype.offset = {
+    top: 6,
+    left: -4
+  };
+
+  ImagePopover.prototype.render = function() {
+    var tpl;
+    tpl = "<div class=\"link-settings\">\n  <div class=\"settings-field\">\n    <label>" + (this._t('imageUrl')) + "</label>\n    <input class=\"image-src\" type=\"text\" tabindex=\"1\" />\n    <a class=\"btn-upload\" href=\"javascript:;\"\n      title=\"" + (this._t('uploadImage')) + "\" tabindex=\"-1\">\n      <span class=\"simditor-icon simditor-icon-upload\"></span>\n    </a>\n  </div>\n  <div class='settings-field'>\n    <label>" + (this._t('imageAlt')) + "</label>\n    <input class=\"image-alt\" id=\"image-alt\" type=\"text\" tabindex=\"1\" />\n  </div>\n  <div class=\"settings-field\">\n    <label>" + (this._t('imageSize')) + "</label>\n    <input class=\"image-size\" id=\"image-width\" type=\"text\" tabindex=\"2\" />\n    <span class=\"times\">×</span>\n    <input class=\"image-size\" id=\"image-height\" type=\"text\" tabindex=\"3\" />\n    <a class=\"btn-restore\" href=\"javascript:;\"\n      title=\"" + (this._t('restoreImageSize')) + "\" tabindex=\"-1\">\n      <span class=\"simditor-icon simditor-icon-undo\"></span>\n    </a>\n  </div>\n</div>";
+    this.el.addClass('image-popover').append(tpl);
+    this.srcEl = this.el.find('.image-src');
+    this.widthEl = this.el.find('#image-width');
+    this.heightEl = this.el.find('#image-height');
+    this.altEl = this.el.find('#image-alt');
+    this.srcEl.on('keydown', (function(_this) {
+      return function(e) {
+        var range;
+        if (!(e.which === 13 && !_this.target.hasClass('uploading'))) {
+          return;
+        }
+        e.preventDefault();
+        range = document.createRange();
+        _this.button.editor.selection.setRangeAfter(_this.target, range);
+        return _this.hide();
+      };
+    })(this));
+    this.srcEl.on('blur', (function(_this) {
+      return function(e) {
+        return _this._loadImage(_this.srcEl.val());
+      };
+    })(this));
+    this.el.find('.image-size').on('blur', (function(_this) {
+      return function(e) {
+        _this._resizeImg($(e.currentTarget));
+        return _this.el.data('popover').refresh();
+      };
+    })(this));
+    this.el.find('.image-size').on('keyup', (function(_this) {
+      return function(e) {
+        var inputEl;
+        inputEl = $(e.currentTarget);
+        if (!(e.which === 13 || e.which === 27 || e.which === 9)) {
+          return _this._resizeImg(inputEl, true);
+        }
+      };
+    })(this));
+    this.el.find('.image-size').on('keydown', (function(_this) {
+      return function(e) {
+        var $img, inputEl, range;
+        inputEl = $(e.currentTarget);
+        if (e.which === 13 || e.which === 27) {
+          e.preventDefault();
+          if (e.which === 13) {
+            _this._resizeImg(inputEl);
+          } else {
+            _this._restoreImg();
+          }
+          $img = _this.target;
+          _this.hide();
+          range = document.createRange();
+          return _this.button.editor.selection.setRangeAfter($img, range);
+        } else if (e.which === 9) {
+          return _this.el.data('popover').refresh();
+        }
+      };
+    })(this));
+    this.altEl.on('keydown', (function(_this) {
+      return function(e) {
+        var range;
+        if (e.which === 13) {
+          e.preventDefault();
+          range = document.createRange();
+          _this.button.editor.selection.setRangeAfter(_this.target, range);
+          return _this.hide();
+        }
+      };
+    })(this));
+    this.altEl.on('keyup', (function(_this) {
+      return function(e) {
+        if (e.which === 13 || e.which === 27 || e.which === 9) {
+          return;
+        }
+        _this.alt = _this.altEl.val();
+        return _this.target.attr('alt', _this.alt);
+      };
+    })(this));
+    this.el.find('.btn-restore').on('click', (function(_this) {
+      return function(e) {
+        _this._restoreImg();
+        return _this.el.data('popover').refresh();
+      };
+    })(this));
+    this.editor.on('valuechanged', (function(_this) {
+      return function(e) {
+        if (_this.active) {
+          return _this.refresh();
+        }
+      };
+    })(this));
+    return this._initUploader();
+  };
+
+  ImagePopover.prototype._initUploader = function() {
+    var $uploadBtn, createInput;
+    $uploadBtn = this.el.find('.btn-upload');
+    if (this.editor.uploader == null) {
+      $uploadBtn.remove();
+      return;
+    }
+    createInput = (function(_this) {
+      return function() {
+        if (_this.input) {
+          _this.input.remove();
+        }
+        return _this.input = $('<input/>', {
+          type: 'file',
+          title: _this._t('uploadImage'),
+          multiple: true,
+          accept: 'image/*'
+        }).appendTo($uploadBtn);
+      };
+    })(this);
+    createInput();
+    this.el.on('click mousedown', 'input[type=file]', function(e) {
+      return e.stopPropagation();
+    });
+    return this.el.on('change', 'input[type=file]', (function(_this) {
+      return function(e) {
+        _this.editor.uploader.upload(_this.input, {
+          inline: true,
+          img: _this.target
+        });
+        return createInput();
+      };
+    })(this));
+  };
+
+  ImagePopover.prototype._resizeImg = function(inputEl, onlySetVal) {
+    var height, value, width;
+    if (onlySetVal == null) {
+      onlySetVal = false;
+    }
+    value = inputEl.val() * 1;
+    if (!(this.target && ($.isNumeric(value) || value < 0))) {
+      return;
+    }
+    if (inputEl.is(this.widthEl)) {
+      width = value;
+      height = this.height * value / this.width;
+      this.heightEl.val(height);
+    } else {
+      height = value;
+      width = this.width * value / this.height;
+      this.widthEl.val(width);
+    }
+    if (!onlySetVal) {
+      this.target.attr({
+        width: width,
+        height: height
+      });
+      return this.editor.trigger('valuechanged');
+    }
+  };
+
+  ImagePopover.prototype._restoreImg = function() {
+    var ref, size;
+    size = ((ref = this.target.data('image-size')) != null ? ref.split(",") : void 0) || [this.width, this.height];
+    this.target.attr({
+      width: size[0] * 1,
+      height: size[1] * 1
+    });
+    this.widthEl.val(size[0]);
+    this.heightEl.val(size[1]);
+    return this.editor.trigger('valuechanged');
+  };
+
+  ImagePopover.prototype._loadImage = function(src, callback) {
+    if (/^data:image/.test(src) && !this.editor.uploader) {
+      if (callback) {
+        callback(false);
+      }
+      return;
+    }
+    if (this.target.attr('src') === src) {
+      return;
+    }
+    return this.button.loadImage(this.target, src, (function(_this) {
+      return function(img) {
+        var blob;
+        if (!img) {
+          return;
+        }
+        if (_this.active) {
+          _this.width = img.width;
+          _this.height = img.height;
+          _this.widthEl.val(_this.width);
+          _this.heightEl.val(_this.height);
+        }
+        if (/^data:image/.test(src)) {
+          blob = _this.editor.util.dataURLtoBlob(src);
+          blob.name = "Base64 Image.png";
+          _this.editor.uploader.upload(blob, {
+            inline: true,
+            img: _this.target
+          });
+        } else {
+          _this.editor.trigger('valuechanged');
+        }
+        if (callback) {
+          return callback(img);
+        }
+      };
+    })(this));
+  };
+
+  ImagePopover.prototype.show = function() {
+    var $img, args;
+    args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+    ImagePopover.__super__.show.apply(this, args);
+    $img = this.target;
+    this.width = $img.width();
+    this.height = $img.height();
+    this.alt = $img.attr('alt');
+    if ($img.hasClass('uploading')) {
+      return this.srcEl.val(this._t('uploading')).prop('disabled', true);
+    } else {
+      this.srcEl.val($img.attr('src')).prop('disabled', false);
+      this.widthEl.val(this.width);
+      this.heightEl.val(this.height);
+      return this.altEl.val(this.alt);
+    }
+  };
+
+  return ImagePopover;
+
+})(Popover);
+
+Simditor.Toolbar.addButton(ImageButton);
+
+IndentButton = (function(superClass) {
+  extend(IndentButton, superClass);
+
+  function IndentButton() {
+    return IndentButton.__super__.constructor.apply(this, arguments);
+  }
+
+  IndentButton.prototype.name = 'indent';
+
+  IndentButton.prototype.icon = 'indent';
+
+  IndentButton.prototype._init = function() {
+    this.title = this._t(this.name) + ' (Tab)';
+    return IndentButton.__super__._init.call(this);
+  };
+
+  IndentButton.prototype._status = function() {};
+
+  IndentButton.prototype.command = function() {
+    return this.editor.indentation.indent();
+  };
+
+  return IndentButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(IndentButton);
+
+OutdentButton = (function(superClass) {
+  extend(OutdentButton, superClass);
+
+  function OutdentButton() {
+    return OutdentButton.__super__.constructor.apply(this, arguments);
+  }
+
+  OutdentButton.prototype.name = 'outdent';
+
+  OutdentButton.prototype.icon = 'outdent';
+
+  OutdentButton.prototype._init = function() {
+    this.title = this._t(this.name) + ' (Shift + Tab)';
+    return OutdentButton.__super__._init.call(this);
+  };
+
+  OutdentButton.prototype._status = function() {};
+
+  OutdentButton.prototype.command = function() {
+    return this.editor.indentation.indent(true);
+  };
+
+  return OutdentButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(OutdentButton);
+
+HrButton = (function(superClass) {
+  extend(HrButton, superClass);
+
+  function HrButton() {
+    return HrButton.__super__.constructor.apply(this, arguments);
+  }
+
+  HrButton.prototype.name = 'hr';
+
+  HrButton.prototype.icon = 'minus';
+
+  HrButton.prototype.htmlTag = 'hr';
+
+  HrButton.prototype._status = function() {};
+
+  HrButton.prototype.command = function() {
+    var $hr, $newBlock, $nextBlock, $rootBlock;
+    $rootBlock = this.editor.selection.rootNodes().first();
+    $nextBlock = $rootBlock.next();
+    if ($nextBlock.length > 0) {
+      this.editor.selection.save();
+    } else {
+      $newBlock = $('<p/>').append(this.editor.util.phBr);
+    }
+    $hr = $('<hr/>').insertAfter($rootBlock);
+    if ($newBlock) {
+      $newBlock.insertAfter($hr);
+      this.editor.selection.setRangeAtStartOf($newBlock);
+    } else {
+      this.editor.selection.restore();
+    }
+    return this.editor.trigger('valuechanged');
+  };
+
+  return HrButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(HrButton);
+
+TableButton = (function(superClass) {
+  extend(TableButton, superClass);
+
+  function TableButton() {
+    return TableButton.__super__.constructor.apply(this, arguments);
+  }
+
+  TableButton.prototype.name = 'table';
+
+  TableButton.prototype.icon = 'table';
+
+  TableButton.prototype.htmlTag = 'table';
+
+  TableButton.prototype.disableTag = 'pre, li, blockquote';
+
+  TableButton.prototype.menu = true;
+
+  TableButton.prototype._init = function() {
+    TableButton.__super__._init.call(this);
+    $.merge(this.editor.formatter._allowedTags, ['thead', 'th', 'tbody', 'tr', 'td', 'colgroup', 'col']);
+    $.extend(this.editor.formatter._allowedAttributes, {
+      td: ['rowspan', 'colspan'],
+      col: ['width']
+    });
+    $.extend(this.editor.formatter._allowedStyles, {
+      td: ['text-align'],
+      th: ['text-align']
+    });
+    this._initShortcuts();
+    this.editor.on('decorate', (function(_this) {
+      return function(e, $el) {
+        return $el.find('table').each(function(i, table) {
+          return _this.decorate($(table));
+        });
+      };
+    })(this));
+    this.editor.on('undecorate', (function(_this) {
+      return function(e, $el) {
+        return $el.find('table').each(function(i, table) {
+          return _this.undecorate($(table));
+        });
+      };
+    })(this));
+    this.editor.on('selectionchanged.table', (function(_this) {
+      return function(e) {
+        var $container, range;
+        _this.editor.body.find('.simditor-table td, .simditor-table th').removeClass('active');
+        range = _this.editor.selection.range();
+        if (!range) {
+          return;
+        }
+        $container = _this.editor.selection.containerNode();
+        if (range.collapsed && $container.is('.simditor-table')) {
+          if (_this.editor.selection.rangeAtStartOf($container)) {
+            $container = $container.find('th:first');
+          } else {
+            $container = $container.find('td:last');
+          }
+          _this.editor.selection.setRangeAtEndOf($container);
+        }
+        return $container.closest('td, th', _this.editor.body).addClass('active');
+      };
+    })(this));
+    this.editor.on('blur.table', (function(_this) {
+      return function(e) {
+        return _this.editor.body.find('.simditor-table td, .simditor-table th').removeClass('active');
+      };
+    })(this));
+    this.editor.keystroke.add('up', 'td', (function(_this) {
+      return function(e, $node) {
+        _this._tdNav($node, 'up');
+        return true;
+      };
+    })(this));
+    this.editor.keystroke.add('up', 'th', (function(_this) {
+      return function(e, $node) {
+        _this._tdNav($node, 'up');
+        return true;
+      };
+    })(this));
+    this.editor.keystroke.add('down', 'td', (function(_this) {
+      return function(e, $node) {
+        _this._tdNav($node, 'down');
+        return true;
+      };
+    })(this));
+    return this.editor.keystroke.add('down', 'th', (function(_this) {
+      return function(e, $node) {
+        _this._tdNav($node, 'down');
+        return true;
+      };
+    })(this));
+  };
+
+  TableButton.prototype._tdNav = function($td, direction) {
+    var $anotherTr, $tr, action, anotherTag, index, parentTag, ref;
+    if (direction == null) {
+      direction = 'up';
+    }
+    action = direction === 'up' ? 'prev' : 'next';
+    ref = direction === 'up' ? ['tbody', 'thead'] : ['thead', 'tbody'], parentTag = ref[0], anotherTag = ref[1];
+    $tr = $td.parent('tr');
+    $anotherTr = this["_" + action + "Row"]($tr);
+    if (!($anotherTr.length > 0)) {
+      return true;
+    }
+    index = $tr.find('td, th').index($td);
+    return this.editor.selection.setRangeAtEndOf($anotherTr.find('td, th').eq(index));
+  };
+
+  TableButton.prototype._nextRow = function($tr) {
+    var $nextTr;
+    $nextTr = $tr.next('tr');
+    if ($nextTr.length < 1 && $tr.parent('thead').length > 0) {
+      $nextTr = $tr.parent('thead').next('tbody').find('tr:first');
+    }
+    return $nextTr;
+  };
+
+  TableButton.prototype._prevRow = function($tr) {
+    var $prevTr;
+    $prevTr = $tr.prev('tr');
+    if ($prevTr.length < 1 && $tr.parent('tbody').length > 0) {
+      $prevTr = $tr.parent('tbody').prev('thead').find('tr');
+    }
+    return $prevTr;
+  };
+
+  TableButton.prototype.initResize = function($table) {
+    var $colgroup, $resizeHandle, $wrapper;
+    $wrapper = $table.parent('.simditor-table');
+    $colgroup = $table.find('colgroup');
+    if ($colgroup.length < 1) {
+      $colgroup = $('<colgroup/>').prependTo($table);
+      $table.find('thead tr th').each(function(i, td) {
+        var $col;
+        return $col = $('<col/>').appendTo($colgroup);
+      });
+      this.refreshTableWidth($table);
+    }
+    $resizeHandle = $('<div />', {
+      "class": 'simditor-resize-handle',
+      contenteditable: 'false'
+    }).appendTo($wrapper);
+    $wrapper.on('mousemove', 'td, th', function(e) {
+      var $col, $td, index, ref, ref1, x;
+      if ($wrapper.hasClass('resizing')) {
+        return;
+      }
+      $td = $(e.currentTarget);
+      x = e.pageX - $(e.currentTarget).offset().left;
+      if (x < 5 && $td.prev().length > 0) {
+        $td = $td.prev();
+      }
+      if ($td.next('td, th').length < 1) {
+        $resizeHandle.hide();
+        return;
+      }
+      if ((ref = $resizeHandle.data('td')) != null ? ref.is($td) : void 0) {
+        $resizeHandle.show();
+        return;
+      }
+      index = $td.parent().find('td, th').index($td);
+      $col = $colgroup.find('col').eq(index);
+      if ((ref1 = $resizeHandle.data('col')) != null ? ref1.is($col) : void 0) {
+        $resizeHandle.show();
+        return;
+      }
+      return $resizeHandle.css('left', $td.position().left + $td.outerWidth() - 5).data('td', $td).data('col', $col).show();
+    });
+    $wrapper.on('mouseleave', function(e) {
+      return $resizeHandle.hide();
+    });
+    return $wrapper.on('mousedown', '.simditor-resize-handle', function(e) {
+      var $handle, $leftCol, $leftTd, $rightCol, $rightTd, minWidth, startHandleLeft, startLeftWidth, startRightWidth, startX, tableWidth;
+      $handle = $(e.currentTarget);
+      $leftTd = $handle.data('td');
+      $leftCol = $handle.data('col');
+      $rightTd = $leftTd.next('td, th');
+      $rightCol = $leftCol.next('col');
+      startX = e.pageX;
+      startLeftWidth = $leftTd.outerWidth() * 1;
+      startRightWidth = $rightTd.outerWidth() * 1;
+      startHandleLeft = parseFloat($handle.css('left'));
+      tableWidth = $leftTd.closest('table').width();
+      minWidth = 50;
+      $(document).on('mousemove.simditor-resize-table', function(e) {
+        var deltaX, leftWidth, rightWidth;
+        deltaX = e.pageX - startX;
+        leftWidth = startLeftWidth + deltaX;
+        rightWidth = startRightWidth - deltaX;
+        if (leftWidth < minWidth) {
+          leftWidth = minWidth;
+          deltaX = minWidth - startLeftWidth;
+          rightWidth = startRightWidth - deltaX;
+        } else if (rightWidth < minWidth) {
+          rightWidth = minWidth;
+          deltaX = startRightWidth - minWidth;
+          leftWidth = startLeftWidth + deltaX;
+        }
+        $leftCol.attr('width', (leftWidth / tableWidth * 100) + '%');
+        $rightCol.attr('width', (rightWidth / tableWidth * 100) + '%');
+        return $handle.css('left', startHandleLeft + deltaX);
+      });
+      $(document).one('mouseup.simditor-resize-table', function(e) {
+        $(document).off('.simditor-resize-table');
+        return $wrapper.removeClass('resizing');
+      });
+      $wrapper.addClass('resizing');
+      return false;
+    });
+  };
+
+  TableButton.prototype._initShortcuts = function() {
+    this.editor.hotkeys.add('ctrl+alt+up', (function(_this) {
+      return function(e) {
+        _this.editMenu.find('.menu-item[data-param=insertRowAbove]').click();
+        return false;
+      };
+    })(this));
+    this.editor.hotkeys.add('ctrl+alt+down', (function(_this) {
+      return function(e) {
+        _this.editMenu.find('.menu-item[data-param=insertRowBelow]').click();
+        return false;
+      };
+    })(this));
+    this.editor.hotkeys.add('ctrl+alt+left', (function(_this) {
+      return function(e) {
+        _this.editMenu.find('.menu-item[data-param=insertColLeft]').click();
+        return false;
+      };
+    })(this));
+    return this.editor.hotkeys.add('ctrl+alt+right', (function(_this) {
+      return function(e) {
+        _this.editMenu.find('.menu-item[data-param=insertColRight]').click();
+        return false;
+      };
+    })(this));
+  };
+
+  TableButton.prototype.decorate = function($table) {
+    var $headRow, $tbody, $thead;
+    if ($table.parent('.simditor-table').length > 0) {
+      this.undecorate($table);
+    }
+    $table.wrap('<div class="simditor-table"></div>');
+    if ($table.find('thead').length < 1) {
+      $thead = $('<thead />');
+      $headRow = $table.find('tr').first();
+      $thead.append($headRow);
+      this._changeCellTag($headRow, 'th');
+      $tbody = $table.find('tbody');
+      if ($tbody.length > 0) {
+        $tbody.before($thead);
+      } else {
+        $table.prepend($thead);
+      }
+    }
+    this.initResize($table);
+    return $table.parent();
+  };
+
+  TableButton.prototype.undecorate = function($table) {
+    if (!($table.parent('.simditor-table').length > 0)) {
+      return;
+    }
+    return $table.parent().replaceWith($table);
+  };
+
+  TableButton.prototype.renderMenu = function() {
+    var $table;
+    $("<div class=\"menu-create-table\">\n</div>\n<div class=\"menu-edit-table\">\n  <ul>\n    <li>\n      <a tabindex=\"-1\" unselectable=\"on\" class=\"menu-item\"\n        href=\"javascript:;\" data-param=\"deleteRow\">\n        <span>" + (this._t('deleteRow')) + "</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex=\"-1\" unselectable=\"on\" class=\"menu-item\"\n        href=\"javascript:;\" data-param=\"insertRowAbove\">\n        <span>" + (this._t('insertRowAbove')) + " ( Ctrl + Alt + ↑ )</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex=\"-1\" unselectable=\"on\" class=\"menu-item\"\n        href=\"javascript:;\" data-param=\"insertRowBelow\">\n        <span>" + (this._t('insertRowBelow')) + " ( Ctrl + Alt + ↓ )</span>\n      </a>\n    </li>\n    <li><span class=\"separator\"></span></li>\n    <li>\n      <a tabindex=\"-1\" unselectable=\"on\" class=\"menu-item\"\n        href=\"javascript:;\" data-param=\"deleteCol\">\n        <span>" + (this._t('deleteColumn')) + "</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex=\"-1\" unselectable=\"on\" class=\"menu-item\"\n        href=\"javascript:;\" data-param=\"insertColLeft\">\n        <span>" + (this._t('insertColumnLeft')) + " ( Ctrl + Alt + ← )</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex=\"-1\" unselectable=\"on\" class=\"menu-item\"\n        href=\"javascript:;\" data-param=\"insertColRight\">\n        <span>" + (this._t('insertColumnRight')) + " ( Ctrl + Alt + → )</span>\n      </a>\n    </li>\n    <li><span class=\"separator\"></span></li>\n    <li>\n      <a tabindex=\"-1\" unselectable=\"on\" class=\"menu-item\"\n        href=\"javascript:;\" data-param=\"deleteTable\">\n        <span>" + (this._t('deleteTable')) + "</span>\n      </a>\n    </li>\n  </ul>\n</div>").appendTo(this.menuWrapper);
+    this.createMenu = this.menuWrapper.find('.menu-create-table');
+    this.editMenu = this.menuWrapper.find('.menu-edit-table');
+    $table = this.createTable(6, 6).appendTo(this.createMenu);
+    this.createMenu.on('mouseenter', 'td, th', (function(_this) {
+      return function(e) {
+        var $td, $tr, $trs, num;
+        _this.createMenu.find('td, th').removeClass('selected');
+        $td = $(e.currentTarget);
+        $tr = $td.parent();
+        num = $tr.find('td, th').index($td) + 1;
+        $trs = $tr.prevAll('tr').addBack();
+        if ($tr.parent().is('tbody')) {
+          $trs = $trs.add($table.find('thead tr'));
+        }
+        return $trs.find("td:lt(" + num + "), th:lt(" + num + ")").addClass('selected');
+      };
+    })(this));
+    this.createMenu.on('mouseleave', function(e) {
+      return $(e.currentTarget).find('td, th').removeClass('selected');
+    });
+    return this.createMenu.on('mousedown', 'td, th', (function(_this) {
+      return function(e) {
+        var $closestBlock, $td, $tr, colNum, rowNum;
+        _this.wrapper.removeClass('menu-on');
+        if (!_this.editor.inputManager.focused) {
+          return;
+        }
+        $td = $(e.currentTarget);
+        $tr = $td.parent();
+        colNum = $tr.find('td').index($td) + 1;
+        rowNum = $tr.prevAll('tr').length + 1;
+        if ($tr.parent().is('tbody')) {
+          rowNum += 1;
+        }
+        $table = _this.createTable(rowNum, colNum, true);
+        $closestBlock = _this.editor.selection.blockNodes().last();
+        if (_this.editor.util.isEmptyNode($closestBlock)) {
+          $closestBlock.replaceWith($table);
+        } else {
+          $closestBlock.after($table);
+        }
+        _this.decorate($table);
+        _this.editor.selection.setRangeAtStartOf($table.find('th:first'));
+        _this.editor.trigger('valuechanged');
+        return false;
+      };
+    })(this));
+  };
+
+  TableButton.prototype.createTable = function(row, col, phBr) {
+    var $table, $tbody, $td, $thead, $tr, c, k, l, r, ref, ref1;
+    $table = $('<table/>');
+    $thead = $('<thead/>').appendTo($table);
+    $tbody = $('<tbody/>').appendTo($table);
+    for (r = k = 0, ref = row; 0 <= ref ? k < ref : k > ref; r = 0 <= ref ? ++k : --k) {
+      $tr = $('<tr/>');
+      $tr.appendTo(r === 0 ? $thead : $tbody);
+      for (c = l = 0, ref1 = col; 0 <= ref1 ? l < ref1 : l > ref1; c = 0 <= ref1 ? ++l : --l) {
+        $td = $(r === 0 ? '<th/>' : '<td/>').appendTo($tr);
+        if (phBr) {
+          $td.append(this.editor.util.phBr);
+        }
+      }
+    }
+    return $table;
+  };
+
+  TableButton.prototype.refreshTableWidth = function($table) {
+    var cols, tableWidth;
+    tableWidth = $table.width();
+    cols = $table.find('col');
+    return $table.find('thead tr th').each(function(i, td) {
+      var $col;
+      $col = cols.eq(i);
+      return $col.attr('width', ($(td).outerWidth() / tableWidth * 100) + '%');
+    });
+  };
+
+  TableButton.prototype.setActive = function(active) {
+    TableButton.__super__.setActive.call(this, active);
+    if (active) {
+      this.createMenu.hide();
+      return this.editMenu.show();
+    } else {
+      this.createMenu.show();
+      return this.editMenu.hide();
+    }
+  };
+
+  TableButton.prototype._changeCellTag = function($tr, tagName) {
+    return $tr.find('td, th').each(function(i, cell) {
+      var $cell;
+      $cell = $(cell);
+      return $cell.replaceWith("<" + tagName + ">" + ($cell.html()) + "</" + tagName + ">");
+    });
+  };
+
+  TableButton.prototype.deleteRow = function($td) {
+    var $newTr, $tr, index;
+    $tr = $td.parent('tr');
+    if ($tr.closest('table').find('tr').length < 1) {
+      return this.deleteTable($td);
+    } else {
+      $newTr = this._nextRow($tr);
+      if (!($newTr.length > 0)) {
+        $newTr = this._prevRow($tr);
+      }
+      index = $tr.find('td, th').index($td);
+      if ($tr.parent().is('thead')) {
+        $newTr.appendTo($tr.parent());
+        this._changeCellTag($newTr, 'th');
+      }
+      $tr.remove();
+      return this.editor.selection.setRangeAtEndOf($newTr.find('td, th').eq(index));
+    }
+  };
+
+  TableButton.prototype.insertRow = function($td, direction) {
+    var $newTr, $table, $tr, cellTag, colNum, i, index, k, ref;
+    if (direction == null) {
+      direction = 'after';
+    }
+    $tr = $td.parent('tr');
+    $table = $tr.closest('table');
+    colNum = 0;
+    $table.find('tr').each(function(i, tr) {
+      return colNum = Math.max(colNum, $(tr).find('td').length);
+    });
+    index = $tr.find('td, th').index($td);
+    $newTr = $('<tr/>');
+    cellTag = 'td';
+    if (direction === 'after' && $tr.parent().is('thead')) {
+      $tr.parent().next('tbody').prepend($newTr);
+    } else if (direction === 'before' && $tr.parent().is('thead')) {
+      $tr.before($newTr);
+      $tr.parent().next('tbody').prepend($tr);
+      this._changeCellTag($tr, 'td');
+      cellTag = 'th';
+    } else {
+      $tr[direction]($newTr);
+    }
+    for (i = k = 1, ref = colNum; 1 <= ref ? k <= ref : k >= ref; i = 1 <= ref ? ++k : --k) {
+      $("<" + cellTag + "/>").append(this.editor.util.phBr).appendTo($newTr);
+    }
+    return this.editor.selection.setRangeAtStartOf($newTr.find('td, th').eq(index));
+  };
+
+  TableButton.prototype.deleteCol = function($td) {
+    var $newTd, $table, $tr, index, noOtherCol, noOtherRow;
+    $tr = $td.parent('tr');
+    noOtherRow = $tr.closest('table').find('tr').length < 2;
+    noOtherCol = $td.siblings('td, th').length < 1;
+    if (noOtherRow && noOtherCol) {
+      return this.deleteTable($td);
+    } else {
+      index = $tr.find('td, th').index($td);
+      $newTd = $td.next('td, th');
+      if (!($newTd.length > 0)) {
+        $newTd = $tr.prev('td, th');
+      }
+      $table = $tr.closest('table');
+      $table.find('col').eq(index).remove();
+      $table.find('tr').each(function(i, tr) {
+        return $(tr).find('td, th').eq(index).remove();
+      });
+      this.refreshTableWidth($table);
+      return this.editor.selection.setRangeAtEndOf($newTd);
+    }
+  };
+
+  TableButton.prototype.insertCol = function($td, direction) {
+    var $col, $newCol, $newTd, $table, $tr, index, tableWidth, width;
+    if (direction == null) {
+      direction = 'after';
+    }
+    $tr = $td.parent('tr');
+    index = $tr.find('td, th').index($td);
+    $table = $td.closest('table');
+    $col = $table.find('col').eq(index);
+    $table.find('tr').each((function(_this) {
+      return function(i, tr) {
+        var $newTd, cellTag;
+        cellTag = $(tr).parent().is('thead') ? 'th' : 'td';
+        $newTd = $("<" + cellTag + "/>").append(_this.editor.util.phBr);
+        return $(tr).find('td, th').eq(index)[direction]($newTd);
+      };
+    })(this));
+    $newCol = $('<col/>');
+    $col[direction]($newCol);
+    tableWidth = $table.width();
+    width = Math.max(parseFloat($col.attr('width')) / 2, 50 / tableWidth * 100);
+    $col.attr('width', width + '%');
+    $newCol.attr('width', width + '%');
+    this.refreshTableWidth($table);
+    $newTd = direction === 'after' ? $td.next('td, th') : $td.prev('td, th');
+    return this.editor.selection.setRangeAtStartOf($newTd);
+  };
+
+  TableButton.prototype.deleteTable = function($td) {
+    var $block, $table;
+    $table = $td.closest('.simditor-table');
+    $block = $table.next('p');
+    $table.remove();
+    if ($block.length > 0) {
+      return this.editor.selection.setRangeAtStartOf($block);
+    }
+  };
+
+  TableButton.prototype.command = function(param) {
+    var $td;
+    $td = this.editor.selection.containerNode().closest('td, th');
+    if (!($td.length > 0)) {
+      return;
+    }
+    if (param === 'deleteRow') {
+      this.deleteRow($td);
+    } else if (param === 'insertRowAbove') {
+      this.insertRow($td, 'before');
+    } else if (param === 'insertRowBelow') {
+      this.insertRow($td);
+    } else if (param === 'deleteCol') {
+      this.deleteCol($td);
+    } else if (param === 'insertColLeft') {
+      this.insertCol($td, 'before');
+    } else if (param === 'insertColRight') {
+      this.insertCol($td);
+    } else if (param === 'deleteTable') {
+      this.deleteTable($td);
+    } else {
+      return;
+    }
+    return this.editor.trigger('valuechanged');
+  };
+
+  return TableButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(TableButton);
+
+StrikethroughButton = (function(superClass) {
+  extend(StrikethroughButton, superClass);
+
+  function StrikethroughButton() {
+    return StrikethroughButton.__super__.constructor.apply(this, arguments);
+  }
+
+  StrikethroughButton.prototype.name = 'strikethrough';
+
+  StrikethroughButton.prototype.icon = 'strikethrough';
+
+  StrikethroughButton.prototype.htmlTag = 'strike';
+
+  StrikethroughButton.prototype.disableTag = 'pre';
+
+  StrikethroughButton.prototype._activeStatus = function() {
+    var active;
+    active = document.queryCommandState('strikethrough') === true;
+    this.setActive(active);
+    return this.active;
+  };
+
+  StrikethroughButton.prototype.command = function() {
+    document.execCommand('strikethrough');
+    if (!this.editor.util.support.oninput) {
+      this.editor.trigger('valuechanged');
+    }
+    return $(document).trigger('selectionchange');
+  };
+
+  return StrikethroughButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(StrikethroughButton);
+
+AlignmentButton = (function(superClass) {
+  extend(AlignmentButton, superClass);
+
+  function AlignmentButton() {
+    return AlignmentButton.__super__.constructor.apply(this, arguments);
+  }
+
+  AlignmentButton.prototype.name = "alignment";
+
+  AlignmentButton.prototype.icon = 'align-left';
+
+  AlignmentButton.prototype.htmlTag = 'p, h1, h2, h3, h4, td, th';
+
+  AlignmentButton.prototype._init = function() {
+    this.menu = [
+      {
+        name: 'left',
+        text: this._t('alignLeft'),
+        icon: 'align-left',
+        param: 'left'
+      }, {
+        name: 'center',
+        text: this._t('alignCenter'),
+        icon: 'align-center',
+        param: 'center'
+      }, {
+        name: 'right',
+        text: this._t('alignRight'),
+        icon: 'align-right',
+        param: 'right'
+      }
+    ];
+    return AlignmentButton.__super__._init.call(this);
+  };
+
+  AlignmentButton.prototype.setActive = function(active, align) {
+    if (align == null) {
+      align = 'left';
+    }
+    if (align !== 'left' && align !== 'center' && align !== 'right') {
+      align = 'left';
+    }
+    if (align === 'left') {
+      AlignmentButton.__super__.setActive.call(this, false);
+    } else {
+      AlignmentButton.__super__.setActive.call(this, active);
+    }
+    this.el.removeClass('align-left align-center align-right');
+    if (active) {
+      this.el.addClass('align-' + align);
+    }
+    this.setIcon('align-' + align);
+    return this.menuEl.find('.menu-item').show().end().find('.menu-item-' + align).hide();
+  };
+
+  AlignmentButton.prototype._status = function() {
+    this.nodes = this.editor.selection.nodes().filter(this.htmlTag);
+    if (this.nodes.length < 1) {
+      this.setDisabled(true);
+      return this.setActive(false);
+    } else {
+      this.setDisabled(false);
+      return this.setActive(true, this.nodes.first().css('text-align'));
+    }
+  };
+
+  AlignmentButton.prototype.command = function(align) {
+    if (align !== 'left' && align !== 'center' && align !== 'right') {
+      throw new Error("simditor alignment button: invalid align " + align);
+    }
+    this.nodes.css({
+      'text-align': align === 'left' ? '' : align
+    });
+    this.editor.trigger('valuechanged');
+    return this.editor.inputManager.throttledSelectionChanged();
+  };
+
+  return AlignmentButton;
+
+})(Button);
+
+Simditor.Toolbar.addButton(AlignmentButton);
+
+return Simditor;
+
+}));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 7 - 0
admin/static/lib/simditor/scripts/simditor.min.js


+ 261 - 0
admin/static/lib/simditor/scripts/uploader.js

@@ -0,0 +1,261 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module unless amdModuleId is set
+    define('simple-uploader', ["jquery","simple-module"], function ($, SimpleModule) {
+      return (root['uploader'] = factory($, SimpleModule));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"),require("simple-module"));
+  } else {
+    root.simple = root.simple || {};
+    root.simple['uploader'] = factory(jQuery,SimpleModule);
+  }
+}(this, function ($, SimpleModule) {
+
+var Uploader, uploader,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+Uploader = (function(superClass) {
+  extend(Uploader, superClass);
+
+  function Uploader() {
+    return Uploader.__super__.constructor.apply(this, arguments);
+  }
+
+  Uploader.count = 0;
+
+  Uploader.prototype.opts = {
+    url: '',
+    params: null,
+    fileKey: 'upload_file',
+    connectionCount: 3
+  };
+
+  Uploader.prototype._init = function() {
+    this.files = [];
+    this.queue = [];
+    this.id = ++Uploader.count;
+    this.on('uploadcomplete', (function(_this) {
+      return function(e, file) {
+        _this.files.splice($.inArray(file, _this.files), 1);
+        if (_this.queue.length > 0 && _this.files.length < _this.opts.connectionCount) {
+          return _this.upload(_this.queue.shift());
+        } else {
+          return _this.uploading = false;
+        }
+      };
+    })(this));
+    return $(window).on('beforeunload.uploader-' + this.id, (function(_this) {
+      return function(e) {
+        if (!_this.uploading) {
+          return;
+        }
+        e.originalEvent.returnValue = _this._t('leaveConfirm');
+        return _this._t('leaveConfirm');
+      };
+    })(this));
+  };
+
+  Uploader.prototype.generateId = (function() {
+    var id;
+    id = 0;
+    return function() {
+      return id += 1;
+    };
+  })();
+
+  Uploader.prototype.upload = function(file, opts) {
+    var f, i, key, len;
+    if (opts == null) {
+      opts = {};
+    }
+    if (file == null) {
+      return;
+    }
+    if ($.isArray(file) || file instanceof FileList) {
+      for (i = 0, len = file.length; i < len; i++) {
+        f = file[i];
+        this.upload(f, opts);
+      }
+    } else if ($(file).is('input:file')) {
+      key = $(file).attr('name');
+      if (key) {
+        opts.fileKey = key;
+      }
+      this.upload($.makeArray($(file)[0].files), opts);
+    } else if (!file.id || !file.obj) {
+      file = this.getFile(file);
+    }
+    if (!(file && file.obj)) {
+      return;
+    }
+    $.extend(file, opts);
+    if (this.files.length >= this.opts.connectionCount) {
+      this.queue.push(file);
+      return;
+    }
+    if (this.triggerHandler('beforeupload', [file]) === false) {
+      return;
+    }
+    this.files.push(file);
+    this._xhrUpload(file);
+    return this.uploading = true;
+  };
+
+  Uploader.prototype.getFile = function(fileObj) {
+    var name, ref, ref1;
+    if (fileObj instanceof window.File || fileObj instanceof window.Blob) {
+      name = (ref = fileObj.fileName) != null ? ref : fileObj.name;
+    } else {
+      return null;
+    }
+    return {
+      id: this.generateId(),
+      url: this.opts.url,
+      params: this.opts.params,
+      fileKey: this.opts.fileKey,
+      name: name,
+      size: (ref1 = fileObj.fileSize) != null ? ref1 : fileObj.size,
+      ext: name ? name.split('.').pop().toLowerCase() : '',
+      obj: fileObj
+    };
+  };
+
+  Uploader.prototype._xhrUpload = function(file) {
+    var formData, k, ref, v;
+    formData = new FormData();
+    formData.append(file.fileKey, file.obj);
+    formData.append("original_filename", file.name);
+    if (file.params) {
+      ref = file.params;
+      for (k in ref) {
+        v = ref[k];
+        formData.append(k, v);
+      }
+    }
+    return file.xhr = $.ajax({
+      url: file.url,
+      data: formData,
+      processData: false,
+      contentType: false,
+      type: 'POST',
+      headers: {
+        'X-File-Name': encodeURIComponent(file.name)
+      },
+      xhr: function() {
+        var req;
+        req = $.ajaxSettings.xhr();
+        if (req) {
+          req.upload.onprogress = (function(_this) {
+            return function(e) {
+              return _this.progress(e);
+            };
+          })(this);
+        }
+        return req;
+      },
+      progress: (function(_this) {
+        return function(e) {
+          if (!e.lengthComputable) {
+            return;
+          }
+          return _this.trigger('uploadprogress', [file, e.loaded, e.total]);
+        };
+      })(this),
+      error: (function(_this) {
+        return function(xhr, status, err) {
+          return _this.trigger('uploaderror', [file, xhr, status]);
+        };
+      })(this),
+      success: (function(_this) {
+        return function(result) {
+          _this.trigger('uploadprogress', [file, file.size, file.size]);
+          _this.trigger('uploadsuccess', [file, result]);
+          return $(document).trigger('uploadsuccess', [file, result, _this]);
+        };
+      })(this),
+      complete: (function(_this) {
+        return function(xhr, status) {
+          return _this.trigger('uploadcomplete', [file, xhr.responseText]);
+        };
+      })(this)
+    });
+  };
+
+  Uploader.prototype.cancel = function(file) {
+    var f, i, len, ref;
+    if (!file.id) {
+      ref = this.files;
+      for (i = 0, len = ref.length; i < len; i++) {
+        f = ref[i];
+        if (f.id === file * 1) {
+          file = f;
+          break;
+        }
+      }
+    }
+    this.trigger('uploadcancel', [file]);
+    if (file.xhr) {
+      file.xhr.abort();
+    }
+    return file.xhr = null;
+  };
+
+  Uploader.prototype.readImageFile = function(fileObj, callback) {
+    var fileReader, img;
+    if (!$.isFunction(callback)) {
+      return;
+    }
+    img = new Image();
+    img.onload = function() {
+      return callback(img);
+    };
+    img.onerror = function() {
+      return callback();
+    };
+    if (window.FileReader && FileReader.prototype.readAsDataURL && /^image/.test(fileObj.type)) {
+      fileReader = new FileReader();
+      fileReader.onload = function(e) {
+        return img.src = e.target.result;
+      };
+      return fileReader.readAsDataURL(fileObj);
+    } else {
+      return callback();
+    }
+  };
+
+  Uploader.prototype.destroy = function() {
+    var file, i, len, ref;
+    this.queue.length = 0;
+    ref = this.files;
+    for (i = 0, len = ref.length; i < len; i++) {
+      file = ref[i];
+      this.cancel(file);
+    }
+    $(window).off('.uploader-' + this.id);
+    return $(document).off('.uploader-' + this.id);
+  };
+
+  Uploader.i18n = {
+    'zh-CN': {
+      leaveConfirm: '正在上传文件,如果离开上传会自动取消'
+    }
+  };
+
+  Uploader.locale = 'zh-CN';
+
+  return Uploader;
+
+})(SimpleModule);
+
+uploader = function(opts) {
+  return new Uploader(opts);
+};
+
+return uploader;
+
+}));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 6 - 0
admin/static/lib/simditor/scripts/uploader.min.js


+ 696 - 0
admin/static/lib/simditor/styles/editor.scss

@@ -0,0 +1,696 @@
+@charset "UTF-8";
+
+$simditor-button-height: 40px;
+$simditor-button-width: 46px;
+
+.simditor {
+  position: relative;
+  border: 1px solid #c9d8db;
+
+  .simditor-wrapper {
+    position: relative;
+    background: #ffffff;
+
+    & > textarea {
+      display: none !important;
+      width: 100%;
+      box-sizing: border-box;
+      font-family: monaco;
+      font-size: 16px;
+      line-height: 1.6;
+      border: none;
+      padding: 22px 15px 40px;
+      min-height: 300px;
+      outline: none;
+      background: transparent;
+      resize: none;
+    }
+
+    .simditor-placeholder {
+      display: none;
+      position: absolute;
+      left: 0;
+      z-index: 0;
+      padding: 22px 15px;
+      font-size: 16px;
+      font-family: arial, sans-serif;
+      line-height: 1.5;
+      color: #999999;
+      background: transparent;
+    }
+
+    &.toolbar-floating {
+      .simditor-toolbar {
+        position: fixed;
+        top: 0;
+        z-index: 10;
+        box-shadow: 0 0 6px rgba(0,0,0,0.1);
+      }
+    }
+
+    .simditor-image-loading {
+      width: 100%;
+      height: 100%;
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 2;
+
+      .progress {
+        width: 100%;
+        height: 100%;
+        background: rgba(0,0,0,0.4);
+        position: absolute;
+        bottom: 0;
+        left: 0;
+      }
+    }
+  }
+
+  .simditor-body {
+    padding: 22px 15px 40px;
+    min-height: 300px;
+    outline: none;
+    cursor: text;
+    position: relative;
+    z-index: 1;
+    background: transparent;
+
+    a.selected {
+      background: #b3d4fd;
+    }
+
+    a.simditor-mention {
+      cursor: pointer;
+    }
+
+    .simditor-table {
+      position: relative;
+
+      &.resizing {
+        cursor: col-resize;
+      }
+
+      .simditor-resize-handle {
+        position: absolute;
+        left: 0;
+        top: 0;
+        width: 10px;
+        height: 100%;
+        cursor: col-resize;
+      }
+    }
+
+    pre {
+      /*min-height: 28px;*/
+      box-sizing: border-box;
+      -moz-box-sizing: border-box;
+      word-wrap: break-word!important;
+      white-space: pre-wrap!important;
+    }
+
+    img {
+      cursor: pointer;
+
+      &.selected {
+        box-shadow: 0 0 0 4px #cccccc;
+      }
+    }
+  }
+
+  .simditor-paste-bin {
+    position: fixed;
+    bottom: 10px;
+    right: 10px;
+    width: 1px;
+    height: 20px;
+    font-size: 1px;
+    line-height: 1px;
+    overflow: hidden;
+    padding: 0;
+    margin: 0;
+    opacity: 0;
+    -webkit-user-select: text;
+  }
+
+  .simditor-toolbar {
+    border-bottom: 1px solid #eeeeee;
+    background: #ffffff;
+    width: 100%;
+
+    & > ul {
+      margin: 0;
+      padding: 0 0 0 6px;
+      list-style: none;
+
+      & > li {
+        position: relative;
+        display: inline-block;
+        font-size: 0;
+
+        & > span.separator {
+          display: inline-block;
+          background: #cfcfcf;
+          width: 1px;
+          height: 18px;
+          margin: ($simditor-button-height - 18px) / 2 15px;
+          vertical-align: middle;
+        }
+
+        & > .toolbar-item {
+          display: inline-block;
+          width: $simditor-button-width;
+          height: $simditor-button-height;
+          outline: none;
+          color: #333333;
+          font-size: 15px;
+          line-height: $simditor-button-height;
+          vertical-align: middle;
+          text-align: center;
+          text-decoration: none;
+
+          span {
+            opacity: 0.6;
+
+            &.simditor-icon {
+              display: inline;
+              line-height: normal;
+            }
+          }
+
+          &:hover span {
+            opacity: 1;
+          }
+
+          &.active {
+            background: #eeeeee;
+
+            span {
+              opacity: 1;
+            }
+          }
+
+          &.disabled {
+            cursor: default;
+
+            span {
+              opacity: 0.3;
+            }
+          }
+
+          &.toolbar-item-title {
+            span:before {
+              content: "H";
+              font-size: 19px;
+              font-weight: bold;
+              font-family: 'Times New Roman';
+            }
+
+            &.active-h1 span:before {
+              content: 'H1';
+              font-size: 18px;
+            }
+
+            &.active-h2 span:before {
+              content: 'H2';
+              font-size: 18px;
+            }
+
+            &.active-h3 span:before {
+              content: 'H3';
+              font-size: 18px;
+            }
+          }
+
+          &.toolbar-item-image {
+            position: relative;
+            overflow: hidden;
+
+            & > input[type=file] {
+              position: absolute;
+              right: 0px;
+              top: 0px;
+              opacity: 0;
+              font-size: 100px;
+              cursor: pointer;
+            }
+          }
+        }
+
+        &.menu-on {
+          .toolbar-item {
+            position: relative;
+            z-index: 20;
+            background: #ffffff;
+            box-shadow: 0 1px 4px rgba(0,0,0,0.3);
+
+            span {
+              opacity: 1;
+            }
+          }
+
+          .toolbar-menu {
+            display: block;
+          }
+        }
+      }
+    }
+
+    .toolbar-menu {
+      display: none;
+      position: absolute;
+      top: $simditor-button-height;
+      left: 0;
+      z-index: 21;
+      background: #ffffff;
+      text-align: left;
+      box-shadow: 0 0 4px rgba(0,0,0,0.3);
+
+      &:before {
+        content: '';
+        display: block;
+        width: $simditor-button-width;
+        height: 4px;
+        background: #ffffff;
+        position: absolute;
+        top: -3px;
+        left: 0;
+      }
+
+      ul {
+        min-width: 160px;
+        list-style: none;
+        margin: 0;
+        padding: 10px 1px;
+
+        & > li {
+
+          .menu-item {
+            display: block;
+            font-size:16px;
+            line-height: 2em;
+            padding: 0 10px;
+            text-decoration: none;
+            color: #666666;
+
+            &:hover {
+              background: #f6f6f6;
+            }
+
+            &.menu-item-h1 {
+              font-size: 24px;
+              color: #333333;
+            }
+
+            &.menu-item-h2 {
+              font-size: 22px;
+              color: #333333;
+            }
+
+            &.menu-item-h3 {
+              font-size: 20px;
+              color: #333333;
+            }
+
+            &.menu-item-h4 {
+              font-size: 18px;
+              color: #333333;
+            }
+
+            &.menu-item-h5 {
+              font-size: 16px;
+              color: #333333;
+            }
+          }
+
+          .separator {
+            display: block;
+            border-top: 1px solid #cccccc;
+            height: 0;
+            line-height: 0;
+            font-size: 0;
+            margin: 6px 0;
+          }
+        }
+
+      }
+
+      &.toolbar-menu-color {
+        width: 96px;
+
+        .color-list {
+          height: 40px;
+          margin: 10px 6px 6px 10px;
+          padding: 0;
+
+          min-width: 0;
+
+          li {
+            float: left;
+            margin: 0 4px 4px 0;
+
+            .font-color {
+              display: block;
+              width: 16px;
+              height: 16px;
+              background: #dfdfdf;
+              border-radius: 2px;
+
+              &:hover {
+                opacity: 0.8;
+              }
+
+              &.font-color-default {
+                background: #333333;
+              }
+            }
+
+            $font-colors: #E33737 #e28b41 #c8a732 #209361 #418caf #aa8773 #999999;
+            $i: 1;
+            @each $color in $font-colors {
+              .font-color-#{$i} {
+                background: $color;
+              }
+              $i: $i + 1;
+            }
+          }
+        }
+      }
+
+      &.toolbar-menu-table {
+        .menu-create-table {
+          background: #ffffff;
+          padding: 1px;
+
+          table {
+            border: none;
+            border-collapse: collapse;
+            border-spacing: 0;
+            table-layout: fixed;
+
+            td {
+              padding: 0;
+              cursor: pointer;
+
+              &:before {
+                width: 16px;
+                height: 16px;
+                border: 1px solid #ffffff;
+                background: #f3f3f3;
+                display: block;
+                content: ''
+              }
+
+              &.selected:before {
+                background: #cfcfcf;
+              }
+            }
+          }
+        }
+
+        .menu-edit-table {
+          display: none;
+
+          ul {
+            li {
+              white-space: nowrap;
+            }
+          }
+        }
+      }
+
+      &.toolbar-menu-image {
+        .menu-item-upload-image {
+          position: relative;
+          overflow: hidden;
+
+          input[type=file] {
+            position: absolute;
+            right: 0px;
+            top: 0px;
+            opacity: 0;
+            font-size: 100px;
+            cursor: pointer;
+          }
+        }
+      }
+
+      &.toolbar-menu-alignment {
+        width: 100%;
+        ul {
+          min-width: 100%;
+        }
+        .menu-item {
+          text-align: center;
+        }
+      }
+    }
+  }
+
+  .simditor-popover {
+    display: none;
+    padding: 5px 8px 0;
+    background: #ffffff;
+    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
+    border-radius: 2px;
+    position: absolute;
+    z-index: 2;
+
+    .settings-field {
+      margin: 0 0 5px 0;
+      font-size: 12px;
+      height: 25px;
+      line-height: 25px;
+
+      label {
+        display: inline-block;
+        margin: 0 5px 0 0;
+      }
+
+      input[type=text] {
+        display: inline-block;
+        width: 200px;
+        box-sizing: border-box;
+        font-size: 12px;
+
+        &.image-size {
+          width: 83px;
+        }
+      }
+
+      .times {
+        display: inline-block;
+        width: 26px;
+        font-size: 12px;
+        text-align: center;
+      }
+    }
+
+    &.link-popover .btn-unlink,
+    &.image-popover .btn-upload,
+    &.image-popover .btn-restore {
+      display: inline-block;
+      margin: 0 0 0 5px;
+      color: #333333;
+      font-size: 14px;
+      outline: 0;
+
+      span {
+        opacity: 0.6;
+      }
+
+      &:hover span {
+        opacity: 1;
+      }
+    }
+
+    &.image-popover .btn-upload {
+      position: relative;
+      display: inline-block;
+      overflow: hidden;
+      vertical-align: middle;
+
+      input[type=file] {
+        position: absolute;
+        right: 0px;
+        top: 0px;
+        opacity: 0;
+        height: 100%;
+        width: 28px;
+      }
+    }
+  }
+
+  &.simditor-mobile {
+    .simditor-wrapper.toolbar-floating .simditor-toolbar {
+        position: absolute;
+        top: 0;
+        z-index: 10;
+        box-shadow: 0 0 6px rgba(0,0,0,0.1);
+    }
+  }
+}
+
+
+
+.simditor .simditor-body, .editor-style {
+  font-size: 16px;
+  font-family: arial, sans-serif;
+  line-height: 1.6;
+  color: #333;
+  outline: none;
+  word-wrap: break-word;
+
+  & > :first-child {
+    margin-top: 0!important;
+  }
+
+  a{ color: #4298BA; text-decoration: none; word-break: break-all;}
+  a:visited{ color: #4298BA; }
+  a:hover{ color: #0F769F; }
+  a:active{ color:#9E792E; }
+  a:hover, a:active{ outline: 0; }
+
+  h1,h2,h3,h4,h5,h6 {
+    font-weight: normal;
+    margin: 40px 0 20px;
+    color: #000000;
+  }
+
+  h1 { font-size: 24px; }
+  h2 { font-size: 22px; }
+  h3 { font-size: 20px; }
+  h4 { font-size: 18px; }
+  h5 { font-size: 16px; }
+  h6 { font-size: 16px; }
+
+  p, div {
+    word-wrap: break-word;
+    margin: 0 0 15px 0;
+    color: #333;
+    word-wrap: break-word;
+  }
+
+  b, strong {
+    font-weight: bold;
+  }
+
+  i, em {
+    font-style: italic;
+  }
+
+  u {
+    text-decoration: underline;
+  }
+
+  strike, del {
+    text-decoration: line-through;
+  }
+
+  ul, ol {
+    list-style:disc outside none;
+    margin: 15px 0;
+    padding: 0 0 0 40px;
+    line-height: 1.6;
+
+    ul, ol {
+      padding-left: 30px;
+    }
+
+    ul {
+      list-style: circle outside none;
+
+      ul {
+        list-style: square outside none;
+      }
+    }
+  }
+
+  ol {
+    list-style:decimal;
+  }
+
+  blockquote {
+    border-left: 6px solid #ddd;
+    padding: 5px 0 5px 10px;
+    margin: 15px 0 15px 15px;
+
+    & > :first-child {
+      margin-top: 0;
+    }
+  }
+
+  code {
+    display: inline-block;
+    padding: 0 4px;
+    margin: 0 5px;
+    background: #eeeeee;
+    border-radius: 3px;
+    font-size: 13px;
+    font-family: 'monaco', 'Consolas', "Liberation Mono", Courier, monospace;
+  }
+
+  pre {
+    padding: 10px 5px 10px 10px;
+    margin: 15px 0;
+    display: block;
+    line-height: 18px;
+    background: #F0F0F0;
+    border-radius: 3px;
+    font-size:13px;
+    font-family: 'monaco', 'Consolas', "Liberation Mono", Courier, monospace;
+    white-space: pre;
+    word-wrap: normal;
+    overflow-x: auto;
+
+    code {
+      display: block;
+      padding: 0;
+      margin: 0;
+      background: none;
+      border-radius: 0;
+    }
+  }
+
+  hr {
+    display: block;
+    height: 0px;
+    border: 0;
+    border-top: 1px solid #ccc;
+    margin: 15px 0;
+    padding: 0;
+  }
+
+  table {
+    width: 100%;
+    table-layout: fixed;
+    border-collapse: collapse;
+    border-spacing: 0;
+    margin: 15px 0;
+
+    thead {
+      background-color: #f9f9f9;
+    }
+
+    td, th {
+      min-width: 40px;
+      height: 30px;
+      border: 1px solid #ccc;
+      vertical-align: top;
+      padding: 2px 4px;
+      text-align: left;
+      box-sizing: border-box;
+
+      &.active {
+        background-color: #ffffee;
+      }
+    }
+  }
+
+
+  img {
+    margin: 0 5px;
+    vertical-align: middle;
+  }
+
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
admin/static/lib/simditor/styles/fonticon.scss


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 7 - 0
admin/static/lib/simditor/styles/simditor.css


+ 4 - 0
admin/static/lib/simditor/styles/simditor.scss

@@ -0,0 +1,4 @@
+@charset "UTF-8";
+
+@import 'fonticon';
+@import 'editor';

+ 1 - 1
admin/templates/common/list.html

@@ -13,7 +13,7 @@
     </div>
     {% elif param[1] == 'hidden' %}
     <div class="layui-input-inline"  style="width:auto" >
-      <input type="hidden" name="search_{{key}}"id="search_{{key}}" placeholder="{{value}}" autocomplete="off" class="layui-input" value="{% if data['search'] and key in data['search'] %}{{data['search'][key]}}{% end %}">
+      <input type="hidden" name="search_{{key}}" id="search_{{key}}" placeholder="{{value}}" autocomplete="off" class="layui-input" value="{% if data['search'] and key in data['search'] %}{{data['search'][key]}}{% end %}">
     </div>
     {% elif param[1] == 'time' %}
     <div class="layui-input-inline"  style="width:auto">

+ 1 - 1
admin/templates/common/set.html

@@ -26,7 +26,7 @@
       {% elif value[1] == 'text' %}
           <textarea placeholder="请输入内容" class="layui-textarea" id="update_{{value[0]}}" name="update_{{value[0]}}" value="{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}" lay-verify="{{value[2]}}">{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}</textarea>
       {% elif value[1] == 'editor' %}
-          <textarea placeholder="请输入内容" class="layui-textarea layui-editor" id="update_{{value[0]}}" name="update_{{value[0]}}" value="{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}" lay-verify="{{value[2]}}">{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}</textarea>
+          <textarea placeholder="请输入内容" class="layui-textarea layui-editor" key="1" id="update_{{value[0]}}" name="update_{{value[0]}}" value="{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}" lay-verify="{{value[2]}}">{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}</textarea>
       {% elif value[1] == 'pic' %}
           <div class="site-demo-upbar">
             <img id="upload_{{value[0]}}_show" width="200" src="{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}" style="{% if  data['info'] and value[0] in data['info'] and data['info'][value[0]] %}display:;{% else %}display:none;{% end %}margin-left:10px;"/>

+ 1 - 1
admin/templates/common/update.html

@@ -29,7 +29,7 @@
       {% elif value[1] == 'text' %}
           <textarea {% if length > 3 %}placeholder="{{value[3]}}"{% end %} class="layui-textarea" id="update_{{value[0]}}" name="update_{{value[0]}}" value="{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}" lay-verify="{{value[2]}}">{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}</textarea>
       {% elif value[1] == 'editor' %}
-          <textarea {% if length > 3 %}placeholder="{{value[3]}}"{% end %} class="layui-form layui-textarea layui-editor" id="update_{{value[0]}}" name="update_{{value[0]}}" value="{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}" lay-verify="{{value[2]}}">{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}</textarea>
+          <textarea {% if length > 3 %}placeholder="{{value[3]}}"{% end %} class="layui-form layui-textarea layui-editor" key="1" id="update_{{value[0]}}" name="update_{{value[0]}}" value="{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}" lay-verify="{{value[2]}}">{% if data['info'] and value[0] in data['info'] %}{{data['info'][value[0]]}}{% end %}</textarea>
       {% elif value[1] == 'pic' %}
           <div class="site-demo-upbar">
             <img id="upload_{{value[0]}}_show" width="200" src="{% if data['info'] and value[0] in data['info'] and data['info'][value[0]] %}{{data['info'][value[0]]}}{% end %}" style="{% if  data['info'] and value[0] in data['info'] and data['info'][value[0]] %}display:;{% else %}display:none;{% end %}margin-left:10px;"/>

+ 1 - 0
admin/templates/inc/script.html

@@ -1,4 +1,5 @@
 <script src="{{static_url('lib/layui/layui.js')}}" charset="utf-8"></script>
+<script src="{{static_url('js/jquery.min.js')}}" charset="utf-8"></script>
 <script src="{{static_url('js/x-admin.js')}}" charset="utf-8"></script>
 <script src="{{static_url('js/x-layui.js')}}" charset="utf-8"></script>
 <script src="{{static_url('js/main.js')}}" charset="utf-8"></script>

+ 23 - 10
admin/templates/theme/set.html

@@ -2,6 +2,8 @@
 <html>
     <head>
         {%include '../inc/header.html'%}
+        <link rel="stylesheet" type="text/css" href="{{static_url('lib/simditor/styles/simditor.css')}}">
+        <link rel="stylesheet" type="text/css" href="{{static_url('lib/simditor/plugins/html/simditor-html.css')}}">
     </head>
     <body>
         <div class="x-nav">
@@ -113,8 +115,15 @@
         </div>
         {%include '../inc/footer.html'%}
         {%include '../inc/script.html'%}
-        
-         <script>
+        <!-- 加载文本编辑器 -->
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/module.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/hotkeys.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/uploader.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/simditor.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/plugins/html/beautify-html.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/plugins/html/simditor-html.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/plugins/dropzone/simditor-dropzone.js')}}"></script>
+        <script>
         layui.use(['laydate', 'form','layer','layedit','upload'], function(){
             $ = layui.jquery;
             var form = layui.form()
@@ -142,20 +151,14 @@
 
             var xsrf = getCookie("_xsrf");
             var uploadUrl = '/upload?_xsrf='+xsrf;
+            initEditor();
+            /*
             layedit.set({
               uploadImage: {
                 url: uploadUrl
                 ,type: 'post'
               }
             });
-            layui.upload({
-                url: uploadUrl
-                ,success: function(res, input){ //上传成功后的回调
-                    var id = $(input).attr('id').replace('upload_', '');
-                    $('#upload_' + id + '_show').attr('src',res.data.src + '?v' + Date.parse(new Date())).show();
-                    $('#update_' + id).val(res.data.src);
-                }
-            });
             if ($('.layui-editor').length) {
                 $('.layui-editor').each(function()
                 {
@@ -164,6 +167,16 @@
                     layedit.sync(index)
                 });
             }
+            */
+            layui.upload({
+                url: uploadUrl
+                ,success: function(res, input){ //上传成功后的回调
+                    var id = $(input).attr('id').replace('upload_', '');
+                    $('#upload_' + id + '_show').attr('src',res.data.src + '?v' + Date.parse(new Date())).show();
+                    $('#update_' + id).val(res.data.src);
+                }
+            });
+            
 
             {% block script %}
             {% end %}

+ 23 - 8
admin/templates/theme/update.html

@@ -3,6 +3,8 @@
     
     <head>
         {%include '../inc/header.html'%}
+        <link rel="stylesheet" type="text/css" href="{{static_url('lib/simditor/styles/simditor.css')}}">
+        <link rel="stylesheet" type="text/css" href="{{static_url('lib/simditor/plugins/html/simditor-html.css')}}">
     </head>
     
     <body>
@@ -67,6 +69,14 @@
             </form>
         </div>
         {%include '../inc/script.html'%}
+        <!-- 加载文本编辑器 -->
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/module.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/hotkeys.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/uploader.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/scripts/simditor.min.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/plugins/html/beautify-html.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/plugins/html/simditor-html.js')}}"></script>
+        <script type="text/javascript" src="{{static_url('lib/simditor/plugins/dropzone/simditor-dropzone.js')}}"></script>
         <script>
         layui.use(['laydate', 'form','layer','layedit','upload'], function(){
             $ = layui.jquery;
@@ -95,20 +105,15 @@
 
             var xsrf = getCookie("_xsrf");
             var uploadUrl = '/upload?_xsrf='+xsrf;
+            initEditor();
+            /*
             layedit.set({
               uploadImage: {
                 url: uploadUrl
                 ,type: 'post'
               }
             });
-            layui.upload({
-                url: uploadUrl
-                ,success: function(res, input){ //上传成功后的回调
-                    var id = $(input).attr('id').replace('upload_', '');
-                    $('#upload_' + id + '_show').attr('src',res.data.src + '?v' + Date.parse(new Date())).show();
-                    $('#update_' + id).val(res.data.src);
-                }
-            });
+
             if ($('.layui-editor').length) {
                 $('.layui-editor').each(function()
                 {
@@ -117,6 +122,16 @@
                     layedit.sync(index)
                 });
             }
+            */
+            layui.upload({
+                url: uploadUrl
+                ,success: function(res, input){ //上传成功后的回调
+                    var id = $(input).attr('id').replace('upload_', '');
+                    $('#upload_' + id + '_show').attr('src',res.data.src + '?v' + Date.parse(new Date())).show();
+                    $('#update_' + id).val(res.data.src);
+                }
+            });
+            
             if ($(".selectLoad").length) {
                 $(".selectLoad").each(function()
                 {

Vissa filer visades inte eftersom för många filer har ändrats