mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-13 00:24:42 +08:00
Add text component [SCI-6808]
This commit is contained in:
parent
d07daa73cb
commit
04e16cf6b5
13 changed files with 260 additions and 29 deletions
3
app/assets/javascripts/sitewide/tiny_mce.js
vendored
3
app/assets/javascripts/sitewide/tiny_mce.js
vendored
|
@ -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');
|
||||
|
||||
|
|
72
app/assets/stylesheets/steps/components/text.scss
Normal file
72
app/assets/stylesheets/steps/components/text.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ module StepComponents
|
|||
@step.step_texts.create!
|
||||
end
|
||||
|
||||
def element_params
|
||||
def orderable_params
|
||||
params.require(:step_text).permit(:text)
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,7 +5,8 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
showDeleteModal() {
|
||||
showDeleteModal(event) {
|
||||
event.stopPropagation();
|
||||
this.confirmingDelete = true;
|
||||
},
|
||||
closeDeleteModal() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
89
app/javascript/vue/shared/tinymce.vue
Normal file
89
app/javascript/vue/shared/tinymce.vue
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Add table
Reference in a new issue