working version for steps first run

This commit is contained in:
zmagod 2017-04-21 16:09:04 +02:00
parent 0757563d02
commit 550f563666
18 changed files with 381 additions and 72 deletions

View file

@ -66,7 +66,6 @@ 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-imageupload', '~> 4.0.0.beta'
gem 'base62' # Used for smart annotations
gem 'newrelic_rpm'

View file

@ -311,9 +311,6 @@ GEM
tilt (2.0.1)
tinymce-rails (4.5.2)
railties (>= 3.1.1)
tinymce-rails-imageupload (4.0.17.beta.2)
railties (>= 3.2, < 6)
tinymce-rails (~> 4.0)
turbolinks (2.5.3)
coffee-rails
tzinfo (1.2.2)
@ -401,7 +398,6 @@ DEPENDENCIES
spinjs-rails
starscope
tinymce-rails
tinymce-rails-imageupload (~> 4.0.0.beta)
turbolinks
tzinfo-data
uglifier (>= 1.3.0)

View file

@ -23,8 +23,8 @@ var TinyMCE = (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 | uploadimage | codesample"],
plugins: "uploadimage, link,advlist,codesample,autolink,lists,charmap,hr,anchor,searchreplace,wordcount,visualblocks,visualchars,insertdatetime,nonbreaking,save,contextmenu,directionality,paste,textcolor,colorpicker,textpattern,imagetools",
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,imagetools",
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',
elementpath: false,
@ -84,8 +84,7 @@ var TinyMCE = (function() {
});
},
codesample_content_css: '<%= asset_path('highlightjs-github-theme') %>',
uploadimage_form_url: getTinyMceAssetsUploadPath()
codesample_content_css: '<%= asset_path('highlightjs-github-theme') %>'
});
}
},

View file

@ -0,0 +1,235 @@
(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;'>&nbsp;</p>"},
{type: 'container', classes: 'throbber'},
],
buttons: [
{text: "<%= I18n.t 'tiny_mce.insert_btn' %>",
onclick: insertImage,
subtype: 'primary'},
{text: "<%= I18n.t 'general.cancel' %>", onclick: ed.windowManager.close}
],
}, {
plugin_url: url
});
win.off('submit');
win.on('submit', insertImage);
iframe = win.find('iframe')[0];
form = createElement('form', {
action: ed.getParam('customimageuploader_form_url',
'/tinymce_assets'),
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: $(ed.getElement())
.data('object-type')}));
form.appendChild(createElement('input',
{type: 'hidden',
name: 'object_id',
value: $(ed.getElement())
.data('object-id')}));
form.appendChild(createElement('input',
{type: 'hidden',
name: 'hint',
value: ed.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("Didn't get a response from the server");
}
}
function handleResponse(ret) {
try {
var json = tinymce.util.JSON.parse(ret);
if(json['error']) {
handleError(json['error']['message']);
} else {
ed.execCommand('mceInsertContent', false, buildHTML(json));
ed.windowManager.close();
}
} catch(e) {
handleError('Got a bad response from the server');
}
}
function clearErrors() {
var message = win.find('.error')[0].getEl();
if(message)
message.getElementsByTagName('p')[0].innerHTML = '&nbsp;';
}
function handleError(error) {
var message = win.find('.error')[0].getEl();
if(message)
message.getElementsByTagName('p')[0].innerHTML = ed.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: ed.translate('Insert an image from your computer'),
icon: 'image',
onclick: showDialog
});
// Adds a menu item to the tools menu
editor.addMenuItem('customimageuploader', {
text: ed.translate('Insert an image from your computer'),
icon: 'image',
context: 'insert',
onclick: showDialog
});
}
});
tinymce.PluginManager.add('customimageuploader',
tinymce.plugins.CustomImageUploader);
})();

View file

@ -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
@ -52,6 +54,7 @@ class StepsController < ApplicationController
respond_to do |format|
if @step.save
# Post process all assets
@step.assets.each do |asset|
asset.post_process_file(@protocol.team)
@ -119,6 +122,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: {
@ -158,6 +162,10 @@ class StepsController < ApplicationController
table.team = current_team
end
# gerate a tag that replaces img tag in database
@step.description = parse_tiny_mce_asset_to_token(@step.description,
@step)
if @step.save
@step.reload

View file

@ -1,17 +1,27 @@
class TinyMceAssetsController < ApplicationController
before_action :find_object
def create
step = Step.find_by_id(params[:step])
image = Asset.create(file: params[:file],
created_by: current_user,
team: current_team)
image.file.reprocess_without_delay!(:original)
byebug
render json: {
image: {
url: view_context.image_url(image.url(:original))
}
}, content_type: 'text/html'
image = params.fetch(:file) { render_404 }
tiny_img = TinyMceAsset.new(image: image, reference: @obj, editing: true)
if tiny_img.save
render json: {
image: {
url: view_context.image_url(tiny_img.url(:original)),
token: Base62.encode(tiny_img.id)
}
}, content_type: 'text/html'
else
render_404
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 ['step', 'result_text'].include? obj_type
@obj = obj_type.classify.constantize.find_by_id(obj_id)
end
end

View file

@ -0,0 +1,27 @@
require 'nokogiri'
module TinyMceHelper
def parse_tiny_mce_asset_to_token(text, ref = nil)
html = Nokogiri::HTML(text)
html.search('img').each do |img|
next unless img['data-token']
img_id = Base62.decode(img['data-token'])
token = "[~tiny_mce_id:#{img_id}]"
img.replace(token)
byebug
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
html
end
def generate_image_tag_from_token(text, ref = nil)
regex = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
new_text = text.gsub(regex) do |el|
match = el.match(regex)
img = TinyMceAsset.find_by_id(match[1])
image_tag img.url, data: { token: Base62.encode(img.id) }
end
end
end

View file

@ -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
end

View file

@ -1,4 +0,0 @@
class StepTinyMceAsset < ActiveRecord::Base
belongs_to :step
belongs_to :asset
end

View file

@ -0,0 +1,51 @@
class TinyMceAsset < ActiveRecord::Base
attr_accessor :reference
before_create :set_reference
belongs_to :step
belongs_to :result_text
has_attached_file :image,
styles: { medium: [Constants::MEDIUM_PIC_FORMAT, :jpg] },
convert_options: { medium: '-quality 70 -strip' }
validates_attachment_content_type :image,
content_type: %r{^image/#{ Regexp.union(
Constants::WHITELISTED_IMAGE_TYPES
) }}
# When using S3 file upload, we can limit file accessibility with url signing
def presigned_url(style = :medium,
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 = :medium, timeout: Constants::URL_SHORT_EXPIRE_TIME)
if image.is_stored_on_s3?
presigned_url(style, timeout: timeout)
else
image.url(style)
end
end
def set_reference
obj_type = "#{@reference.class.to_s.underscore}=".to_sym
self.public_send(obj_type, @reference) if @reference
end
end

View file

@ -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, tiny_mce_upload_path: tiny_mce_assets_path(step: @step) ) %>
<%= 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">

View file

@ -54,7 +54,7 @@
<em><%= t('protocols.steps.no_description') %></em>
<% else %>
<div class="ql-editor">
<%= custom_auto_link(step.description,
<%= custom_auto_link(generate_image_tag_from_token(step.description),
simple_format: false,
tags: ['img']) %>
</div>

View file

@ -1567,7 +1567,11 @@ 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'
general:
save: "Save"
update: "Update"

View file

@ -1,11 +0,0 @@
class CreateStepTinyMceAssets < ActiveRecord::Migration
def change
create_table :step_tiny_mce_assets do |t|
t.references :step, index: true, foreign_key: true
t.references :asset, index: true, foreign_key: true
t.boolean :edited, default: false
t.timestamps null: false
end
end
end

View file

@ -0,0 +1,11 @@
class AddAttachmentImageToTinyMceAssets < ActiveRecord::Migration
def change
create_table :tiny_mce_assets do |t|
t.attachment :image
t.boolean :editing, default: false
t.references :step, index: true
t.references :result_text, index: true
t.timestamps null: false
end
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170419143608) do
ActiveRecord::Schema.define(version: 20170420075905) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -486,17 +486,6 @@ ActiveRecord::Schema.define(version: 20170419143608) do
add_index "step_tables", ["step_id", "table_id"], name: "index_step_tables_on_step_id_and_table_id", unique: true, using: :btree
create_table "step_tiny_mce_assets", force: :cascade do |t|
t.integer "step_id"
t.integer "asset_id"
t.boolean "edited", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "step_tiny_mce_assets", ["asset_id"], name: "index_step_tiny_mce_assets_on_asset_id", using: :btree
add_index "step_tiny_mce_assets", ["step_id"], name: "index_step_tiny_mce_assets_on_step_id", using: :btree
create_table "steps", force: :cascade do |t|
t.string "name"
t.string "description"
@ -571,6 +560,21 @@ ActiveRecord::Schema.define(version: 20170419143608) 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.boolean "editing", default: false
t.integer "step_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
create_table "tokens", force: :cascade do |t|
t.string "token", null: false
t.integer "ttl", null: false
@ -801,8 +805,6 @@ ActiveRecord::Schema.define(version: 20170419143608) do
add_foreign_key "step_assets", "steps"
add_foreign_key "step_tables", "steps"
add_foreign_key "step_tables", "tables"
add_foreign_key "step_tiny_mce_assets", "assets"
add_foreign_key "step_tiny_mce_assets", "steps"
add_foreign_key "steps", "protocols"
add_foreign_key "steps", "users"
add_foreign_key "steps", "users", column: "last_modified_by_id"

View file

@ -1,11 +0,0 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
step_id:
asset_id:
edited: false
two:
step_id:
asset_id:
edited: false

View file

@ -1,7 +0,0 @@
require 'test_helper'
class StepTinyMceAssetTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end