mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Merge pull request #568 from ZmagoD/zd_SCI_1119
Add images to fields with rich text formatting
This commit is contained in:
commit
4b816f5322
2
Gemfile
2
Gemfile
|
@ -65,7 +65,7 @@ gem 'aws-sdk-v1'
|
|||
gem 'delayed_job_active_record'
|
||||
gem 'devise-async'
|
||||
gem 'ruby-graphviz', '~> 1.2' # Graphviz for rails
|
||||
gem 'tinymce-rails' # Rich text editor
|
||||
gem 'tinymce-rails', '~> 4.5.7' # Rich text editor
|
||||
|
||||
gem 'base62' # Used for smart annotations
|
||||
gem 'newrelic_rpm'
|
||||
|
|
|
@ -310,7 +310,7 @@ GEM
|
|||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.1)
|
||||
tinymce-rails (4.5.2)
|
||||
tinymce-rails (4.5.7)
|
||||
railties (>= 3.1.1)
|
||||
turbolinks (2.5.3)
|
||||
coffee-rails
|
||||
|
@ -398,7 +398,7 @@ DEPENDENCIES
|
|||
sneaky-save!
|
||||
spinjs-rails
|
||||
starscope
|
||||
tinymce-rails
|
||||
tinymce-rails (~> 4.5.7)
|
||||
turbolinks
|
||||
tzinfo-data
|
||||
uglifier (>= 1.3.0)
|
||||
|
|
|
@ -543,12 +543,15 @@ $("[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() {
|
||||
$("#step_name").focus();
|
||||
});
|
||||
|
||||
TinyMCE.refresh();
|
||||
|
||||
});
|
||||
|
||||
// Needed because server-side validation failure clears locations of
|
||||
|
|
|
@ -18,13 +18,15 @@ var TinyMCE = (function() {
|
|||
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 | forecolor backcolor | codesample"],
|
||||
plugins: "link,advlist,codesample,autolink,lists,charmap,hr,anchor,searchreplace,wordcount,visualblocks,visualchars,insertdatetime,nonbreaking,save,contextmenu,directionality,paste,textcolor,colorpicker,textpattern,imagetools",
|
||||
selector: 'textarea.tinymce',
|
||||
toolbar: ["undo redo | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | forecolor backcolor | customimageuploader | codesample"],
|
||||
plugins: "customimageuploader,link,advlist,codesample,autolink,lists,charmap,hr,anchor,searchreplace,wordcount,visualblocks,visualchars,insertdatetime,nonbreaking,save,contextmenu,directionality,paste,textcolor,colorpicker,textpattern",
|
||||
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"}],
|
||||
removed_menuitems: 'newdocument',
|
||||
object_resizing: false,
|
||||
elementpath: false,
|
||||
default_link_target: "_blank",
|
||||
forced_root_block: false,
|
||||
default_link_target: '_blank',
|
||||
target_list: [
|
||||
{title: 'New page', value: '_blank'},
|
||||
{title: 'Same page', value: '_self'}
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
tinymce.PluginManager.requireLangPack('customimageuploader');
|
||||
|
||||
tinymce.create('tinymce.plugins.CustomImageUploader', {
|
||||
CustomImageUploader: function(ed, url) {
|
||||
var form, iframe, win, throbber, editor = ed;
|
||||
function showDialog() {
|
||||
win = editor.windowManager.open({
|
||||
title: "<%= I18n.t 'tiny_mce.upload_window_title' %>",
|
||||
width: 520 + parseInt(editor.getLang(
|
||||
'customimageuploader.delta_width', 0
|
||||
), 10),
|
||||
height: 180 + parseInt(editor.getLang(
|
||||
'customimageuploader.delta_height', 0
|
||||
), 10),
|
||||
body: [
|
||||
{type: 'iframe', url: 'javascript:void(0)'},
|
||||
{type: 'textbox',
|
||||
name: 'file',
|
||||
label: "<%= I18n.t 'tiny_mce.upload_window_label' %>",
|
||||
subtype: 'file'},
|
||||
{type: 'container',
|
||||
classes: 'error',
|
||||
html: "<p style='color: #b94a48;'> </p>"},
|
||||
{type: 'container', classes: 'throbber'},
|
||||
],
|
||||
buttons: [
|
||||
{text: "<%= I18n.t 'tiny_mce.insert_btn' %>",
|
||||
onclick: insertImage,
|
||||
subtype: 'primary'},
|
||||
{text: "<%= I18n.t 'general.cancel' %>",
|
||||
onclick: editor.windowManager.close}
|
||||
],
|
||||
}, {
|
||||
plugin_url: url
|
||||
});
|
||||
|
||||
win.off('submit');
|
||||
win.on('submit', insertImage);
|
||||
|
||||
iframe = win.find('iframe')[0];
|
||||
form = createElement('form', {
|
||||
action: editor.getParam('customimageuploader_form_url',
|
||||
'<%= Rails.application
|
||||
.routes
|
||||
.url_helpers
|
||||
.tiny_mce_assets_path %>'),
|
||||
target: iframe._id,
|
||||
method: 'POST',
|
||||
enctype: 'multipart/form-data',
|
||||
accept_charset: 'UTF-8',
|
||||
});
|
||||
|
||||
iframe.getEl().name = iframe._id;
|
||||
|
||||
// Create some needed hidden inputs
|
||||
form.appendChild(createElement('input',
|
||||
{type: 'hidden',
|
||||
name: 'utf8',
|
||||
value: '✓'}));
|
||||
form.appendChild(createElement('input',
|
||||
{type: 'hidden',
|
||||
name: 'authenticity_token',
|
||||
value: getMetaContents('csrf-token')}));
|
||||
form.appendChild(createElement('input',
|
||||
{type: 'hidden',
|
||||
name: 'object_type',
|
||||
value: $(editor.getElement())
|
||||
.data('object-type')}));
|
||||
form.appendChild(createElement('input',
|
||||
{type: 'hidden',
|
||||
name: 'object_id',
|
||||
value: $(editor.getElement())
|
||||
.data('object-id')}));
|
||||
form.appendChild(createElement('input',
|
||||
{type: 'hidden',
|
||||
name: 'hint',
|
||||
value: editor.getParam('uploadimage_hint', '')}));
|
||||
|
||||
var el = win.getEl();
|
||||
var body = document.getElementById(el.id + '-body');
|
||||
|
||||
// Copy everything TinyMCE made into our form
|
||||
var containers = body.getElementsByClassName('mce-container');
|
||||
for(var i = 0; i < containers.length; i++) {
|
||||
form.appendChild(containers[i]);
|
||||
}
|
||||
|
||||
var inputs = form.getElementsByTagName('input');
|
||||
for(var i = 0; i < inputs.length; i++) {
|
||||
var ctrl = inputs[i];
|
||||
|
||||
if(ctrl.tagName.toLowerCase() == 'input' && ctrl.type != 'hidden') {
|
||||
if(ctrl.type == 'file') {
|
||||
ctrl.name = 'file';
|
||||
|
||||
tinymce.DOM.setStyles(ctrl, {
|
||||
'border': 0,
|
||||
'boxShadow': 'none',
|
||||
'webkitBoxShadow': 'none',
|
||||
});
|
||||
} else {
|
||||
ctrl.name = 'alt';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.appendChild(form);
|
||||
}
|
||||
|
||||
function insertImage() {
|
||||
if(getInputValue('file') == '') {
|
||||
return handleError("<%= I18n.t 'tiny_mce.error_message' %>");
|
||||
}
|
||||
|
||||
throbber = new top.tinymce.ui.Throbber(win.getEl());
|
||||
throbber.show();
|
||||
|
||||
clearErrors();
|
||||
|
||||
/* Add event listeners.
|
||||
* We remove the existing to avoid them being called twice in case
|
||||
* of errors and re-submitting afterwards.
|
||||
*/
|
||||
var target = iframe.getEl();
|
||||
if(target.attachEvent) {
|
||||
target.detachEvent('onload', uploadDone);
|
||||
target.attachEvent('onload', uploadDone);
|
||||
} else {
|
||||
target.removeEventListener('load', uploadDone);
|
||||
target.addEventListener('load', uploadDone, false);
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function uploadDone() {
|
||||
if(throbber) {
|
||||
throbber.hide();
|
||||
}
|
||||
|
||||
var target = iframe.getEl();
|
||||
if(target.document || target.contentDocument) {
|
||||
var doc = target.contentDocument || target.contentWindow.document;
|
||||
handleResponse(doc.getElementsByTagName("body")[0].innerHTML);
|
||||
} else {
|
||||
handleError("<%= I18n.t 'tiny_mce.server_not_respond' %>");
|
||||
}
|
||||
}
|
||||
|
||||
function handleResponse(ret) {
|
||||
try {
|
||||
var json = tinymce.util.JSON.parse(ret);
|
||||
|
||||
if(json['error']) {
|
||||
handleError(json['error']['message']);
|
||||
} else {
|
||||
editor.execCommand('mceInsertContent', false, buildHTML(json));
|
||||
editor.windowManager.close();
|
||||
}
|
||||
} catch(e) {
|
||||
// hack that gets the server error message
|
||||
var error_json = JSON.parse($(ret).text());
|
||||
handleError(error_json['error'][0]);
|
||||
}
|
||||
}
|
||||
|
||||
function clearErrors() {
|
||||
var message = win.find('.error')[0].getEl();
|
||||
|
||||
if(message)
|
||||
message.getElementsByTagName('p')[0].innerHTML = ' ';
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
var message = win.find('.error')[0].getEl();
|
||||
|
||||
if(message) {
|
||||
message
|
||||
.getElementsByTagName('p')[0]
|
||||
.innerHTML = editor.translate(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createElement(element, attributes) {
|
||||
var el = document.createElement(element);
|
||||
for(var property in attributes) {
|
||||
if (!(attributes[property] instanceof Function)) {
|
||||
el[property] = attributes[property];
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
function buildHTML(json) {
|
||||
var imgstr = "<img src='" + json['image']['url'] + "'";
|
||||
imgstr += " data-token='" + json['image']['token'] + "'"
|
||||
imgstr += " alt='description-" + json['image']['token'] + "'/>";
|
||||
return imgstr;
|
||||
}
|
||||
|
||||
function getInputValue(name) {
|
||||
var inputs = form.getElementsByTagName('input');
|
||||
|
||||
for(var i in inputs)
|
||||
if(inputs[i].name == name)
|
||||
return inputs[i].value;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function getMetaContents(mn) {
|
||||
var m = document.getElementsByTagName('meta');
|
||||
|
||||
for(var i in m)
|
||||
if(m[i].name == mn)
|
||||
return m[i].content;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add a button that opens a window
|
||||
editor.addButton('customimageuploader', {
|
||||
tooltip: "<%= I18n.t 'tiny_mce.upload_window_label' %>",
|
||||
icon: 'image',
|
||||
onclick: showDialog
|
||||
});
|
||||
|
||||
// Adds a menu item to the tools menu
|
||||
editor.addMenuItem('customimageuploader', {
|
||||
text: "<%= I18n.t 'tiny_mce.upload_window_label' %>",
|
||||
icon: 'image',
|
||||
context: 'insert',
|
||||
onclick: showDialog
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tinymce.PluginManager.add('customimageuploader',
|
||||
tinymce.plugins.CustomImageUploader);
|
||||
})();
|
|
@ -2,6 +2,7 @@ class ResultTextsController < ApplicationController
|
|||
include ResultsHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ApplicationHelper
|
||||
include TinyMceHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
before_action :load_vars, only: [:edit, :update, :download]
|
||||
|
@ -31,6 +32,8 @@ class ResultTextsController < ApplicationController
|
|||
|
||||
def create
|
||||
@result_text = ResultText.new(result_params[:result_text_attributes])
|
||||
# gerate a tag that replaces img tag in database
|
||||
@result_text.text = parse_tiny_mce_asset_to_token(@result_text.text)
|
||||
@result = Result.new(
|
||||
user: current_user,
|
||||
my_module: @my_module,
|
||||
|
@ -40,7 +43,9 @@ class ResultTextsController < ApplicationController
|
|||
@result.last_modified_by = current_user
|
||||
|
||||
respond_to do |format|
|
||||
if (@result.save and @result_text.save) then
|
||||
if @result.save && @result_text.save
|
||||
# link tiny_mce_assets to the text result
|
||||
link_tiny_mce_assets(@result_text.text, @result_text)
|
||||
|
||||
result_annotation_notification
|
||||
# Generate activity
|
||||
|
@ -82,6 +87,7 @@ class ResultTextsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
@result_text.text = generate_image_tag_from_token(@result_text.text)
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
render json: {
|
||||
|
@ -98,6 +104,8 @@ class ResultTextsController < ApplicationController
|
|||
update_params = result_params
|
||||
@result.last_modified_by = current_user
|
||||
@result.assign_attributes(update_params)
|
||||
@result_text.text = parse_tiny_mce_asset_to_token(@result_text.text,
|
||||
@result_text)
|
||||
success_flash = t("result_texts.update.success_flash",
|
||||
module: @my_module.name)
|
||||
if @result.archived_changed?(from: false, to: true)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class StepsController < ApplicationController
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ApplicationHelper
|
||||
include TinyMceHelper
|
||||
include StepsActions
|
||||
|
||||
before_action :load_vars, only: [:edit, :update, :destroy, :show]
|
||||
|
@ -28,7 +29,8 @@ class StepsController < ApplicationController
|
|||
|
||||
def create
|
||||
@step = Step.new(step_params)
|
||||
|
||||
# gerate a tag that replaces img tag in database
|
||||
@step.description = parse_tiny_mce_asset_to_token(@step.description)
|
||||
@step.completed = false
|
||||
@step.position = @protocol.number_of_steps
|
||||
@step.protocol = @protocol
|
||||
|
@ -57,7 +59,11 @@ class StepsController < ApplicationController
|
|||
asset.post_process_file(@protocol.team)
|
||||
end
|
||||
|
||||
# link tiny_mce_assets to the step
|
||||
link_tiny_mce_assets(@step.description, @step)
|
||||
|
||||
create_annotation_notifications(@step)
|
||||
|
||||
# Generate activity
|
||||
if @protocol.in_module?
|
||||
Activity.create(
|
||||
|
@ -120,6 +126,7 @@ class StepsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
@step.description = generate_image_tag_from_token(@step.description)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
|
@ -159,6 +166,12 @@ class StepsController < ApplicationController
|
|||
table.team = current_team
|
||||
end
|
||||
|
||||
# gerate a tag that replaces img tag in databases
|
||||
@step.description = parse_tiny_mce_asset_to_token(
|
||||
params[:step][:description],
|
||||
@step
|
||||
)
|
||||
|
||||
if @step.save
|
||||
@step.reload
|
||||
|
||||
|
|
31
app/controllers/tiny_mce_assets_controller.rb
Normal file
31
app/controllers/tiny_mce_assets_controller.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
class TinyMceAssetsController < ApplicationController
|
||||
before_action :find_object
|
||||
|
||||
def create
|
||||
image = params.fetch(:file) { render_404 }
|
||||
tiny_img = TinyMceAsset.new(image: image,
|
||||
reference: @obj,
|
||||
team_id: current_team.id)
|
||||
if tiny_img.save
|
||||
render json: {
|
||||
image: {
|
||||
url: view_context.image_url(tiny_img.url(:large)),
|
||||
token: Base62.encode(tiny_img.id)
|
||||
}
|
||||
}, content_type: 'text/html'
|
||||
else
|
||||
render json: {
|
||||
error: tiny_img.errors.full_messages
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_object
|
||||
obj_type = params.fetch(:object_type) { render_404 }
|
||||
obj_id = params.fetch(:object_id) { render_404 }
|
||||
render_404 unless %w(step result_text).include? obj_type
|
||||
@obj = obj_type.classify.constantize.find_by_id(obj_id)
|
||||
end
|
||||
end
|
|
@ -130,9 +130,8 @@ class SampleDatatable < AjaxDatatablesRails::Base
|
|||
|
||||
# Add custom attributes
|
||||
record.sample_custom_fields.each do |scf|
|
||||
sample[@cf_mappings[scf.custom_field_id]] = custom_auto_link(scf.value,
|
||||
true,
|
||||
@team)
|
||||
sample[@cf_mappings[scf.custom_field_id]] =
|
||||
custom_auto_link(scf.value, simple_format: true, team: @team)
|
||||
end
|
||||
sample
|
||||
end
|
||||
|
|
|
@ -103,6 +103,9 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def smart_annotation_parser(text, team = nil)
|
||||
# sometimes happens that the "team" param gets wrong data: "{nil, []}"
|
||||
# so we have to check if the "team" param is kind of Team object
|
||||
team = nil unless team.is_a? Team
|
||||
new_text = smart_annotation_filter_resources(text)
|
||||
new_text = smart_annotation_filter_users(new_text, team)
|
||||
new_text
|
||||
|
@ -187,10 +190,9 @@ module ApplicationHelper
|
|||
if user &&
|
||||
team &&
|
||||
UserTeam.user_in_team(user, team).any?
|
||||
user_t, = user
|
||||
.user_teams
|
||||
.where('user_teams.team_id = ?', team)
|
||||
.first
|
||||
user_t = user.user_teams
|
||||
.where('user_teams.team_id = ?', team)
|
||||
.first
|
||||
end
|
||||
user_description = %(<div class='col-xs-4'>
|
||||
<img src='#{user_avatar_absolute_url(user, :thumb)}'
|
||||
|
|
|
@ -15,14 +15,18 @@ module InputSanitizeHelper
|
|||
ERB::Util.html_escape(text)
|
||||
end
|
||||
|
||||
def custom_auto_link(text, simple_format = true, org = nil, wrapper_tag = {})
|
||||
def custom_auto_link(text, options = {})
|
||||
simple_format = options.fetch(:simple_format) { true }
|
||||
team = options.fetch(:team) { nil },
|
||||
wrapper_tag = options.fetch(:wrapper_tag) { {} }
|
||||
tags = options.fetch(:tags) { [] }
|
||||
text = if simple_format
|
||||
simple_format(sanitize_input(text), {}, wrapper_tag)
|
||||
else
|
||||
sanitize_input(text)
|
||||
sanitize_input(text, tags)
|
||||
end
|
||||
auto_link(
|
||||
smart_annotation_parser(text, org),
|
||||
smart_annotation_parser(text, team),
|
||||
link: :urls,
|
||||
sanitize: false,
|
||||
html: { target: '_blank' }
|
||||
|
|
47
app/helpers/tiny_mce_helper.rb
Normal file
47
app/helpers/tiny_mce_helper.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
module TinyMceHelper
|
||||
def parse_tiny_mce_asset_to_token(text, ref = nil)
|
||||
ids = []
|
||||
html = Nokogiri::HTML(text)
|
||||
html.search('img').each do |img|
|
||||
next unless img['data-token']
|
||||
img_id = Base62.decode(img['data-token'])
|
||||
ids << img_id
|
||||
token = "[~tiny_mce_id:#{img_id}]"
|
||||
img.replace(token)
|
||||
next unless ref
|
||||
tiny_img = TinyMceAsset.find_by_id(img_id)
|
||||
tiny_img.reference = ref unless tiny_img.step || tiny_img.result_text
|
||||
tiny_img.save
|
||||
end
|
||||
destroy_removed_tiny_mce_assets(ids, ref) if ref
|
||||
html
|
||||
end
|
||||
|
||||
def generate_image_tag_from_token(text)
|
||||
regex = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
|
||||
text.gsub(regex) do |el|
|
||||
match = el.match(regex)
|
||||
img = TinyMceAsset.find_by_id(match[1])
|
||||
next unless img
|
||||
image_tag img.url, data: { token: Base62.encode(img.id) }
|
||||
end
|
||||
end
|
||||
|
||||
def link_tiny_mce_assets(text, ref)
|
||||
ids = []
|
||||
regex = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
|
||||
text.gsub(regex) do |img|
|
||||
match = img.match(regex)
|
||||
tiny_img = TinyMceAsset.find_by_id(match[1])
|
||||
next unless tiny_img
|
||||
ids << tiny_img.id
|
||||
tiny_img.public_send("#{ref.class.to_s.underscore}=".to_sym, ref)
|
||||
tiny_img.save!
|
||||
end
|
||||
destroy_removed_tiny_mce_assets(ids, ref)
|
||||
end
|
||||
|
||||
def destroy_removed_tiny_mce_assets(ids, ref)
|
||||
ref.tiny_mce_assets.where.not('id IN (?)', ids).destroy_all
|
||||
end
|
||||
end
|
|
@ -4,6 +4,6 @@ class ResultText < ActiveRecord::Base
|
|||
presence: true,
|
||||
length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
|
||||
validates :result, presence: true
|
||||
|
||||
belongs_to :result, inverse_of: :result_text
|
||||
has_many :tiny_mce_assets, inverse_of: :result_text, dependent: :destroy
|
||||
end
|
||||
|
|
|
@ -25,6 +25,7 @@ class Step < ActiveRecord::Base
|
|||
has_many :tables, through: :step_tables
|
||||
has_many :report_elements, inverse_of: :step,
|
||||
dependent: :destroy
|
||||
has_many :tiny_mce_assets, inverse_of: :step, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :checklists,
|
||||
reject_if: :all_blank,
|
||||
|
|
|
@ -24,7 +24,7 @@ class Team < ActiveRecord::Base
|
|||
has_many :custom_fields, inverse_of: :team
|
||||
has_many :protocols, inverse_of: :team, dependent: :destroy
|
||||
has_many :protocol_keywords, inverse_of: :team, dependent: :destroy
|
||||
|
||||
has_many :tiny_mce_assets, inverse_of: :team, dependent: :destroy
|
||||
# Based on file's extension opens file (used for importing)
|
||||
def self.open_spreadsheet(file)
|
||||
filename = file.original_filename
|
||||
|
@ -268,10 +268,13 @@ class Team < ActiveRecord::Base
|
|||
project.project_my_modules.find_each do |my_module|
|
||||
my_module.protocol.steps.find_each do |step|
|
||||
step.assets.find_each { |asset| st += asset.estimated_size }
|
||||
step.tiny_mce_assets.find_each { |tiny| st += tiny.estimated_size }
|
||||
end
|
||||
my_module.results.find_each do |result|
|
||||
if result.is_asset then
|
||||
st += result.asset.estimated_size
|
||||
st += result.asset.estimated_size if result.is_asset
|
||||
if result.is_text
|
||||
tiny_assets = TinyMceAsset.where(result_text: result.result_text)
|
||||
tiny_assets.find_each { |tiny| st += tiny.estimated_size }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
78
app/models/tiny_mce_asset.rb
Normal file
78
app/models/tiny_mce_asset.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
class TinyMceAsset < ActiveRecord::Base
|
||||
attr_accessor :reference
|
||||
before_create :set_reference
|
||||
after_create :update_estimated_size
|
||||
after_destroy :release_team_space
|
||||
|
||||
belongs_to :team, inverse_of: :tiny_mce_assets
|
||||
belongs_to :step, inverse_of: :tiny_mce_assets
|
||||
belongs_to :result_text, inverse_of: :tiny_mce_assets
|
||||
has_attached_file :image,
|
||||
styles: { large: [Constants::LARGE_PIC_FORMAT, :jpg] },
|
||||
convert_options: { large: '-quality 100 -strip' }
|
||||
|
||||
validates_attachment_content_type :image,
|
||||
content_type: %r{^image/#{ Regexp.union(
|
||||
Constants::WHITELISTED_IMAGE_TYPES
|
||||
) }}
|
||||
validates_attachment :image,
|
||||
presence: true,
|
||||
size: {
|
||||
less_than: Constants::FILE_MAX_SIZE_MB.megabytes
|
||||
}
|
||||
validates :estimated_size, presence: true
|
||||
|
||||
# When using S3 file upload, we can limit file accessibility with url signing
|
||||
def presigned_url(style = :large,
|
||||
download: false,
|
||||
timeout: Constants::URL_SHORT_EXPIRE_TIME)
|
||||
if stored_on_s3?
|
||||
if download
|
||||
download_arg = 'attachment; filename=' + URI.escape(image_file_name)
|
||||
else
|
||||
download_arg = nil
|
||||
end
|
||||
|
||||
signer = Aws::S3::Presigner.new(client: S3_BUCKET.client)
|
||||
signer.presigned_url(:get_object,
|
||||
bucket: S3_BUCKET.name,
|
||||
key: image.path(style)[1..-1],
|
||||
expires_in: timeout,
|
||||
response_content_disposition: download_arg)
|
||||
end
|
||||
end
|
||||
|
||||
def stored_on_s3?
|
||||
image.options[:storage].to_sym == :s3
|
||||
end
|
||||
|
||||
def url(style = :large, timeout: Constants::URL_SHORT_EXPIRE_TIME)
|
||||
if image.is_stored_on_s3?
|
||||
presigned_url(style, timeout: timeout)
|
||||
else
|
||||
image.url(style)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_estimated_size
|
||||
return if image_file_size.blank?
|
||||
es = image_file_size * Constants::ASSET_ESTIMATED_SIZE_FACTOR
|
||||
update(estimated_size: es)
|
||||
Rails.logger.info "Asset #{id}: Estimated size successfully calculated"
|
||||
# update team space taken
|
||||
self.team.take_space(es)
|
||||
self.team.save
|
||||
end
|
||||
|
||||
def release_team_space
|
||||
self.team.release_space(estimated_size)
|
||||
self.team.save
|
||||
end
|
||||
|
||||
def set_reference
|
||||
obj_type = "#{@reference.class.to_s.underscore}=".to_sym
|
||||
public_send(obj_type, @reference) if @reference
|
||||
end
|
||||
end
|
|
@ -23,7 +23,9 @@
|
|||
<div class="report-element-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-container ql-editor">
|
||||
<%= custom_auto_link(result_text.text, false) %>
|
||||
<%= custom_auto_link(generate_image_tag_from_token(result_text.text),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,9 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-12 ql-editor">
|
||||
<% if strip_tags(step.description).present? %>
|
||||
<%= custom_auto_link(step.description, false) %>
|
||||
<%= custom_auto_link(generate_image_tag_from_token(step.description),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
<% else %>
|
||||
<em><%=t "projects.reports.elements.step.no_description" %></em>
|
||||
<% end %>
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
<li>
|
||||
<input type="checkbox" disabled="disabled" <%= "checked='checked'" if item.checked %>/>
|
||||
<span class="<%= 'checked' if item.checked %>">
|
||||
<%= custom_auto_link(item.text, true, nil, {wrapper_tag: "span"}) %>
|
||||
<%= custom_auto_link(item.text,
|
||||
simple_format: true,
|
||||
wrapper_tag: { wrapper_tag: 'span'}) %>
|
||||
</span>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
|
||||
<%= f.fields_for :result_text do |ff| %>
|
||||
<div class="form-group">
|
||||
<%= ff.tiny_mce_editor(:text, value: @result.result_text.text) %>
|
||||
<%= ff.tiny_mce_editor(:text,
|
||||
value: @result.result_text.text,
|
||||
data: { object_type: 'result_text',
|
||||
object_id: @result.result_text.id }) %>
|
||||
</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,8 @@
|
|||
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
|
||||
<%= f.fields_for :result_text do |ff| %>
|
||||
<div class="form-group">
|
||||
<%= ff.tiny_mce_editor(:text) %>
|
||||
<%= ff.tiny_mce_editor(:text, data: { object_type: 'result_text',
|
||||
object_id: @result.result_text.id }) %>
|
||||
</div>
|
||||
<% end %><br />
|
||||
<%= f.submit t("result_texts.new.create"), class: 'btn btn-primary save-result', onclick: "processResult(event, ResultTypeEnum.TEXT, false);" %>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<div class="ql-editor">
|
||||
<%= custom_auto_link(result.result_text.text, false) %>
|
||||
<%= custom_auto_link(generate_image_tag_from_token(result.result_text.text),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
</div>
|
||||
|
|
|
@ -27,7 +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">
|
||||
<%= f.tiny_mce_editor(:description) %>
|
||||
<%= f.tiny_mce_editor(:description, data: { object_type: 'step', object_id: @step.id }) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" role="tabpanel" id="new-step-checklists">
|
||||
|
|
|
@ -54,7 +54,9 @@
|
|||
<em><%= t('protocols.steps.no_description') %></em>
|
||||
<% else %>
|
||||
<div class="ql-editor">
|
||||
<%= custom_auto_link(step.description, false) %>
|
||||
<%= custom_auto_link(generate_image_tag_from_token(step.description),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1572,7 +1572,12 @@ en:
|
|||
expired_description: 'The downloadable file expires in 3 days after its creation.'
|
||||
# This section contains general words that can be used in any parts of
|
||||
# application.
|
||||
|
||||
tiny_mce:
|
||||
upload_window_title: 'Insert an image from your computer'
|
||||
upload_window_label: 'Choose an image'
|
||||
insert_btn: 'Insert'
|
||||
error_message: 'You must choose a file'
|
||||
server_not_respond: "Didn't get a response from the server"
|
||||
general:
|
||||
save: "Save"
|
||||
update: "Update"
|
||||
|
|
|
@ -322,6 +322,9 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
# tinyMCE image uploader endpoint
|
||||
post '/tinymce_assets', to: 'tiny_mce_assets#create', as: :tiny_mce_assets
|
||||
|
||||
resources :results, only: [:update, :destroy] do
|
||||
resources :result_comments,
|
||||
path: '/comments',
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
class AddAttachmentImageToTinyMceAssets < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :tiny_mce_assets do |t|
|
||||
t.attachment :image
|
||||
t.integer :estimated_size, default: 0, null: false
|
||||
t.references :step, index: true
|
||||
t.references :team, index: true
|
||||
t.references :result_text, index: true
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
57
db/schema.rb
57
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20170321131116) do
|
||||
ActiveRecord::Schema.define(version: 20170420075905) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -60,12 +60,13 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
t.integer "lock_ttl"
|
||||
t.integer "version", default: 1
|
||||
t.boolean "file_processing"
|
||||
t.integer "team_id"
|
||||
end
|
||||
|
||||
add_index "assets", ["created_at"], name: "index_assets_on_created_at", using: :btree
|
||||
add_index "assets", ["created_by_id"], name: "index_assets_on_created_by_id", using: :btree
|
||||
add_index "assets", ["file_file_name"], name: "index_assets_on_file_file_name", using: :gist
|
||||
add_index "assets", ["last_modified_by_id"], name: "index_assets_on_last_modified_by_id", using: :btree
|
||||
add_index "assets", ["team_id"], name: "index_assets_on_team_id", using: :btree
|
||||
|
||||
create_table "checklist_items", force: :cascade do |t|
|
||||
t.string "text", null: false
|
||||
|
@ -75,7 +76,7 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "position", default: 0, null: false
|
||||
t.integer "position"
|
||||
end
|
||||
|
||||
add_index "checklist_items", ["checklist_id"], name: "index_checklist_items_on_checklist_id", using: :btree
|
||||
|
@ -93,6 +94,7 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
|
||||
add_index "checklists", ["created_by_id"], name: "index_checklists_on_created_by_id", using: :btree
|
||||
add_index "checklists", ["last_modified_by_id"], name: "index_checklists_on_last_modified_by_id", using: :btree
|
||||
add_index "checklists", ["step_id"], name: "index_checklists_on_step_id", using: :btree
|
||||
|
||||
create_table "comments", force: :cascade do |t|
|
||||
t.string "message", null: false
|
||||
|
@ -107,6 +109,7 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_index "comments", ["associated_id"], name: "index_comments_on_associated_id", using: :btree
|
||||
add_index "comments", ["created_at"], name: "index_comments_on_created_at", using: :btree
|
||||
add_index "comments", ["last_modified_by_id"], name: "index_comments_on_last_modified_by_id", using: :btree
|
||||
add_index "comments", ["type"], name: "index_comments_on_type", using: :btree
|
||||
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
|
||||
|
||||
create_table "connections", force: :cascade do |t|
|
||||
|
@ -170,11 +173,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_index "experiments", ["project_id"], name: "index_experiments_on_project_id", using: :btree
|
||||
add_index "experiments", ["restored_by_id"], name: "index_experiments_on_restored_by_id", using: :btree
|
||||
|
||||
create_table "logs", force: :cascade do |t|
|
||||
t.integer "team_id", null: false
|
||||
t.string "message", null: false
|
||||
end
|
||||
|
||||
create_table "my_module_groups", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
|
@ -185,7 +183,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
|
||||
add_index "my_module_groups", ["created_by_id"], name: "index_my_module_groups_on_created_by_id", using: :btree
|
||||
add_index "my_module_groups", ["experiment_id"], name: "index_my_module_groups_on_experiment_id", using: :btree
|
||||
add_index "my_module_groups", ["name"], name: "index_my_module_groups_on_name", using: :gist
|
||||
|
||||
create_table "my_module_tags", force: :cascade do |t|
|
||||
t.integer "my_module_id"
|
||||
|
@ -225,7 +222,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_index "my_modules", ["experiment_id"], name: "index_my_modules_on_experiment_id", using: :btree
|
||||
add_index "my_modules", ["last_modified_by_id"], name: "index_my_modules_on_last_modified_by_id", using: :btree
|
||||
add_index "my_modules", ["my_module_group_id"], name: "index_my_modules_on_my_module_group_id", using: :btree
|
||||
add_index "my_modules", ["name"], name: "index_my_modules_on_name", using: :gist
|
||||
add_index "my_modules", ["restored_by_id"], name: "index_my_modules_on_restored_by_id", using: :btree
|
||||
|
||||
create_table "notifications", force: :cascade do |t|
|
||||
|
@ -258,7 +254,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_index "projects", ["archived_by_id"], name: "index_projects_on_archived_by_id", using: :btree
|
||||
add_index "projects", ["created_by_id"], name: "index_projects_on_created_by_id", using: :btree
|
||||
add_index "projects", ["last_modified_by_id"], name: "index_projects_on_last_modified_by_id", using: :btree
|
||||
add_index "projects", ["name"], name: "index_projects_on_name", using: :gist
|
||||
add_index "projects", ["restored_by_id"], name: "index_projects_on_restored_by_id", using: :btree
|
||||
add_index "projects", ["team_id"], name: "index_projects_on_team_id", using: :btree
|
||||
|
||||
|
@ -270,7 +265,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
t.integer "team_id", null: false
|
||||
end
|
||||
|
||||
add_index "protocol_keywords", ["name"], name: "index_protocol_keywords_on_name", using: :btree
|
||||
add_index "protocol_keywords", ["team_id"], name: "index_protocol_keywords_on_team_id", using: :btree
|
||||
|
||||
create_table "protocol_protocol_keywords", force: :cascade do |t|
|
||||
|
@ -303,11 +297,9 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
|
||||
add_index "protocols", ["added_by_id"], name: "index_protocols_on_added_by_id", using: :btree
|
||||
add_index "protocols", ["archived_by_id"], name: "index_protocols_on_archived_by_id", using: :btree
|
||||
add_index "protocols", ["authors"], name: "index_protocols_on_authors", using: :btree
|
||||
add_index "protocols", ["description"], name: "index_protocols_on_description", using: :btree
|
||||
add_index "protocols", ["my_module_id"], name: "index_protocols_on_my_module_id", using: :btree
|
||||
add_index "protocols", ["name"], name: "index_protocols_on_name", using: :btree
|
||||
add_index "protocols", ["parent_id"], name: "index_protocols_on_parent_id", using: :btree
|
||||
add_index "protocols", ["protocol_type"], name: "index_protocols_on_protocol_type", using: :btree
|
||||
add_index "protocols", ["restored_by_id"], name: "index_protocols_on_restored_by_id", using: :btree
|
||||
add_index "protocols", ["team_id"], name: "index_protocols_on_team_id", using: :btree
|
||||
|
||||
|
@ -393,7 +385,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_index "results", ["created_at"], name: "index_results_on_created_at", using: :btree
|
||||
add_index "results", ["last_modified_by_id"], name: "index_results_on_last_modified_by_id", using: :btree
|
||||
add_index "results", ["my_module_id"], name: "index_results_on_my_module_id", using: :btree
|
||||
add_index "results", ["name"], name: "index_results_on_name", using: :gist
|
||||
add_index "results", ["restored_by_id"], name: "index_results_on_restored_by_id", using: :btree
|
||||
add_index "results", ["user_id"], name: "index_results_on_user_id", using: :btree
|
||||
|
||||
|
@ -458,7 +449,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
end
|
||||
|
||||
add_index "samples", ["last_modified_by_id"], name: "index_samples_on_last_modified_by_id", using: :btree
|
||||
add_index "samples", ["name"], name: "index_samples_on_name", using: :gist
|
||||
add_index "samples", ["sample_group_id"], name: "index_samples_on_sample_group_id", using: :btree
|
||||
add_index "samples", ["sample_type_id"], name: "index_samples_on_sample_type_id", using: :btree
|
||||
add_index "samples", ["team_id"], name: "index_samples_on_team_id", using: :btree
|
||||
|
@ -511,7 +501,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
|
||||
add_index "steps", ["created_at"], name: "index_steps_on_created_at", using: :btree
|
||||
add_index "steps", ["last_modified_by_id"], name: "index_steps_on_last_modified_by_id", using: :btree
|
||||
add_index "steps", ["name"], name: "index_steps_on_name", using: :gist
|
||||
add_index "steps", ["position"], name: "index_steps_on_position", using: :btree
|
||||
add_index "steps", ["protocol_id"], name: "index_steps_on_protocol_id", using: :btree
|
||||
add_index "steps", ["user_id"], name: "index_steps_on_user_id", using: :btree
|
||||
|
@ -524,12 +513,14 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
t.integer "last_modified_by_id"
|
||||
t.tsvector "data_vector"
|
||||
t.string "name", default: ""
|
||||
t.integer "team_id"
|
||||
end
|
||||
|
||||
add_index "tables", ["created_at"], name: "index_tables_on_created_at", using: :btree
|
||||
add_index "tables", ["created_by_id"], name: "index_tables_on_created_by_id", using: :btree
|
||||
add_index "tables", ["data_vector"], name: "index_tables_on_data_vector", using: :gin
|
||||
add_index "tables", ["last_modified_by_id"], name: "index_tables_on_last_modified_by_id", using: :btree
|
||||
add_index "tables", ["team_id"], name: "index_tables_on_team_id", using: :btree
|
||||
|
||||
create_table "tags", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
|
@ -543,7 +534,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
|
||||
add_index "tags", ["created_by_id"], name: "index_tags_on_created_by_id", using: :btree
|
||||
add_index "tags", ["last_modified_by_id"], name: "index_tags_on_last_modified_by_id", using: :btree
|
||||
add_index "tags", ["name"], name: "index_tags_on_name", using: :gist
|
||||
add_index "tags", ["project_id"], name: "index_tags_on_project_id", using: :btree
|
||||
|
||||
create_table "teams", force: :cascade do |t|
|
||||
|
@ -570,6 +560,23 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
t.datetime "file_updated_at"
|
||||
end
|
||||
|
||||
create_table "tiny_mce_assets", force: :cascade do |t|
|
||||
t.string "image_file_name"
|
||||
t.string "image_content_type"
|
||||
t.integer "image_file_size"
|
||||
t.datetime "image_updated_at"
|
||||
t.integer "estimated_size", default: 0, null: false
|
||||
t.integer "step_id"
|
||||
t.integer "team_id"
|
||||
t.integer "result_text_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "tiny_mce_assets", ["result_text_id"], name: "index_tiny_mce_assets_on_result_text_id", using: :btree
|
||||
add_index "tiny_mce_assets", ["step_id"], name: "index_tiny_mce_assets_on_step_id", using: :btree
|
||||
add_index "tiny_mce_assets", ["team_id"], name: "index_tiny_mce_assets_on_team_id", using: :btree
|
||||
|
||||
create_table "tokens", force: :cascade do |t|
|
||||
t.string "token", null: false
|
||||
t.integer "ttl", null: false
|
||||
|
@ -601,11 +608,11 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_index "user_notifications", ["user_id"], name: "index_user_notifications_on_user_id", using: :btree
|
||||
|
||||
create_table "user_projects", force: :cascade do |t|
|
||||
t.integer "role", default: 0
|
||||
t.integer "user_id", null: false
|
||||
t.integer "project_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "role"
|
||||
t.integer "user_id", null: false
|
||||
t.integer "project_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "assigned_by_id"
|
||||
end
|
||||
|
||||
|
@ -671,7 +678,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
|
||||
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
|
||||
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
|
||||
add_index "users", ["full_name"], name: "index_users_on_full_name", using: :btree
|
||||
add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", unique: true, using: :btree
|
||||
add_index "users", ["invitations_count"], name: "index_users_on_invitations_count", using: :btree
|
||||
add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id", using: :btree
|
||||
|
@ -735,7 +741,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
|
|||
add_foreign_key "experiments", "users", column: "created_by_id"
|
||||
add_foreign_key "experiments", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "experiments", "users", column: "restored_by_id"
|
||||
add_foreign_key "logs", "teams"
|
||||
add_foreign_key "my_module_groups", "experiments"
|
||||
add_foreign_key "my_module_groups", "users", column: "created_by_id"
|
||||
add_foreign_key "my_module_tags", "users", column: "created_by_id"
|
||||
|
|
9
lib/tasks/tiny_mce_asset.rake
Normal file
9
lib/tasks/tiny_mce_asset.rake
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace :tiny_mce_asset do
|
||||
desc 'Remove obsolete images that were created on new steps or '\
|
||||
'results and the step/result didn\'t get saved.'
|
||||
task remove_obsolete_images: :environment do
|
||||
TinyMceAsset.where('step_id IS ? AND ' \
|
||||
'result_text_id IS ? AND created_at < ?',
|
||||
nil, nil, 7.days.ago)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue