mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-04 14:44:26 +08:00
commit
039a8d7da6
21 changed files with 221 additions and 81 deletions
5
Gemfile
5
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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 () {
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
48
app/assets/javascripts/sitewide/tiny_mce.js
vendored
Normal file
48
app/assets/javascripts/sitewide/tiny_mce.js
vendored
Normal 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
|
||||
});
|
||||
|
||||
})();
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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);" %>
|
||||
|
|
|
@ -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);" %>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 %>
|
||||
|
|
3
vendor/assets/javascripts/highlight.pack.js
vendored
Normal file
3
vendor/assets/javascripts/highlight.pack.js
vendored
Normal file
File diff suppressed because one or more lines are too long
99
vendor/assets/stylesheets/highlightjs-github-theme.css
vendored
Normal file
99
vendor/assets/stylesheets/highlightjs-github-theme.css
vendored
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue