mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-02-27 01:05:21 +08:00
Fix text field error states [SCI-6952] (#4216)
This commit is contained in:
parent
fa623a9f19
commit
a51ce1c322
11 changed files with 189 additions and 73 deletions
7
app/assets/javascripts/sitewide/tiny_mce.js
vendored
7
app/assets/javascripts/sitewide/tiny_mce.js
vendored
|
@ -123,6 +123,7 @@ var TinyMCE = (function() {
|
|||
// returns a public API for TinyMCE editor
|
||||
return Object.freeze({
|
||||
init: function(selector, onSaveCallback) {
|
||||
var editorInstancePromise;
|
||||
var tinyMceContainer;
|
||||
var tinyMceInitSize;
|
||||
var plugins;
|
||||
|
@ -142,7 +143,7 @@ var TinyMCE = (function() {
|
|||
document.location.hash = textAreaObject.data('objectType') + '_' + textAreaObject.data('objectId');
|
||||
}
|
||||
|
||||
tinyMCE.init({
|
||||
editorInstancePromise = tinyMCE.init({
|
||||
cache_suffix: '?v=4.9.10', // This suffix should be changed any time library is updated
|
||||
selector: selector,
|
||||
convert_urls: false,
|
||||
|
@ -354,6 +355,8 @@ var TinyMCE = (function() {
|
|||
});
|
||||
|
||||
editor.on('blur', function(e) {
|
||||
if (editor.blurDisabled) return false;
|
||||
|
||||
if ($('.atwho-view:visible').length || $('#MarvinJsModal:visible').length) return false;
|
||||
setTimeout(() => {
|
||||
if (editor.isNotDirty === false) {
|
||||
|
@ -373,6 +376,8 @@ var TinyMCE = (function() {
|
|||
save_onsavecallback: function(editor) { saveAction(editor); }
|
||||
});
|
||||
}
|
||||
|
||||
return editorInstancePromise;
|
||||
},
|
||||
destroyAll: function() {
|
||||
_.each(tinyMCE.editors, function(editor) {
|
||||
|
|
57
app/assets/stylesheets/protocols/tinymce_editor.scss
Normal file
57
app/assets/stylesheets/protocols/tinymce_editor.scss
Normal file
|
@ -0,0 +1,57 @@
|
|||
.protocol-content {
|
||||
.tinymce-container {
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
min-height: 36px;
|
||||
width: 100%;
|
||||
|
||||
form > .form-group {
|
||||
margin-bottom: 0;
|
||||
|
||||
> .mce-tinymce {
|
||||
border: 1px solid $color-silver-chalice;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.form-group > .mce-tinymce {
|
||||
border-color: $brand-danger;
|
||||
}
|
||||
|
||||
.tinymce-save-button {
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mce-tinymce.mce-container.mce-panel {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mce-menubar.mce-toolbar.mce-first {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.mce-statusbar {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.tinymce-status-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tinymce-view {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.tinymce-error {
|
||||
color: $brand-danger;
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
.sci-inline-edit__content {
|
||||
border: $border-transparent;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
@ -15,6 +16,7 @@
|
|||
margin-left: -.25em;
|
||||
min-height: 36px;
|
||||
padding-left: .25em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.sci-inline-edit__view {
|
||||
|
@ -40,22 +42,35 @@
|
|||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: $brand-danger;
|
||||
}
|
||||
}
|
||||
|
||||
.sci-inline-edit__error {
|
||||
bottom: -16px;
|
||||
color: $brand-danger;
|
||||
font-size: .8em;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
|
||||
.sci-inline-edit__content {
|
||||
background-color: $color-white;
|
||||
border-color: $brand-focus;
|
||||
|
||||
&.error {
|
||||
border-color: $brand-danger;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.sci-inline-edit__control {
|
||||
&.btn-disabled {
|
||||
background: $color-silver-chalice;
|
||||
color: $color-concrete;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,21 +31,6 @@
|
|||
position: absolute;
|
||||
}
|
||||
|
||||
.tinymce-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
min-height: 36px;
|
||||
width: 100%;
|
||||
|
||||
.tinymce-status-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tinymce-view {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.edit) {
|
||||
background: $color-concrete;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
.step-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 1.2em;
|
||||
|
||||
.step-collapse-link {
|
||||
display: inline-block;
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
:objectId="parseInt(protocol.id)"
|
||||
:fieldName="'protocol[description]'"
|
||||
:lastUpdated="protocol.attributes.updated_at"
|
||||
:characterLimit="100000"
|
||||
@update="updateDescription"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
v-if="element.attributes.orderable.urls.update_url"
|
||||
:value="element.attributes.orderable.name"
|
||||
:sa_value="element.attributes.orderable.sa_name"
|
||||
:characterLimit="255"
|
||||
:characterLimit="10000"
|
||||
:placeholder="''"
|
||||
:allowBlank="false"
|
||||
:autofocus="editingName"
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
:objectId="element.attributes.orderable.id"
|
||||
:fieldName="'step_text[text]'"
|
||||
:lastUpdated="element.attributes.orderable.updated_at"
|
||||
:characterLimit="100000"
|
||||
@update="update"
|
||||
@editingDisabled="disableEditMode"
|
||||
@editingEnabled="enableEditMode"
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
ref="input"
|
||||
rows="1"
|
||||
v-if="editing"
|
||||
:class="{ 'error': error }"
|
||||
:placeholder="placeholder"
|
||||
v-model="newValue"
|
||||
@input="handleInput"
|
||||
|
@ -19,7 +18,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<template v-if="editing">
|
||||
<div class="sci-inline-edit__control btn btn-primary icon-btn" @click="update">
|
||||
<div :class="{ 'btn-primary': !error, 'btn-disabled': error }" class="sci-inline-edit__control btn icon-btn" @click="update">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<div class="sci-inline-edit__control btn btn-light icon-btn" @click="cancelEdit">
|
||||
|
@ -153,7 +152,7 @@
|
|||
},
|
||||
update() {
|
||||
setTimeout(() => {
|
||||
if(!this.allowBlank && this.isBlank) return;
|
||||
if(this.error) return;
|
||||
if(!this.editing) return;
|
||||
this.newValue = this.$refs.input.value // Fix for smart annotation
|
||||
this.newValue = this.newValue.trim();
|
||||
|
|
|
@ -1,50 +1,55 @@
|
|||
<template>
|
||||
<div class="tinymce-container">
|
||||
<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>
|
||||
<div class="tinymce-container" :class="{ 'error': error }">
|
||||
<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>
|
||||
<div v-if="error" class="tinymce-error">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -60,13 +65,45 @@
|
|||
objectId: Number,
|
||||
fieldName: String,
|
||||
lastUpdated: Number,
|
||||
inEditMode: Boolean
|
||||
inEditMode: Boolean,
|
||||
characterLimit: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editorInstance: null,
|
||||
characterCount: 0,
|
||||
blurEventHandler: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
inEditMode() {
|
||||
if (this.inEditMode) {
|
||||
this.initTinymce()
|
||||
}
|
||||
},
|
||||
characterCount() {
|
||||
if(this.error) {
|
||||
this.editorInstance.blurDisabled = true;
|
||||
} else {
|
||||
this.editorInstance.blurDisabled = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
error() {
|
||||
if(this.characterLimit && this.characterCount > this.characterLimit) {
|
||||
return(
|
||||
this.i18n.t(
|
||||
'inline_edit.errors.over_limit',
|
||||
{ attribute: this.i18n.t('general.text.name'), limit: this.characterLimit }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -77,12 +114,26 @@
|
|||
this.$emit('update', data)
|
||||
}
|
||||
this.$emit('editingDisabled')
|
||||
}).then((editorInstance) => {
|
||||
this.editorInstance = editorInstance[0]; // TinyMCE initialization returns an array
|
||||
this.initCharacterCount();
|
||||
});
|
||||
this.$emit('editingEnabled')
|
||||
},
|
||||
|
||||
getStaticUrl(name) {
|
||||
return $(`meta[name=\'${name}\']`).attr('content');
|
||||
},
|
||||
initCharacterCount() {
|
||||
this.characterCount = $(this.editorInstance.getContent()).text().length
|
||||
|
||||
this.editorInstance.on('input', (e) => {
|
||||
this.characterCount = e.currentTarget.innerText.length
|
||||
});
|
||||
|
||||
// clear error on cancel
|
||||
$(this.editorInstance.container).find('.tinymce-cancel-button').on('click', ()=> {
|
||||
this.characterCount = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3001,6 +3001,7 @@ en:
|
|||
upload_failure: "File upload error. Try again or contact the administrator."
|
||||
file_name_too_long: 'is too long (maximum is %{limit} characters, with extension)'
|
||||
text:
|
||||
name: "Text"
|
||||
not_blank: "can't be blank"
|
||||
length_too_long_general: "is too long"
|
||||
length_too_long: "is too long (maximum is %{max_length} characters)"
|
||||
|
|
Loading…
Reference in a new issue