Merge pull request #426 from ZmagoD/zd_add_tinyMCE

Add tiny mce
This commit is contained in:
Zmago Devetak 2017-01-19 12:57:55 +01:00 committed by GitHub
commit 039a8d7da6
21 changed files with 221 additions and 81 deletions

View file

@ -62,9 +62,8 @@ gem 'aws-sdk-v1'
gem 'delayed_job_active_record'
gem 'devise-async'
gem 'ruby-graphviz', '~> 1.2' # Graphviz for rails
gem 'quill-rails', # Rich text editor
git: 'https://github.com/biosistemika/quill-rails.git',
ref: 'e765c04'
gem 'tinymce-rails' # Rich text editor
gem 'base62' # Used for smart annotations
group :development, :test do

View file

@ -27,6 +27,8 @@
//= require bootstrap-checkbox.min
//= require typeahead.bundle.min
//= require nested_form_fields
//= require highlight.pack
//= require tinymce-jquery
//= require_directory ./sitewide
//= require datatables
//= require dataTables.noSearchHidden
@ -35,7 +37,6 @@
//= require i18n.js
//= require i18n/translations
//= require turbolinks
//= require quill
// Initialize links for submitting forms. This is useful for submitting
@ -228,3 +229,12 @@ var HelperModule = (function(){
return helpers;
})();
// initialize code markup in rich text fields
(function() {
$(document).ready(function() {
$('pre code [class^=language]').each(function(i, block) {
hljs.highlightBlock(block);
});
});
})();

View file

@ -183,8 +183,8 @@ function processResult(ev, resultTypeEnum, editMode, forS3) {
var $nameInput = $form.find("#result_name");
var nameValid = textValidator(ev, $nameInput, 0,
<%= Constants::NAME_MAX_LENGTH %>);
var $textInput = $form.find("#result_result_text_attributes_text");
textValidator(ev, $textInput, 1, <%= Constants::TEXT_MAX_LENGTH %>);
var $textInput = TinyMCE.getContent();
textValidator(ev, $textInput, 1, <%= Constants::TEXT_MAX_LENGTH %>, false, true);
break;
case ResultTypeEnum.COMMENT:
var $commentInput = $form.find("#comment_message");

View file

@ -79,7 +79,6 @@ function applyCancelCallBack() {
setTimeout(function() {
initStepsComments();
openLinksInNewTab();
}, 1000);
})
@ -104,6 +103,7 @@ function applyEditCallBack() {
toggleButtons(false);
initializeCheckboxSorting();
TinyMCE.init();
$("#new-step-checklists fieldset.nested_step_checklists ul").each(function () {
enableCheckboxSorting(this);
});
@ -111,7 +111,6 @@ function applyEditCallBack() {
$("#new-step-main-tab a").on("shown.bs.tab", function() {
$("#step_name").focus();
});
openLinksInNewTab();
});
}
@ -182,7 +181,6 @@ function formCallback($form) {
setTimeout(function() {
initStepsComments();
openLinksInNewTab();
}, 1000);
return true;
});
@ -205,6 +203,7 @@ function formEditAjax($form) {
initHandsOnTable($new_step);
toggleButtons(true);
TinyMCE.destroyAll();
// Show the edited step
$new_step.find(".panel-collapse:first").addClass("collapse in");
@ -220,6 +219,7 @@ function formEditAjax($form) {
initEditableHandsOnTable($form);
applyCancelCallBack();
TinyMCE.refresh();
//Rerender tables
$form.find("[data-role='step-hot-table']").each(function() {
renderTable($(this));
@ -244,6 +244,7 @@ function formNewAjax($form) {
expandStep($new_step);
toggleButtons(true);
TinyMCE.init();
//Rerender tables
$new_step.find("div.step-result-hot-table").each(function() {
$(this).handsontable("render");
@ -260,6 +261,7 @@ function formNewAjax($form) {
formCallback($form);
formNewAjax($form);
applyCancelOnNew();
TinyMCE.destroyAll();
});
}
@ -384,7 +386,7 @@ function initCallBacks() {
applyMoveStepCallBack();
applyCollapseLinkCallBack();
initDeleteStep();
initHighlightjs();
TinyMCE.highlight();
}
/*
@ -487,6 +489,7 @@ $("[data-action='new-step']").on("ajax:success", function(e, data) {
applyCancelOnNew();
toggleButtons(false);
initializeCheckboxSorting();
TinyMCE.init();
$("#step_name").focus();
$("#new-step-main-tab a").on("shown.bs.tab", function() {
@ -556,14 +559,6 @@ function renderTable(table) {
}
}
function initHighlightjs() {
if(hljs){
$('.ql-editor pre').each(function(i, block) {
hljs.highlightBlock(block);
});
}
}
function initStepsComments() {
Comments.initialize();
Comments.initCommentOptions("ul.content-comments");
@ -578,7 +573,7 @@ $(document).ready(function() {
expandAllSteps();
setupAssetsLoading();
initStepsComments();
initHighlightjs();
TinyMCE.highlight();
$(function () {

View file

@ -11,6 +11,7 @@ $("#new-result-text").on("ajax:success", function(e, data) {
toggleResultEditButtons(true);
});
TinyMCE.init();
toggleResultEditButtons(false);
$("#result_name").focus();
@ -39,6 +40,7 @@ function applyEditResultTextCallback() {
toggleResultEditButtons(true);
});
TinyMCE.init();
toggleResultEditButtons(false);
$("#result_name").focus();
@ -61,12 +63,12 @@ function formAjaxResultText($form) {
applyCollapseLinkCallBack();
toggleResultEditButtons(true);
expandResult(newResult);
initHighlightjs();
TinyMCE.destroyAll();
});
$form.on("ajax:error", function(e, xhr, status, error) {
var data = xhr.responseJSON;
$form.renderFormErrors("result", data);
initHighlightjs();
TinyMCE.highlight();
if (data["result_text.text"]) {
var $el = $form.find("textarea[name=result\\[result_text_attributes\\]\\[text\\]]");
@ -76,15 +78,7 @@ function formAjaxResultText($form) {
});
}
function initHighlightjs() {
if(hljs) {
$('.ql-editor pre').each(function(i, block) {
hljs.highlightBlock(block);
});
}
}
$(document).ready(function() {
initHighlightjs();
TinyMCE.highlight();
});
applyEditResultTextCallback();

View file

@ -249,7 +249,7 @@ var SmartAnnotation = (function() {
function atWhoSwitchHack(filterTypeTag, remoteFilterCb) {
if(atWhoUpdating || (!$(field).length && _.isUndefined(filterTypeTag))) {
setTimeout(function() {
$(field).atwho('run');
$(field).click();
}, 100);
return;
}

View file

@ -22,13 +22,17 @@ $.fn.onSubmitValidator = function(validatorCb) {
* @param {boolean} clearErr Set clearErr to true if this is the only
* error that can happen/show.
*/
function textValidator(ev, textInput, textLimitMin, textLimitMax, clearErr) {
function textValidator(ev, textInput, textLimitMin, textLimitMax, clearErr, tinyMCE) {
clearErr = _.isUndefined(clearErr) ? false : clearErr;
var text = $(textInput).val().trim();
$(textInput).val(text);
var text_from_html = $("<div/>").html(text).text();
if (text_from_html.length < text.length) text = text_from_html;
if(tinyMCE){
var text = textInput.length;
} else {
var text = $(textInput).val().trim();
$(textInput).val(text);
var text_from_html = $("<div/>").html(text).text();
if (text_from_html.length < text.length) text = text_from_html;
}
var nameTooShort = text.length < textLimitMin;
var nameTooLong = text.length > textLimitMax;

View file

@ -1,23 +0,0 @@
//= require quill
// Globally overwrite links handling in Quill rich text editor
var Link = Quill.import('formats/link');
Link.sanitize = function(url) {
if (url.includes('http:') || url.includes('https:')) {
return url;
}
return 'http://' + url;
};
function openLinksInNewTab() {
_.each($('.ql-editor a'), function(el) {
if ($(el).attr('target') !== '_blank') {
$(el).attr('target', '_blank');
}
});
}
$(document).ready(function(){
openLinksInNewTab();
});

View file

@ -0,0 +1,48 @@
var TinyMCE = (function() {
'use strict';
function initHighlightjs() {
$('pre code [class^=language]').each(function(i, block) {
if(!$(block).hasClass('hljs')) {
hljs.highlightBlock(block);
}
});
}
return Object.freeze({
init : function() {
if (typeof tinyMCE != 'undefined') {
tinyMCE.init({
selector: "textarea.tinymce",
toolbar: ["undo redo | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | print preview | forecolor backcolor | codesample"],
plugins: "link,advlist,codesample,autolink,lists,link,charmap,print,preview,hr,anchor,pagebreak,searchreplace,wordcount,visualblocks,visualchars,code,fullscreen,insertdatetime,nonbreaking,save,table,contextmenu,directionality,paste,textcolor,colorpicker,textpattern,imagetools,toc",
codesample_languages: [{"text":"R","value":"r"},{"text":"MATLAB","value":"matlab"},{"text":"Python","value":"python"},{"text":"JSON","value":"javascript"},{"text":"HTML/XML","value":"markup"},{"text":"JavaScript","value":"javascript"},{"text":"CSS","value":"css"},{"text":"PHP","value":"php"},{"text":"Ruby","value":"ruby"},{"text":"Java","value":"java"},{"text":"C","value":"c"},{"text":"C#","value":"csharp"},{"text":"C++","value":"cpp"}],
init_instance_callback: function(editor) {
SmartAnnotation.init($(editor.contentDocument.activeElement));
},
setup: function(editor) {
editor.on('keydown', function(e) {
if(e.keyCode == 13 && $(editor.contentDocument.activeElement).atwho('isSelecting'))
return false;
});
}
});
}
},
destroyAll: function() {
_.each(tinymce.editors, function(editor) {
editor.destroy();
initHighlightjs();
});
},
refresh: function() {
this.destroyAll();
this.init();
},
getContent: function() {
return tinymce.editors[0].getContent();
},
highlight: initHighlightjs
});
})();

View file

@ -1,4 +1,5 @@
/*
*= require highlightjs-github-theme
*= require_self
*= require_tree .
*= require jquery-ui/draggable
@ -7,7 +8,6 @@
*= require constants
*= require introjs
*= stub reports_pdf
*= require quill.snow
*/
@import "bootstrap-sprockets";
@import "bootstrap";

View file

@ -260,5 +260,11 @@ module BootstrapFormHelper
end
text_area(name, opts)
end
# Returns <textarea> helper tag for tinyMCE editor
def tiny_mce_editor(name, options = {})
options.merge!(class: 'tinymce', cols: 120, rows: 40)
text_area(name, options)
end
end
end

View file

@ -1,6 +1,6 @@
module QuillJsHelper
def sanitize_quill_js_input(input)
require "#{Rails.root}/app/utilities/scrubbers/quill_js_scrubber"
module TinyMceJsHelper
def sanitize_tiny_mce_js_input(input)
require "#{Rails.root}/app/utilities/scrubbers/tiny_mce_js_scrubber"
# We need to disable formatting to prevent unwanted \n
# symbols from creeping into sanitized HTML (which
@ -11,7 +11,7 @@ module QuillJsHelper
Loofah
.fragment(input)
.scrub!(QuillJsScrubber.new)
.scrub!(TinyMceJsScrubber.new)
.to_html(save_with: disable_formatting)
end
end

View file

@ -1,5 +1,5 @@
module ProtocolsImporter
include RenamingUtil, QuillJsHelper
include RenamingUtil, TinyMceJsHelper
def import_new_protocol(protocol_json, organization, type, user)
remove_empty_inputs(protocol_json)
@ -54,7 +54,7 @@ module ProtocolsImporter
step = Step.create!(
name: step_json["name"],
description: # Sanitize description HTML
sanitize_quill_js_input(
sanitize_tiny_mce_js_input(
step_json['description']
),
position: step_pos,

View file

@ -3,7 +3,7 @@
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
<%= f.fields_for :result_text do |ff| %>
<div class="form-group">
<%= quill_editor nil, { id: 'result_result_text_attributes_text', name: 'result[result_text_attributes][text]', value: @result.result_text.text } %>
<%= ff.tiny_mce_editor(:text, value: @result.result_text.text) %>
</div>
<% end %><br />
<%= f.submit t("result_texts.edit.update"), class: 'btn btn-primary save-result', onclick: "processResult(event, ResultTypeEnum.TEXT, true);" %>

View file

@ -3,7 +3,7 @@
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
<%= f.fields_for :result_text do |ff| %>
<div class="form-group">
<%= quill_editor nil, { id: 'result_result_text_attributes_text', name: 'result[result_text_attributes][text]', value: @result.result_text.text } %>
<%= ff.tiny_mce_editor(:text) %>
</div>
<% end %><br />
<%= f.submit t("result_texts.new.create"), class: 'btn btn-primary save-result', onclick: "processResult(event, ResultTypeEnum.TEXT, false);" %>

View file

@ -1,5 +1,5 @@
<div class="ql-editor">
<%= auto_link(result.result_text.text,
<%= auto_link(smart_annotation_parser(result.result_text.text),
link: :urls,
html: { target: '_blank' }).html_safe %>
</div>

View file

@ -27,11 +27,7 @@
<div class="tab-pane active" role="tabpanel" id="new-step-main">
<%= f.text_field :name, label: t("protocols.steps.new.name"), placeholder: t("protocols.steps.new.name_placeholder") %>
<div class="form-group">
<label class="control-label" for="step_description"><%= t('protocols.steps.new.description') %></label>
<%= quill_editor nil, { id: 'step_description',
name: 'step[description]',
value: @step.description,
data: { 'atwho-res-edit' => '' } } %>
<%= f.tiny_mce_editor(:description) %>
</div>
</div>
<div class="tab-pane" role="tabpanel" id="new-step-checklists">

View file

@ -37,8 +37,9 @@
<em><%= t("protocols.steps.no_description") %></em>
<% else %>
<div class="ql-editor">
<%= auto_link(step.description,
<%= auto_link(smart_annotation_parser(step.description),
link: :urls,
sanitize: false,
html: { target: '_blank' }).html_safe %>
</div>
<% end %>
@ -89,11 +90,15 @@
<div class="col-xs-12">
<% step.checklists.each do |checklist| %>
<strong><%= auto_link(
simple_format(
smart_annotation_parser(checklist.name)
smart_annotation_parser(
simple_format(
checklist.name,
sanitize: false
)
),
link: :urls,
html: { target: '_blank' }) %></strong>
sanitize: false,
html: { target: '_blank' }).html_safe %></strong>
<% if checklist.checklist_items.empty? %>
</br>
<%= t("protocols.steps.empty_checklist") %>
@ -108,11 +113,15 @@
<input type="checkbox" value="" disabled="disabled" />
<% end %>
<%= auto_link(
simple_format(
smart_annotation_parser(checklist_item.text)
smart_annotation_parser(
simple_format(
checklist_item.text,
sanitize: false
)
),
link: :urls,
html: { target: '_blank' }) %>
sanitize: false,
html: { target: '_blank' }).html_safe %>
</label>
</div>
<% end %>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,99 @@
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #333;
background: #f8f8f8;
}
.hljs-comment,
.hljs-quote {
color: #998;
font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-subst {
color: #333;
font-weight: bold;
}
.hljs-number,
.hljs-literal,
.hljs-variable,
.hljs-template-variable,
.hljs-tag .hljs-attr {
color: #008080;
}
.hljs-string,
.hljs-doctag {
color: #d14;
}
.hljs-title,
.hljs-section,
.hljs-selector-id {
color: #900;
font-weight: bold;
}
.hljs-subst {
font-weight: normal;
}
.hljs-type,
.hljs-class .hljs-title {
color: #458;
font-weight: bold;
}
.hljs-tag,
.hljs-name,
.hljs-attribute {
color: #000080;
font-weight: normal;
}
.hljs-regexp,
.hljs-link {
color: #009926;
}
.hljs-symbol,
.hljs-bullet {
color: #990073;
}
.hljs-built_in,
.hljs-builtin-name {
color: #0086b3;
}
.hljs-meta {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}