Add text component [SCI-6808]

This commit is contained in:
Anton 2022-05-10 13:28:09 +02:00
parent d07daa73cb
commit 04e16cf6b5
13 changed files with 260 additions and 29 deletions

View file

@ -278,7 +278,7 @@ var TinyMCE = (function() {
editorForm.find('.tinymce-view').html(data.html).removeClass('hidden');
editor.plugins.autosave.removeDraft();
removeDraft(editor, textAreaObject);
if (onSaveCallback) { onSaveCallback(); }
if (onSaveCallback) { onSaveCallback(data); }
}).on('ajax:error', function(ev, data) {
var model = editor.getElement().dataset.objectType;
$(this).renderFormErrors(model, data.responseJSON);
@ -302,6 +302,7 @@ var TinyMCE = (function() {
editorForm.find('.tinymce-view').removeClass('hidden');
editor.remove();
updateScrollPosition(editorForm);
if (onSaveCallback) { onSaveCallback(); }
})
.removeClass('hidden');

View file

@ -0,0 +1,72 @@
.step-text-container {
display: block;
margin-bottom: 16px;
margin-left: -16px;
padding-left: 36px;
position: relative;
width: calc(100% + 16px);
.action-container {
cursor: pointer;
height: 100%;
left: 0;
position: absolute;
width: 100%;
z-index: 100;
.buttons-container {
background: linear-gradient(90deg, transparent 0%, $color-concrete 25%, $color-concrete 100%);
display: none;
padding-left: 2em;
position: absolute;
right: 0;
}
}
.element-grip {
align-items: center;
color: $color-alto;
display: none;
height: 100%;
justify-content: center;
left: 0;
position: absolute;
width: 36px;
}
.tinymce-container {
display: flex;
width: 100%;
.tinymce-status-badge {
display: none;
};
.tiny-mce-editor {
flex-basis: 100%;
}
.tinymce-view {
border-color: transparent;
}
}
&:hover:not(.edit) {
background: $color-concrete;
.buttons-container {
display: flex;
}
.element-grip {
display: flex;
}
}
&.edit {
.action-container {
display: none;
}
}
}

View file

@ -8,7 +8,7 @@ module StepComponents
@step.step_texts.create!
end
def element_params
def orderable_params
params.require(:step_text).permit(:text)
end

View file

@ -11,7 +11,7 @@ class StepOrderableElementsController < ApplicationController
position: @step.step_orderable_elements.length,
orderable: create_step_element
)
render json: element, serializer: StepOrderableElementSerializer
render json: element, serializer: StepOrderableElementSerializer, user: current_user
rescue ActiveRecord::RecordInvalid
render json: {}, status: :unprocessable_entity
end
@ -19,14 +19,14 @@ class StepOrderableElementsController < ApplicationController
def update
@element.update!(orderable_params)
render json: @element, serializer: "#{@element.class}Serializer".constantize
render json: @element, serializer: "#{@element.class}Serializer".constantize, user: current_user
rescue ActiveRecord::RecordInvalid
render json: {}, status: :unprocessable_entity
end
def destroy
if @element.destroy
render json: @orderable_element, serializer: StepOrderableElementSerializer
render json: @orderable_element, serializer: StepOrderableElementSerializer, user: current_user
else
render json: {}, status: :unprocessable_entity
end

View file

@ -20,7 +20,7 @@ class StepsController < ApplicationController
end
def elements
render json: @step.step_orderable_elements.order(:position), each_serializer: StepOrderableElementSerializer
render json: @step.step_orderable_elements.order(:position), each_serializer: StepOrderableElementSerializer, user: current_user
end
def new

View file

@ -5,7 +5,8 @@ export default {
};
},
methods: {
showDeleteModal() {
showDeleteModal(event) {
event.stopPropagation();
this.confirmingDelete = true;
},
closeDeleteModal() {

View file

@ -139,19 +139,23 @@
this.reorderComponents(unordered_elements)
},
updateComponent(element) {
updateComponent(element, skipRequest=false) {
let index = this.elements.findIndex((e) => e.id === element.id);
$.ajax({
url: element.attributes.orderable.urls.update_url,
method: 'PUT',
data: element.attributes.orderable,
success: (result) => {
this.elements[index].orderable = result;
}
}).error(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
})
if (skipRequest) {
this.elements[index].orderable = element;
} else {
$.ajax({
url: element.attributes.orderable.urls.update_url,
method: 'PUT',
data: element.attributes.orderable,
success: (result) => {
this.elements[index].orderable = result;
}
}).error(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
})
}
},
reorderComponents(elements) {
this.elements = elements.sort((a, b) => a.attributes.position - b.attributes.position);

View file

@ -1,26 +1,68 @@
<template>
<div class="step-text-container">
Text
<button class="btn icon-btn btn-light" @click="showDeleteModal">
<i class="fas fa-trash"></i>
</button>
<deleteComponentModal v-if="confirmingDelete" @confirm="deleteComponent" @cancel="closeDeleteModal"/>
<div :class="`step-text-container ${inEditMode ? 'edit' : ''}`">
<div class="action-container" @click="enableEditMode">
<div class="element-grip">
<i class="fas fa-grip-vertical"></i>
</div>
<div class="buttons-container">
<button class="btn icon-btn btn-light">
<i class="fas fa-pen"></i>
</button>
<button class="btn icon-btn btn-light" @click="showDeleteModal">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<Tinymce
:inEditMode="inEditMode"
:value="element.attributes.orderable.text"
:value_html="element.attributes.orderable.text_view"
:placeholder="'Enter step text'"
:updateUrl="element.attributes.orderable.urls.update_url"
:objectType="'StepText'"
:objectId="element.attributes.orderable.id"
:fieldName="'step_text[text]'"
:lastUpdated="element.attributes.orderable.updated_at"
@update="update"
@editingDisabled="disableEditMode"
/>
<deleteComponentModal v-if="confirmingDelete" @confirm="deleteComponent(event)" @cancel="closeDeleteModal"/>
</div>
</template>
<script>
import DeleteMixin from 'vue/protocol/mixins/components/delete.js'
import deleteComponentModal from 'vue/protocol/modals/delete_component.vue'
import Tinymce from 'vue/shared/tinymce.vue'
export default {
name: 'StepText',
components: { deleteComponentModal },
components: { deleteComponentModal, Tinymce },
mixins: [DeleteMixin],
props: {
element: {
type: Object,
required: true
}
},
data() {
return {
inEditMode: false,
}
},
methods: {
enableEditMode() {
this.inEditMode = true
},
disableEditMode() {
this.inEditMode = false
},
update(data) {
this.element.attributes.orderable.text_view = data.data.attributes.text_view
this.element.attributes.orderable.text = data.data.attributes.text
this.element.attributes.orderable.udpated_at = data.data.attributes.udpated_at
this.$emit('update', this.element, true)
}
}
}
</script>

View file

@ -0,0 +1,89 @@
<template>
<div :class="`tinymce-container ${inEditMode ? 'edit' : ''}`">
<form class="tiny-mce-editor" role="form" :action="updateUrl" accept-charset="UTF-8" data-remote="true" method="post">
<input type="hidden" name="_method" value="patch">
<div class="hidden tinymce-cancel-button mce-widget mce-btn mce-menubtn mce-flow-layout-item mce-btn-has-text pull-right" tabindex="-1">
<button type="button" tabindex="-1">
<span class="fas fa-times"></span>
<span class="mce-txt">{{ i18n.t('general.cancel') }}</span>
</button>
</div>
<div class="hidden tinymce-save-button mce-widget mce-btn mce-menubtn mce-flow-layout-item mce-btn-has-text mce-last pull-right" tabindex="-1">
<button type="button" tabindex="-1">
<span class="fas fa-check"></span>
<span class="mce-txt">{{ i18n.t('general.save') }}</span>
</button>
</div>
<div class="hidden tinymce-status-badge pull-right">
<i class="fas fa-check-circle"></i>
<span>{{ i18n.t('tiny_mce.saved_label') }}</span>
</div>
<div :id="`${objectType}_view_${objectId}`"
@click="initTinymce"
v-html="value_html"
class="ql-editor tinymce-view"
:data-placeholder="placeholder"
:data-tinymce-init="`tinymce-${objectType}-description-${objectId}`">
</div>
<div class="form-group">
<textarea :id="`${objectType}_textarea_${objectId}`"
class="form-control hidden"
:placeholder="placeholder"
autocomplete="off"
:data-tinymce-object="`tinymce-${objectType}-description-${objectId}`"
:data-object-type="objectType"
:data-object-id="objectId"
:data-highlightjs-path="this.getStaticUrl('highlightjs-url')"
:data-last-updated="lastUpdated * 1000"
:data-tinymce-asset-path="this.getStaticUrl('tiny-mce-assets-url')"
:value="value"
cols="120"
rows="10"
:name="fieldName"
aria-hidden="true">
</textarea>
<input type="hidden" id="tiny-mce-images" name="tiny_mce_images" value="[]">
</div>
</form>
</div>
</template>
<script>
export default {
name: 'Tinymce',
props: {
value: String,
value_html: String,
placeholder: String,
updateUrl: String,
objectType: String,
objectId: Number,
fieldName: String,
lastUpdated: Number,
inEditMode: Boolean
},
watch: {
inEditMode() {
if (this.inEditMode) {
this.initTinymce()
}
}
},
methods: {
initTinymce() {
let textArea = `#${this.objectType}_textarea_${this.objectId}`;
TinyMCE.init(textArea, (data) => {
if (data) {
this.$emit('update', data)
}
this.$emit('editingDisabled')
});
this.$emit('editingEnabled')
},
getStaticUrl(name) {
return $(`meta[name=\'${name}\']`).attr('content');
}
}
}
</script>

View file

@ -10,7 +10,7 @@ class StepOrderableElementSerializer < ActiveModel::Serializer
when 'StepTable'
TableSerializer.new(object.orderable.table).as_json
when 'StepText'
StepTextSerializer.new(object.orderable).as_json
StepTextSerializer.new(object.orderable, scope: { user: @instance_options[:user] }).as_json
end
end
end

View file

@ -2,14 +2,33 @@
class StepTextSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
include ApplicationHelper
include ActionView::Helpers::TextHelper
attributes :text, :urls
attributes :id, :text, :urls, :text_view, :updated_at
def updated_at
object.updated_at.to_i
end
def text_view
@user = scope[:user]
custom_auto_link(object.tinymce_render('text'),
simple_format: false,
tags: %w(img),
team: object.step.protocol.team)
end
def text
sanitize_input(object.tinymce_render('text'))
end
def urls
return if object.destroyed?
{
delete_url: step_text_path(object.step, object)
delete_url: step_text_path(object.step, object),
update_url: step_text_path(object.step, object)
}
end
end

View file

@ -5,11 +5,14 @@
<title><%=t "head.title", title: (yield :head_title) %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="max-file-size" content="<%= Rails.configuration.x.file_max_size_mb %>">
<meta name="tiny-mce-assets-url" content="<%= tiny_mce_assets_path %>">
<meta name="highlightjs-url" content="<%= asset_path('highlightjs-github-theme.css') %>">
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= javascript_pack_tag 'application' %>
<% if MarvinJsService.enabled? && ENV['MARVINJS_API_KEY'] %>
<script src="https://marvinjs.chemicalize.com/v1/<%= ENV['MARVINJS_API_KEY'] %>/client-settings.js"></script>
<script src="https://marvinjs.chemicalize.com/v1/client.js"></script>

View file

@ -450,7 +450,7 @@ Rails.application.routes.draw do
only: %i(create index update destroy)
resources :tables, controller: 'step_components/tables', only: %i(create destroy update)
resources :texts, controller: 'step_components/texts', only: %i(create destroy)
resources :texts, controller: 'step_components/texts', only: %i(create destroy update)
resources :checklists, controller: 'step_components/checklists', only: %i(create destroy update)
member do
get 'elements'