diff --git a/Gemfile b/Gemfile index e7446cae8..c100b466e 100644 --- a/Gemfile +++ b/Gemfile @@ -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 diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index 5838a2ba6..9cb8c9a80 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -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); + }); + }); +})(); diff --git a/app/assets/javascripts/my_modules/results.js.erb b/app/assets/javascripts/my_modules/results.js.erb index f5d0e824f..774466a91 100644 --- a/app/assets/javascripts/my_modules/results.js.erb +++ b/app/assets/javascripts/my_modules/results.js.erb @@ -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"); diff --git a/app/assets/javascripts/protocols/steps.js.erb b/app/assets/javascripts/protocols/steps.js.erb index 112387b96..8104718f4 100644 --- a/app/assets/javascripts/protocols/steps.js.erb +++ b/app/assets/javascripts/protocols/steps.js.erb @@ -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 () { diff --git a/app/assets/javascripts/results/result_texts.js b/app/assets/javascripts/results/result_texts.js index b102adeb1..0128ea589 100644 --- a/app/assets/javascripts/results/result_texts.js +++ b/app/assets/javascripts/results/result_texts.js @@ -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(); diff --git a/app/assets/javascripts/sitewide/atwho_res.js.erb b/app/assets/javascripts/sitewide/atwho_res.js.erb index a82df87c4..d9bd6cd2c 100644 --- a/app/assets/javascripts/sitewide/atwho_res.js.erb +++ b/app/assets/javascripts/sitewide/atwho_res.js.erb @@ -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; } diff --git a/app/assets/javascripts/sitewide/form_validators.js.erb b/app/assets/javascripts/sitewide/form_validators.js.erb index c0e3eddd3..8b82985a4 100644 --- a/app/assets/javascripts/sitewide/form_validators.js.erb +++ b/app/assets/javascripts/sitewide/form_validators.js.erb @@ -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 = $("
").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 = $("").html(text).text(); + if (text_from_html.length < text.length) text = text_from_html; + } var nameTooShort = text.length < textLimitMin; var nameTooLong = text.length > textLimitMax; diff --git a/app/assets/javascripts/sitewide/quill_links.js b/app/assets/javascripts/sitewide/quill_links.js deleted file mode 100644 index c1bd7ffc7..000000000 --- a/app/assets/javascripts/sitewide/quill_links.js +++ /dev/null @@ -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(); -}); diff --git a/app/assets/javascripts/sitewide/tiny_mce.js b/app/assets/javascripts/sitewide/tiny_mce.js new file mode 100644 index 000000000..8214eaaa1 --- /dev/null +++ b/app/assets/javascripts/sitewide/tiny_mce.js @@ -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 + }); + +})(); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 423693c49..023bd4d94 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -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"; diff --git a/app/helpers/bootstrap_form_helper.rb b/app/helpers/bootstrap_form_helper.rb index f707c4740..1db5da6fb 100644 --- a/app/helpers/bootstrap_form_helper.rb +++ b/app/helpers/bootstrap_form_helper.rb @@ -260,5 +260,11 @@ module BootstrapFormHelper end text_area(name, opts) end + + # Returns