Summernote editor (#2665)
* [summernote] working commit * fix set_input method, esc key fix * styling fixes * removed old texteditor
This commit is contained in:
parent
e422e16953
commit
d0bf9ae3d3
9 changed files with 7220 additions and 682 deletions
|
|
@ -41,6 +41,7 @@
|
|||
"css/desk.min.css": [
|
||||
"public/js/lib/datepicker/datepicker.min.css",
|
||||
"public/js/lib/awesomplete/awesomplete.css",
|
||||
"public/js/lib/summernote/summernote.css",
|
||||
"public/css/bootstrap.css",
|
||||
"public/css/font-awesome.css",
|
||||
"public/css/octicons/octicons.css",
|
||||
|
|
@ -66,6 +67,7 @@
|
|||
"public/js/lib/awesomplete/awesomplete.min.js",
|
||||
"public/js/lib/Sortable.min.js",
|
||||
"public/js/lib/taggle/taggle.min.js",
|
||||
"public/js/lib/summernote/summernote.js",
|
||||
"public/js/lib/notify.js",
|
||||
"public/js/lib/bootstrap.min.js",
|
||||
"public/js/lib/moment/moment-with-locales.min.js",
|
||||
|
|
|
|||
|
|
@ -642,3 +642,23 @@ fieldset[disabled] .form-control {
|
|||
.search-result {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.note-editor {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.note-editor.note-frame {
|
||||
border-color: #d1d8dd;
|
||||
}
|
||||
.note-editor .btn {
|
||||
outline: none !important;
|
||||
}
|
||||
.note-editor .dropdown-style > li > a > * {
|
||||
margin: 0;
|
||||
}
|
||||
.note-editor .fa.fa-check {
|
||||
color: #36414C !important;
|
||||
}
|
||||
.note-editor .dropdown-menu {
|
||||
z-index: 100;
|
||||
max-height: 300px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1555,130 +1555,111 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
|
|||
});
|
||||
|
||||
frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
||||
editor_name: "bsEditor",
|
||||
horizontal: false,
|
||||
make_input: function() {
|
||||
//$(this.input_area).css({"min-height":"360px"});
|
||||
this.has_input = true;
|
||||
this.make_rich_text_editor();
|
||||
this.make_markdown_editor();
|
||||
this.make_switcher();
|
||||
this.make_editor();
|
||||
this.hide_elements_on_mobile();
|
||||
},
|
||||
make_rich_text_editor: function() {
|
||||
make_editor: function() {
|
||||
var me = this;
|
||||
this.editor_wrapper = $("<div>").appendTo(this.input_area);
|
||||
var onchange = function(value) {
|
||||
me.md_editor.val(value);
|
||||
me.parse_validate_and_set_in_model(value);
|
||||
this.editor = $("<div>").appendTo(this.input_area);
|
||||
this.editor.summernote({
|
||||
minHeight: 400,
|
||||
toolbar: [
|
||||
['magic', ['style']],
|
||||
['style', ['bold', 'italic', 'underline', 'clear']],
|
||||
['fontsize', ['fontsize']],
|
||||
['color', ['color']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['height', ['height']],
|
||||
['misc', ['fullscreen', 'codeview']]
|
||||
],
|
||||
callbacks: {
|
||||
onChange: function(value) {
|
||||
me.parse_validate_and_set_in_model(value);
|
||||
},
|
||||
onKeydown: function(e) {
|
||||
var key = frappe.ui.keys.get_key(e);
|
||||
// prevent 'New DocType (Ctrl + B)' shortcut in editor
|
||||
if(['ctrl+b', 'meta+b'].indexOf(key) !== -1) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
if(key.indexOf('escape') !== -1) {
|
||||
if(me.note_editor.hasClass('fullscreen')) {
|
||||
// exit fullscreen on escape key
|
||||
me.note_editor
|
||||
.find('.note-btn.btn-fullscreen')
|
||||
.trigger('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
icons: {
|
||||
'align': 'fa fa-align',
|
||||
'alignCenter': 'fa fa-align-center',
|
||||
'alignJustify': 'fa fa-align-justify',
|
||||
'alignLeft': 'fa fa-align-left',
|
||||
'alignRight': 'fa fa-align-right',
|
||||
'indent': 'fa fa-indent',
|
||||
'outdent': 'fa fa-outdent',
|
||||
'arrowsAlt': 'fa fa-arrows-alt',
|
||||
'bold': 'fa fa-bold',
|
||||
'caret': 'caret',
|
||||
'circle': 'fa fa-circle',
|
||||
'close': 'fa fa-close',
|
||||
'code': 'fa fa-code',
|
||||
'eraser': 'fa fa-eraser',
|
||||
'font': 'fa fa-font',
|
||||
'frame': 'fa fa-frame',
|
||||
'italic': 'fa fa-italic',
|
||||
'link': 'fa fa-link',
|
||||
'unlink': 'fa fa-chain-broken',
|
||||
'magic': 'fa fa-magic',
|
||||
'menuCheck': 'fa fa-check',
|
||||
'minus': 'fa fa-minus',
|
||||
'orderedlist': 'fa fa-list-ol',
|
||||
'pencil': 'fa fa-pencil',
|
||||
'picture': 'fa fa-picture',
|
||||
'question': 'fa fa-question',
|
||||
'redo': 'fa fa-redo',
|
||||
'square': 'fa fa-square',
|
||||
'strikethrough': 'fa fa-strikethrough',
|
||||
'subscript': 'fa fa-subscript',
|
||||
'superscript': 'fa fa-superscript',
|
||||
'table': 'fa fa-table',
|
||||
'textHeight': 'fa fa-text-height',
|
||||
'trash': 'fa fa-trash',
|
||||
'underline': 'fa fa-underline',
|
||||
'undo': 'fa fa-undo',
|
||||
'unorderedlist': 'fa fa-list-ul',
|
||||
'video': 'fa fa-video'
|
||||
}
|
||||
});
|
||||
this.note_editor = $(this.input_area).find('.note-editor');
|
||||
},
|
||||
hide_elements_on_mobile: function() {
|
||||
this.note_editor.find('.note-btn-underline,\
|
||||
.note-btn-italic, .note-fontsize,\
|
||||
.note-color, .note-height, .btn-codeview')
|
||||
.addClass('hidden-xs');
|
||||
if($('.toggle-sidebar').is(':visible')) {
|
||||
// disable tooltips on mobile
|
||||
this.note_editor.find('.note-btn')
|
||||
.attr('data-original-title', '');
|
||||
}
|
||||
this.editor = new (frappe.provide(this.editor_name))({
|
||||
parent: this.editor_wrapper,
|
||||
change: onchange,
|
||||
field: this
|
||||
});
|
||||
this.editor.editor.on("blur", function() {
|
||||
onchange(me.editor.clean_html());
|
||||
});
|
||||
this.editor.editor.keypress("ctrl+s meta+s", function() {
|
||||
me.frm.save_or_update();
|
||||
});
|
||||
},
|
||||
make_markdown_editor: function() {
|
||||
var me = this;
|
||||
this.md_editor_wrapper = $("<div class='hide'>")
|
||||
.appendTo(this.input_area);
|
||||
this.md_editor = $("<textarea class='form-control markdown-text-editor'>")
|
||||
.appendTo(this.md_editor_wrapper)
|
||||
.allowTabs()
|
||||
.on("change", function() {
|
||||
var value = $(this).val();
|
||||
me.editor.set_input(value);
|
||||
me.parse_validate_and_set_in_model(value);
|
||||
});
|
||||
|
||||
$('<div class="text-muted small">Add <!-- markdown --> \
|
||||
to always interpret as markdown</div>')
|
||||
.appendTo(this.md_editor_wrapper);
|
||||
},
|
||||
make_switcher: function() {
|
||||
var me = this;
|
||||
this.current_editor = this.editor;
|
||||
this.switcher = $('<p class="text-right small">\
|
||||
<a href="#" class="switcher"></a></p>')
|
||||
.appendTo(this.input_area)
|
||||
.find("a")
|
||||
.click(function() {
|
||||
me.switch();
|
||||
return false;
|
||||
});
|
||||
this.render_switcher();
|
||||
},
|
||||
switch: function() {
|
||||
if(this.current_editor===this.editor) {
|
||||
// switch to md
|
||||
var value = this.editor.get_value();
|
||||
this.editor_wrapper.addClass("hide");
|
||||
this.md_editor_wrapper.removeClass("hide");
|
||||
this.current_editor = this.md_editor;
|
||||
this.add_type_marker("markdown");
|
||||
} else {
|
||||
// switch to html
|
||||
var value = this.md_editor.val();
|
||||
this.md_editor_wrapper.addClass("hide");
|
||||
this.editor_wrapper.removeClass("hide");
|
||||
this.current_editor = this.editor;
|
||||
this.add_type_marker("html");
|
||||
}
|
||||
this.render_switcher();
|
||||
},
|
||||
add_type_marker: function(marker) {
|
||||
var opp_marker = marker==="html" ? "markdown" : "html";
|
||||
if(!this.value) this.value = "";
|
||||
if(this.value.indexOf("<!-- " + opp_marker + " -->")!==-1) {
|
||||
// replace opposite marker
|
||||
this.set_value(this.value.split("<!-- " + opp_marker + " -->").join("<!-- " + marker + " -->"));
|
||||
} else if(this.value.indexOf("<!-- " + marker + " -->")===-1) {
|
||||
// add marker (marker missing)
|
||||
this.set_value(this.value + "\n\n\n<!-- " + marker + " -->");
|
||||
}
|
||||
},
|
||||
render_switcher: function() {
|
||||
this.switcher.html(__("Edit as {0}", [this.current_editor == this.editor ?
|
||||
__("Markdown") : __("Rich Text")]));
|
||||
},
|
||||
get_value: function() {
|
||||
return this.current_editor === this.editor
|
||||
? this.editor.get_value()
|
||||
: this.md_editor.val();
|
||||
return this.editor.summernote('code');
|
||||
},
|
||||
set_input: function(value) {
|
||||
this._set_input(value);
|
||||
|
||||
// guess editor type
|
||||
var is_markdown = false;
|
||||
if(value) {
|
||||
if(value.indexOf("<!-- markdown -->") !== -1) {
|
||||
var is_markdown = true;
|
||||
}
|
||||
if((is_markdown && this.current_editor===this.editor)
|
||||
|| (!is_markdown && this.current_editor===this.md_editor)) {
|
||||
this.switch();
|
||||
}
|
||||
}
|
||||
},
|
||||
_set_input: function(value) {
|
||||
if(value == null) value = "";
|
||||
value = frappe.dom.remove_script_and_style(value);
|
||||
this.editor.set_input(value);
|
||||
this.md_editor.val(value);
|
||||
if(value !== this.get_value())
|
||||
this.editor.summernote('code', value);
|
||||
this.last_value = value;
|
||||
},
|
||||
set_focus: function() {
|
||||
var editor = this.$wrapper.find('.text-editor');
|
||||
if(editor) {
|
||||
editor.focus();
|
||||
return true;
|
||||
}
|
||||
return this.editor.summernote('focus');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
<div class="frappe-list-toolbar frappe-ignore-click">
|
||||
<div class="btn-toolbar" data-role="editor-toolbar"
|
||||
style="margin-bottom: 7px;">
|
||||
<div class="btn-group form-group editor-toolbar-list-group">
|
||||
<a class="btn btn-default btn-small" data-edit="bold"
|
||||
title="{{ __("Bold (Ctrl/Cmd+B)") }}"><b>B</b></a>
|
||||
<a class="btn btn-default btn-small"
|
||||
data-edit="insertunorderedlist" title="{{ __("Bullet list") }}">
|
||||
<i class="octicon octicon-list-unordered"></i></a>
|
||||
<a class="btn btn-default btn-small"
|
||||
data-edit="insertorderedlist"
|
||||
title="{{ __("Number list") }}">
|
||||
<i class="octicon octicon-list-ordered"></i></a>
|
||||
</div>
|
||||
<div class="btn-group form-group editor-toolbar-text-group">
|
||||
<a class="btn btn-default btn-small dropdown-toggle"
|
||||
data-toggle="dropdown" title="{{ __("Font Size") }}">
|
||||
<i class="fa fa-text-height"></i>
|
||||
<small style="margin-left: 5px;" class="hidden-xs"></small>
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="#" data-edit="formatBlock <p>">
|
||||
<p>{{ __("Paragraph") }}</p></a>
|
||||
</li>
|
||||
<li><a href="#" data-edit="formatBlock <h1>">
|
||||
<h1>{{ __("Heading") }} 1</h1></a>
|
||||
</li>
|
||||
<li><a href="#" data-edit="formatBlock <h2>">
|
||||
<h2>{{ __("Heading") }} 2</h2></a>
|
||||
</li>
|
||||
<li><a href="#" data-edit="formatBlock <h3>">
|
||||
<h3>{{ __("Heading") }} 3</h3></a>
|
||||
</li>
|
||||
<li><a href="#" data-edit="formatBlock <h4>">
|
||||
<h4>{{ __("Heading") }} 4</h4></a>
|
||||
</li>
|
||||
<li><a href="#" data-edit="formatBlock <h5>">
|
||||
<h5>{{ __("Heading") }} 5</h5></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group form-group editor-toolbar-link-group">
|
||||
<a class="btn btn-default btn-small btn-insert-img"
|
||||
title="{{ __("Insert picture (or just drag & drop)") }}">
|
||||
<i class="octicon octicon-file-media"></i></a>
|
||||
<a class="btn btn-default btn-small btn-add-link"
|
||||
title="{{ __("Insert Link") }}"><i class="fa fa-link"></i></a>
|
||||
<a class="btn btn-default btn-small"
|
||||
title="{{ __("Remove Link") }}" data-edit="unlink">
|
||||
<i class="fa fa-unlink"></i></a>
|
||||
</div>
|
||||
<div class="btn-group hidden-xs form-group editor-toolbar-align-group">
|
||||
<a class="btn btn-default btn-small" data-edit="justifyleft"
|
||||
title="{{ __("Align Left (Ctrl/Cmd+L)") }}">
|
||||
<i class="fa fa-align-left"></i></a>
|
||||
<a class="btn btn-default btn-small" data-edit="justifycenter"
|
||||
title="{{ __("Center (Ctrl/Cmd+E)") }}">
|
||||
<i class="fa fa-align-center"></i></a>
|
||||
<a class="btn btn-default btn-small" data-edit="justifyright"
|
||||
title="{{ __("Align Right (Ctrl/Cmd+R)") }}">
|
||||
<i class="fa fa-align-right"></i></a>
|
||||
<a class="btn btn-default btn-small" data-edit="outdent"
|
||||
title="{{ __("Reduce indent (Shift+Tab)") }}">
|
||||
<i class="fa fa-indent"></i></a>
|
||||
<a class="btn btn-default btn-small" data-edit="indent"
|
||||
title="{{ __("Indent (Tab)") }}">
|
||||
<i class="fa fa-outdent"></i></a>
|
||||
<a class="btn btn-default btn-small"
|
||||
data-edit="insertHorizontalRule"
|
||||
title="{{ __("Horizontal Line Break") }}">
|
||||
<i class="octicon octicon-horizontal-rule"></i></a>
|
||||
</div>
|
||||
<div class="btn-group form-group editor-toolbar-code-group">
|
||||
<a class="btn btn-default btn-small btn-html hidden-xs"
|
||||
title="{{ __("HTML") }}">
|
||||
<i class="octicon octicon-code"></i></a>
|
||||
<a class="btn btn-default btn-small btn-success" data-action="Save"
|
||||
title="{{ __("Save") }}">
|
||||
<i class="octicon octicon-check"></i></a>
|
||||
</div>
|
||||
<input type="file" data-edit="insertImage" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,471 +0,0 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
/* Inspired from: http://github.com/mindmup/bootstrap-wysiwyg */
|
||||
|
||||
// todo
|
||||
// make it inline friendly
|
||||
|
||||
bsEditor = Class.extend({
|
||||
init: function(options) {
|
||||
this.options = $.extend({}, this.default_options, options || {});
|
||||
this.edit_mode = true;
|
||||
if(this.options.editor) {
|
||||
this.setup_editor(this.options.editor);
|
||||
this.setup_fixed_toolbar();
|
||||
} else if(this.options.parent) {
|
||||
this.wrapper = $("<div></div>").appendTo(this.options.parent);
|
||||
this.setup_editor($("<div class='frappe-list'></div>").appendTo(this.wrapper));
|
||||
this.setup_inline_toolbar();
|
||||
this.editor.addClass("text-editor");
|
||||
this.set_editing();
|
||||
}
|
||||
},
|
||||
setup_editor: function(editor) {
|
||||
var me = this;
|
||||
this.editor = $(editor);
|
||||
this.editor.on("click", function() {
|
||||
if(me.edit_mode && !me.editing) {
|
||||
me.set_editing();
|
||||
}
|
||||
}).on("mouseup keyup mouseout", function() {
|
||||
var html = me.clean_html();
|
||||
if(me.editing) {
|
||||
me.toolbar.save_selection();
|
||||
me.toolbar.update();
|
||||
if(html != me.last_html) {
|
||||
me.options.change && me.options.change(html);
|
||||
me.last_html = html;
|
||||
}
|
||||
}
|
||||
}).data("object", this);
|
||||
|
||||
this.bind_hotkeys();
|
||||
this.init_file_drops();
|
||||
},
|
||||
|
||||
set_editing: function() {
|
||||
this.editor.attr('contenteditable', true);
|
||||
this.toolbar.show();
|
||||
if(this.options.editor)
|
||||
this.toolbar.editor = this.editor.focus();
|
||||
this.editing = true;
|
||||
},
|
||||
|
||||
setup_fixed_toolbar: function() {
|
||||
if(!window.bs_editor_toolbar) {
|
||||
window.bs_editor_toolbar = new bsEditorToolbar(this.options)
|
||||
}
|
||||
this.toolbar = window.bs_editor_toolbar;
|
||||
},
|
||||
setup_inline_toolbar: function() {
|
||||
this.toolbar = new bsEditorToolbar(this.options, this.wrapper, this.editor);
|
||||
},
|
||||
onhide: function() {
|
||||
this.editing = false;
|
||||
this.options.onsave && this.options.onsave(this);
|
||||
this.options.change && this.options.change(this.get_value());
|
||||
},
|
||||
toggle_edit_mode: function(bool) {
|
||||
// switch to enter editing mode
|
||||
this.edit_mode = bool;
|
||||
if(this.edit_mode) {
|
||||
this.editor.trigger("click");
|
||||
}
|
||||
},
|
||||
default_options: {
|
||||
hotKeys: {
|
||||
'ctrl+b meta+b': 'bold',
|
||||
'ctrl+i meta+i': 'italic',
|
||||
'ctrl+u meta+u': 'underline',
|
||||
'ctrl+z meta+z': 'undo',
|
||||
'ctrl+y meta+y meta+shift+z': 'redo',
|
||||
'ctrl+l meta+l': 'justifyleft',
|
||||
'ctrl+e meta+e': 'justifycenter',
|
||||
'ctrl+j meta+j': 'justifyfull',
|
||||
'shift+tab': 'outdent',
|
||||
'tab': 'indent'
|
||||
},
|
||||
toolbar_selector: '[data-role=editor-toolbar]',
|
||||
command_role: 'edit',
|
||||
selection_marker: 'edit-focus-marker',
|
||||
selection_color: 'darkgrey',
|
||||
remove_typography: false,
|
||||
max_file_size: 5,
|
||||
},
|
||||
|
||||
bind_hotkeys: function () {
|
||||
var me = this;
|
||||
$.each(this.options.hotKeys, function (hotkey, command) {
|
||||
me.editor.keydown(hotkey, function (e) {
|
||||
if (me.editor.attr('contenteditable') && me.editor.is(':visible')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
me.toolbar.execCommand(command);
|
||||
return false;
|
||||
}
|
||||
}).keyup(hotkey, function (e) {
|
||||
if (me.editor.attr('contenteditable') && me.editor.is(':visible')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
clean_html: function() {
|
||||
|
||||
var html = this.editor.html() || "";
|
||||
|
||||
if(!$.trim(this.editor.text()) && !(this.editor.find("img"))) html = "";
|
||||
|
||||
// remove custom typography (use CSS!)
|
||||
if(this.options.remove_typography) {
|
||||
var tmp = $("<div></div>").html(html);
|
||||
// remove style attributes
|
||||
tmp.find("*")
|
||||
.removeAttr("style")
|
||||
.removeAttr("font");
|
||||
html = tmp.html();
|
||||
}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
init_file_drops: function () {
|
||||
var me = this;
|
||||
this.editor.on('dragenter dragover', false)
|
||||
.on('drop', function (e) {
|
||||
var dataTransfer = e.originalEvent.dataTransfer;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
|
||||
me.insert_files(dataTransfer.files);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
insert_files: function (files) {
|
||||
var me = this;
|
||||
this.editor.focus();
|
||||
$.each(files, function (i, file) {
|
||||
if (/^image\//.test(file.type)) {
|
||||
me.get_image(file, function(image_url) {
|
||||
me.toolbar.execCommand('insertImage', image_url);
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_image: function (fileobj, callback) {
|
||||
var freader = new FileReader(),
|
||||
me = this;
|
||||
|
||||
freader.onload = function() {
|
||||
var dataurl = freader.result;
|
||||
// add filename to dataurl
|
||||
var parts = dataurl.split(",");
|
||||
parts[0] += ";filename=" + fileobj.name;
|
||||
dataurl = parts[0] + ',' + parts[1];
|
||||
if(me.options.max_file_size) {
|
||||
if(dataurl.length > (me.options.max_file_size * 1024 * 1024 * 1.4)) {
|
||||
bs_get_modal("Upload Error", "Max file size (" + me.options.max_file_size + "M) exceeded.").modal("show");
|
||||
throw "file size exceeded";
|
||||
}
|
||||
}
|
||||
callback(dataurl);
|
||||
}
|
||||
freader.readAsDataURL(fileobj);
|
||||
},
|
||||
|
||||
get_value: function() {
|
||||
return this.clean_html()
|
||||
},
|
||||
|
||||
set_input: function(value) {
|
||||
if(this.options.field && this.options.field.inside_change_event)
|
||||
return;
|
||||
if(value==null) value = "";
|
||||
this.last_html = value;
|
||||
this.editor.html(value);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
bsEditorToolbar = Class.extend({
|
||||
init: function(options, parent, editor) {
|
||||
this.options = options;
|
||||
this.editor = editor;
|
||||
this.inline = !!parent;
|
||||
this.options.toolbar_style = $.extend((this.inline ? this.inline_style : this.fixed_style),
|
||||
this.options.toolbar_style || {});
|
||||
this.make(parent);
|
||||
this.toolbar.css(this.options.toolbar_style);
|
||||
this.setup_image_button();
|
||||
this.bind_events();
|
||||
//this.bind_touch();
|
||||
},
|
||||
fixed_style: {
|
||||
position: "fixed",
|
||||
top: "0px",
|
||||
"padding-top": "5px",
|
||||
width: "100%",
|
||||
height: "45px",
|
||||
"background-color": "black",
|
||||
display: "none"
|
||||
},
|
||||
inline_style: {
|
||||
"padding-top": "5px",
|
||||
},
|
||||
make: function(parent) {
|
||||
if(!parent)
|
||||
parent = $("body");
|
||||
if(!parent.find(".frappe-list-toolbar").length) {
|
||||
this.toolbar = $(frappe.render_template("editor")).prependTo(parent);
|
||||
|
||||
if(this.inline) {
|
||||
this.toolbar.find("[data-action]").remove();
|
||||
} else {
|
||||
this.toolbar.find(".btn-toolbar").addClass("container");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setup_image_button: function() {
|
||||
// magic-overlay
|
||||
var me = this;
|
||||
this.file_input = this.toolbar.find('input[type="file"]')
|
||||
.css({
|
||||
'visibility': 'hidden',
|
||||
'width':0,
|
||||
'height':0
|
||||
});
|
||||
this.toolbar.find(".btn-insert-img").on("click", function() {
|
||||
me.file_input.trigger("click");
|
||||
})
|
||||
},
|
||||
|
||||
show: function() {
|
||||
var me = this;
|
||||
this.toolbar.toggle(true);
|
||||
if(!this.inline) {
|
||||
$("body").animate({"padding-top": this.toolbar.outerHeight() }, {
|
||||
complete: function() { me.toolbar.css("z-index", 1001); }
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
if(!this.editor)
|
||||
return;
|
||||
var me = this;
|
||||
this.toolbar.css("z-index", 0);
|
||||
if(!this.inline) {
|
||||
$("body").animate({"padding-top": 0 }, {complete: function() {
|
||||
me.toolbar.toggle(false);
|
||||
}});
|
||||
}
|
||||
|
||||
this.editor && this.editor.attr('contenteditable', false).data("object").onhide();
|
||||
this.editor = null;
|
||||
},
|
||||
|
||||
bind_events: function () {
|
||||
var me = this;
|
||||
|
||||
// standard button events
|
||||
this.toolbar.find('a[data-' + me.options.command_role + ']').click(function (e) {
|
||||
me.restore_selection();
|
||||
me.editor.focus();
|
||||
me.execCommand($(this).data(me.options.command_role));
|
||||
me.save_selection();
|
||||
// close dropdown
|
||||
if(me.toolbar.find("ul.dropdown-menu:visible").length)
|
||||
me.toolbar.find('[data-toggle="dropdown"]').dropdown("toggle");
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
this.toolbar.find('[data-toggle=dropdown]').click(function() { me.restore_selection() });
|
||||
|
||||
// link
|
||||
this.toolbar.find(".btn-add-link").on("click", function() {
|
||||
if(!me.toolbar.bs_link_editor) {
|
||||
if(me.inline) {
|
||||
me.toolbar.bs_link_editor = new bsLinkEditor(me);
|
||||
} else {
|
||||
if(!window.bs_link_editor) {
|
||||
window.bs_link_editor = new bsLinkEditor(me);
|
||||
}
|
||||
me.toolbar.bs_link_editor = window.bs_link_editor;
|
||||
}
|
||||
}
|
||||
me.toolbar.bs_link_editor.show();
|
||||
});
|
||||
|
||||
// file event
|
||||
this.toolbar.find('input[type=file][data-' + me.options.command_role + ']').change(function () {
|
||||
me.restore_selection();
|
||||
if (this.type === 'file' && this.files && this.files.length > 0) {
|
||||
me.editor.data("object").insert_files(this.files);
|
||||
}
|
||||
me.save_selection();
|
||||
this.value = '';
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// save
|
||||
this.toolbar.find("[data-action='Save']").on("click", function() { me.hide(); });
|
||||
|
||||
// edit html
|
||||
this.toolbar.find(".btn-html").on("click", function() {
|
||||
new bsHTMLEditor().show(me.editor);
|
||||
});
|
||||
},
|
||||
|
||||
update: function () {
|
||||
var me = this;
|
||||
if (this.toolbar) {
|
||||
$(this.toolbar).find('.btn[data-' + this.options.command_role + ']').each(function () {
|
||||
var command = $(this).data(me.options.command_role);
|
||||
|
||||
// try catch for buggy firefox!
|
||||
try {
|
||||
var query_command_state = document.queryCommandState(command);
|
||||
} catch(e) {
|
||||
var query_command_state = false;
|
||||
}
|
||||
|
||||
if (query_command_state) {
|
||||
$(this).addClass(me.options.active_toolbar_class);
|
||||
} else {
|
||||
$(this).removeClass(me.options.active_toolbar_class);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
execCommand: function (commandWithArgs, valueArg) {
|
||||
var commandArr = commandWithArgs.split(' '),
|
||||
command = commandArr.shift(),
|
||||
args = commandArr.join(' ') + (valueArg || '');
|
||||
document.execCommand(command, 0, args);
|
||||
this.update();
|
||||
},
|
||||
|
||||
get_current_range: function () {
|
||||
var sel = window.getSelection();
|
||||
if (sel.getRangeAt && sel.rangeCount) {
|
||||
return sel.getRangeAt(0);
|
||||
}
|
||||
},
|
||||
|
||||
save_selection: function () {
|
||||
this.selected_range = this.get_current_range();
|
||||
},
|
||||
|
||||
restore_selection: function () {
|
||||
var selection = window.getSelection();
|
||||
if (this.selected_range) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(this.selected_range);
|
||||
}
|
||||
},
|
||||
|
||||
mark_selection: function (input, color) {
|
||||
this.restore_selection();
|
||||
document.execCommand('hiliteColor', 0, color || 'transparent');
|
||||
this.save_selection();
|
||||
input.data(this.options.selection_marker, color);
|
||||
},
|
||||
|
||||
// bind_touch: function() {
|
||||
// var me = this;
|
||||
// $(window).bind('touchend', function (e) {
|
||||
// var isInside = (me.editor.is(e.target) || me.editor.has(e.target).length > 0),
|
||||
// current_range = me.get_current_range(),
|
||||
// clear = current_range && (current_range.startContainer === current_range.endContainer && current_range.startOffset === current_range.endOffset);
|
||||
// if (!clear || isInside) {
|
||||
// me.save_selection();
|
||||
// me.update();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
bsHTMLEditor = Class.extend({
|
||||
init: function() {
|
||||
var me = this;
|
||||
this.modal = bs_get_modal("<i class='fa fa-code'></i> Edit HTML", '<textarea class="form-control" \
|
||||
style="height: 400px; width: 100%; font-family: Monaco, \'Courier New\', monospace; font-size: 11px">\
|
||||
</textarea>');
|
||||
this.modal.addClass("frappe-ignore-click");
|
||||
this.modal.find(".btn-primary").removeClass("hide").html(__("Update")).on("click", function() {
|
||||
me._html = me.modal.find("textarea").val();
|
||||
|
||||
$.each(me.editor.dataurls, function(key, val) {
|
||||
me._html = replace_all(me._html, key, val);
|
||||
});
|
||||
|
||||
var editor = me.editor.data("object");
|
||||
editor.set_input(me._html);
|
||||
editor.options.change && editor.options.change(editor.clean_html());
|
||||
me.modal.modal("hide");
|
||||
});
|
||||
},
|
||||
show: function(editor) {
|
||||
var me = this;
|
||||
this.editor = editor;
|
||||
this.modal.modal("show");
|
||||
var html = me.editor.html();
|
||||
// pack dataurls so that html display is faster
|
||||
this.editor.dataurls = {}
|
||||
html = html.replace(/<img\s*src=\s*["\'](data:[^,]*),([^"\']*)["\']/g, function(full, g1, g2) {
|
||||
var key = g2.slice(0,5) + "..." + g2.slice(-5);
|
||||
me.editor.dataurls[key] = g1 + "," + g2;
|
||||
return '<img src="'+ key+'"';
|
||||
});
|
||||
this.modal.find("textarea").val(html_beautify(html));
|
||||
}
|
||||
});
|
||||
|
||||
bsLinkEditor = Class.extend({
|
||||
init: function(toolbar) {
|
||||
var me = this;
|
||||
this.toolbar = toolbar;
|
||||
this.modal = bs_get_modal("<i class='fa fa-globe'></i> Insert Link", '<div class="form-group">\
|
||||
<input type="text" class="form-control" placeholder="http://example.com" />\
|
||||
</div>\
|
||||
<div class="checkbox" style="position: static;">\
|
||||
<label>\
|
||||
<input type="checkbox"> <span>' + __("Open Link in a new Window") + '</span>\
|
||||
</label>\
|
||||
</div>\
|
||||
<button class="btn btn-primary" style="margin-top: 7px;">' + __("Insert") + '</button>');
|
||||
|
||||
this.modal.addClass("frappe-ignore-click");
|
||||
this.modal.find(".btn-primary").on("click", function() {
|
||||
me.toolbar.restore_selection();
|
||||
var url = me.modal.find("input[type=text]").val();
|
||||
var selection = me.toolbar.selected_range.toString();
|
||||
if(url) {
|
||||
if(me.modal.find("input[type=checkbox]:checked").length) {
|
||||
var html = "<a href='" + url + "' target='_blank'>" + selection + "</a>";
|
||||
document.execCommand("insertHTML", false, html);
|
||||
} else {
|
||||
document.execCommand("CreateLink", false, url);
|
||||
}
|
||||
}
|
||||
me.modal.modal("hide");
|
||||
return false;
|
||||
});
|
||||
},
|
||||
show: function() {
|
||||
this.modal.find("input[type=text]").val("");
|
||||
this.modal.modal("show");
|
||||
}
|
||||
});
|
||||
|
||||
bs_get_modal = frappe.get_modal;
|
||||
|
|
@ -2,23 +2,7 @@ frappe.provide('frappe.ui.keys.handlers');
|
|||
|
||||
frappe.ui.keys.setup = function() {
|
||||
$(window).on('keydown', function(e) {
|
||||
var key = e.key;
|
||||
//safari doesn't have key property
|
||||
if(!key) {
|
||||
key = String.fromCharCode(e.keyCode).toLowerCase();
|
||||
}
|
||||
if(key.substr(0, 5)==='Arrow') {
|
||||
// ArrowDown -> down
|
||||
key = key.substr(5).toLowerCase();
|
||||
}
|
||||
if(e.ctrlKey || e.metaKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'ctrl+' + key;
|
||||
}
|
||||
if(e.shiftKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'shift+' + key;
|
||||
}
|
||||
var key = frappe.ui.keys.get_key(e);
|
||||
if(frappe.ui.keys.handlers[key]) {
|
||||
var out = null;
|
||||
for(var i=0, l = frappe.ui.keys.handlers[key].length; i<l; i++) {
|
||||
|
|
@ -34,6 +18,29 @@ frappe.ui.keys.setup = function() {
|
|||
});
|
||||
}
|
||||
|
||||
frappe.ui.keys.get_key = function(e) {
|
||||
var key = e.key;
|
||||
//safari doesn't have key property
|
||||
if(!key) {
|
||||
var keycode = e.keyCode || e.which;
|
||||
key = frappe.ui.keys.key_map[keycode] ||
|
||||
String.fromCharCode(keycode);
|
||||
}
|
||||
if(key.substr(0, 5)==='Arrow') {
|
||||
// ArrowDown -> down
|
||||
key = key.substr(5).toLowerCase();
|
||||
}
|
||||
if(e.ctrlKey || e.metaKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'ctrl+' + key;
|
||||
}
|
||||
if(e.shiftKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'shift+' + key;
|
||||
}
|
||||
return key.toLowerCase();
|
||||
}
|
||||
|
||||
frappe.ui.keys.on = function(key, handler) {
|
||||
if(!frappe.ui.keys.handlers[key]) {
|
||||
frappe.ui.keys.handlers[key] = [];
|
||||
|
|
@ -98,3 +105,14 @@ frappe.ui.keys.on('ctrl+up', function(e) {
|
|||
frappe.ui.keys.on('shift+ctrl+r', function(e) {
|
||||
frappe.ui.toolbar.clear_cache();
|
||||
});
|
||||
|
||||
frappe.ui.keys.key_map = {
|
||||
8: 'backspace',
|
||||
9: 'tab',
|
||||
13: 'enter',
|
||||
16: 'shift',
|
||||
17: 'ctrl',
|
||||
91: 'meta',
|
||||
18: 'alt',
|
||||
27: 'escape'
|
||||
}
|
||||
1
frappe/public/js/lib/summernote/summernote.css
Executable file
1
frappe/public/js/lib/summernote/summernote.css
Executable file
File diff suppressed because one or more lines are too long
7046
frappe/public/js/lib/summernote/summernote.js
Executable file
7046
frappe/public/js/lib/summernote/summernote.js
Executable file
File diff suppressed because it is too large
Load diff
|
|
@ -504,4 +504,29 @@ textarea.form-control {
|
|||
|
||||
.search-result {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
// summernote editor
|
||||
.note-editor {
|
||||
margin-top: 5px;
|
||||
|
||||
&.note-frame {
|
||||
border-color: @border-color;
|
||||
}
|
||||
|
||||
.btn {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.dropdown-style > li > a > * {
|
||||
margin: 0;
|
||||
}
|
||||
.fa.fa-check {
|
||||
color: @text-color !important;
|
||||
}
|
||||
.dropdown-menu {
|
||||
z-index: 100;
|
||||
max-height: 300px;
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue