mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-02-28 17:54:16 +08:00
Merge branch 'develop' into features/bugfixes-and-css
This commit is contained in:
commit
5a073b1364
11 changed files with 170 additions and 46 deletions
|
@ -1,6 +1,6 @@
|
|||
env:
|
||||
- DOCKER_COMPOSE_VERSION=1.23.2
|
||||
|
||||
- DOCKER_COMPOSE_VERSION=v2.22.0
|
||||
dist: jammy
|
||||
sudo: required
|
||||
language: ruby
|
||||
addons:
|
||||
|
@ -10,9 +10,9 @@ services:
|
|||
- docker
|
||||
before_install:
|
||||
- sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
- make docker
|
||||
- make docker-ci
|
||||
script:
|
||||
- make tests-ci
|
||||
|
|
18
Makefile
18
Makefile
|
@ -24,6 +24,9 @@ heroku:
|
|||
docker:
|
||||
@docker-compose build
|
||||
|
||||
docker-ci:
|
||||
@docker-compose --progress plain build web
|
||||
|
||||
docker-production:
|
||||
@docker-compose -f docker-compose.production.yml build --build-arg BUILD_TIMESTAMP=$(BUILD_TIMESTAMP)
|
||||
|
||||
|
@ -81,10 +84,17 @@ integration-tests:
|
|||
@$(MAKE) rails cmd="bundle exec cucumber"
|
||||
|
||||
tests-ci:
|
||||
@docker-compose run --rm web bash -c "bundle install && yarn install"
|
||||
@docker-compose up -d webpack
|
||||
@docker-compose ps
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false -e MAIL_FROM=MAIL_FROM -e MAIL_REPLYTO=MAIL_REPLYTO -e RAILS_ENV=test -e MAIL_SERVER_URL=localhost:3000 -e ENABLE_RECAPTCHA=false -e ENABLE_USER_CONFIRMATION=false -e ENABLE_USER_REGISTRATION=true -e CORE_API_RATE_LIMIT=1000000 --rm web bash -c "rake db:create && rake db:migrate && yarn install && bundle exec rspec"
|
||||
@docker-compose run --rm web bash -c "bundle install"
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false \
|
||||
-e MAIL_FROM=MAIL_FROM \
|
||||
-e MAIL_REPLYTO=MAIL_REPLYTO \
|
||||
-e RAILS_ENV=test \
|
||||
-e MAIL_SERVER_URL=localhost:3000 \
|
||||
-e ENABLE_RECAPTCHA=false \
|
||||
-e ENABLE_USER_CONFIRMATION=false \
|
||||
-e ENABLE_USER_REGISTRATION=true \
|
||||
-e CORE_API_RATE_LIMIT=1000000 \
|
||||
--rm web bash -c "rake db:create && rake db:migrate && bundle exec rspec ./spec/requests/api/"
|
||||
|
||||
console:
|
||||
@$(MAKE) rails cmd="rails console"
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.29.1
|
||||
1.29.1.1
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@layer components {
|
||||
|
||||
.sci-label {
|
||||
@apply text-sm font-medium text-sn-grey;
|
||||
@apply text-sm font-medium text-sn-dark-grey;
|
||||
}
|
||||
|
||||
.sci-input-container-v2 {
|
||||
|
|
|
@ -212,6 +212,14 @@
|
|||
@publish="publishProtocol"
|
||||
@cancel="closePublishModal"
|
||||
/>
|
||||
<clipboardPasteModal v-if="showClipboardPasteModal"
|
||||
:image="pasteImages"
|
||||
:objects="steps"
|
||||
:objectType="'step'"
|
||||
:selectedObjectId="firstObjectInViewport()"
|
||||
@files="uploadFilesToStep"
|
||||
@cancel="showClipboardPasteModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -223,6 +231,8 @@
|
|||
import Tinymce from '../shared/tinymce.vue'
|
||||
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
|
||||
import PublishProtocol from './modals/publish_protocol.vue'
|
||||
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
|
||||
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js'
|
||||
|
||||
import UtilsMixin from '../mixins/utils.js'
|
||||
import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
|
||||
|
@ -236,8 +246,8 @@
|
|||
required: true
|
||||
}
|
||||
},
|
||||
components: { Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol},
|
||||
mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver],
|
||||
components: { Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol, clipboardPasteModal},
|
||||
mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
|
||||
computed: {
|
||||
inRepository() {
|
||||
return this.protocol.attributes.in_repository
|
||||
|
@ -247,7 +257,7 @@
|
|||
},
|
||||
urls() {
|
||||
return this.protocol.attributes.urls || {}
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -439,6 +449,16 @@
|
|||
},
|
||||
dragEnter(id) {
|
||||
this.activeDragStep = id;
|
||||
},
|
||||
uploadFilesToStep(file, stepId) {
|
||||
this.$children.find(child => child.step?.id == stepId).uploadFiles(file);
|
||||
},
|
||||
firstObjectInViewport() {
|
||||
let step = $('.step-container:not(.locked)').toArray().find(element => {
|
||||
const { top, bottom } = element.getBoundingClientRect()
|
||||
return bottom > 0 && top < window.innerHeight
|
||||
})
|
||||
return step ? step.dataset.id : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
@drop.prevent="dropFile"
|
||||
@dragenter.prevent="dragEnter($event)"
|
||||
@dragover.prevent
|
||||
:class="{ 'draging-file': dragingFile, 'editing-name': editingName }"
|
||||
:data-id="step.id"
|
||||
:class="{ 'draging-file': dragingFile, 'editing-name': editingName, 'locked': !urls.update_url }"
|
||||
>
|
||||
<div class="drop-message" @dragleave.prevent="!showFileModal ? dragingFile = false : null">
|
||||
{{ i18n.t('protocols.steps.drop_message', { position: step.attributes.position + 1 }) }}
|
||||
|
@ -134,12 +135,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<deleteStepModal v-if="confirmingDelete" @confirm="deleteStep" @cancel="closeDeleteModal"/>
|
||||
<clipboardPasteModal v-if="showClipboardPasteModal"
|
||||
:parent="step"
|
||||
:image="pasteImages"
|
||||
@files="uploadFiles"
|
||||
@cancel="showClipboardPasteModal = false"
|
||||
/>
|
||||
<ReorderableItemsModal v-if="reordering"
|
||||
:title="i18n.t('protocols.steps.modals.reorder_elements.title', { step_position: step.attributes.position + 1 })"
|
||||
:items="reorderableElements"
|
||||
|
@ -162,7 +157,6 @@
|
|||
import Checklist from '../shared/content/checklist.vue'
|
||||
import deleteStepModal from './modals/delete_step.vue'
|
||||
import Attachments from '../shared/content/attachments.vue'
|
||||
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
|
||||
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
|
||||
import MenuDropdown from '../shared/menu_dropdown.vue'
|
||||
|
||||
|
@ -206,7 +200,6 @@
|
|||
attachmentsReady: false,
|
||||
confirmingDelete: false,
|
||||
showFileModal: false,
|
||||
showClipboardPasteModal: false,
|
||||
showCommentsSidebar: false,
|
||||
dragingFile: false,
|
||||
reordering: false,
|
||||
|
@ -230,7 +223,6 @@
|
|||
StepText,
|
||||
Checklist,
|
||||
deleteStepModal,
|
||||
clipboardPasteModal,
|
||||
Attachments,
|
||||
StorageUsage,
|
||||
ReorderableItemsModal,
|
||||
|
@ -552,10 +544,6 @@
|
|||
rect.right <= (window.innerWidth || $(window).width())
|
||||
);
|
||||
},
|
||||
copyPasteImageModal(pasteImages) {
|
||||
this.pasteImages = pasteImages;
|
||||
this.showClipboardPasteModal = true;
|
||||
},
|
||||
insertElement(element) {
|
||||
let position = element.attributes.position;
|
||||
this.elements = this.elements.map( s => {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
@drop.prevent="dropFile"
|
||||
@dragenter.prevent="dragEnter($event)"
|
||||
@dragover.prevent
|
||||
:class="{ 'bg-sn-super-light-blue': dragingFile, 'bg-white': !dragingFile }"
|
||||
:data-id="result.id"
|
||||
:class="{ 'bg-sn-super-light-blue': dragingFile, 'bg-white': !dragingFile, 'locked': locked }"
|
||||
>
|
||||
<div class="text-xl items-center flex flex-col text-sn-blue h-full justify-center left-0 absolute top-0 w-full"
|
||||
v-if="dragingFile"
|
||||
|
|
|
@ -32,6 +32,14 @@
|
|||
@result:drag_enter="dragEnter"
|
||||
/>
|
||||
</div>
|
||||
<clipboardPasteModal v-if="showClipboardPasteModal"
|
||||
:image="pasteImages"
|
||||
:objects="results"
|
||||
:objectType="'result'"
|
||||
:selectedObjectId="firstObjectInViewport()"
|
||||
@files="uploadFilesToResult"
|
||||
@cancel="showClipboardPasteModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -43,10 +51,13 @@
|
|||
import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
|
||||
import moduleNameObserver from '../mixins/moduleNameObserver';
|
||||
|
||||
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
|
||||
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js'
|
||||
|
||||
export default {
|
||||
name: 'Results',
|
||||
components: { ResultsToolbar, Result },
|
||||
mixins: [stackableHeadersMixin, moduleNameObserver],
|
||||
components: { ResultsToolbar, Result, clipboardPasteModal },
|
||||
mixins: [stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
|
||||
props: {
|
||||
url: { type: String, required: true },
|
||||
canCreate: { type: String, required: true },
|
||||
|
@ -136,6 +147,16 @@
|
|||
},
|
||||
dragEnter(id) {
|
||||
this.activeDragResult = id;
|
||||
},
|
||||
uploadFilesToResult(file, resultId) {
|
||||
this.$children.find(child => child.result?.id == resultId).uploadFiles(file);
|
||||
},
|
||||
firstObjectInViewport() {
|
||||
let result = $('.result-wrapper:not(.locked)').toArray().find(element => {
|
||||
const { top, bottom } = element.getBoundingClientRect()
|
||||
return bottom > 0 && top < window.innerHeight
|
||||
})
|
||||
return result ? result.dataset.id : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,29 +12,68 @@
|
|||
<h4 class="modal-title">{{i18n.t('assets.from_clipboard.modal_title')}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><strong>{{i18n.t('assets.from_clipboard.image_preview')}}</strong></p>
|
||||
<canvas style="border:1px solid grey;max-width:400px;max-height:300px" id="clipboardPreview" />
|
||||
<p><strong>{{i18n.t('assets.from_clipboard.file_name')}}</strong></p>
|
||||
<div class="input-group">
|
||||
<input id="clipboardImageName" type="text" class="form-control"
|
||||
<label class="sci-label">{{i18n.t('assets.from_clipboard.image_preview')}}</label>
|
||||
<div class="flex justify-center w-full">
|
||||
<canvas class="max-h-80 max-w-lg rounded border border-solid border-sn-light-grey" ref="preview" />
|
||||
</div>
|
||||
<div class="w-full py-6">
|
||||
<label class="sci-label">{{i18n.t(`assets.from_clipboard.select_${objectType}`)}}</label>
|
||||
<SelectSearch
|
||||
:value="target"
|
||||
@change="setTarget"
|
||||
:options="targets"
|
||||
:isLoading="false"
|
||||
:placeholder="
|
||||
i18n.t(`protocols.steps.modals.move_element.${objectType}.search_placeholder`)
|
||||
"
|
||||
:searchPlaceholder="
|
||||
i18n.t(`protocols.steps.modals.move_element.${objectType}.search_placeholder`)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<label class="sci-label">{{i18n.t('assets.from_clipboard.file_name')}}</label>
|
||||
<div class="sci-input-container-v2">
|
||||
<input id="clipboardImageName" type="text" class="sci-input-field !pr-16" v-model="fileName"
|
||||
:placeholder="i18n.t('assets.from_clipboard.file_name_placeholder')" aria-describedby="image-name">
|
||||
<span class="input-group-addon" id="image-name"></span>
|
||||
<span class="absolute right-2.5 text-sn-grey ">
|
||||
.{{ extension }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="cancel">{{i18n.t('general.cancel')}}</button>
|
||||
<button type="button" class="btn btn-success" @click="uploadImage">{{i18n.t('assets.from_clipboard.add_image')}}</button>
|
||||
<button type="button" class="btn btn-success" :disabled="!valid" @click="uploadImage">{{i18n.t('assets.from_clipboard.add_image')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import SelectSearch from "../../select_search.vue";
|
||||
|
||||
export default {
|
||||
name: 'clipboardPasteModal',
|
||||
props: {
|
||||
parent: Object,
|
||||
image: DataTransferItem
|
||||
objects: Array,
|
||||
image: DataTransferItem,
|
||||
objectType: String,
|
||||
selectedObjectId: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
target: null,
|
||||
targets: [],
|
||||
fileName: '',
|
||||
extension: '',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SelectSearch
|
||||
},
|
||||
computed: {
|
||||
valid() {
|
||||
return this.target && this.fileName.length > 0;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.modal).modal('show');
|
||||
|
@ -42,15 +81,26 @@
|
|||
$(this.$refs.modal).on('hidden.bs.modal', () => {
|
||||
this.$emit('cancel');
|
||||
});
|
||||
|
||||
if (this.selectedObjectId) this.target = this.selectedObjectId;
|
||||
this.targets = this.objects.map((object) => {
|
||||
return [
|
||||
object.id,
|
||||
object.attributes.name
|
||||
]
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
setTarget(target) {
|
||||
this.target = target;
|
||||
},
|
||||
cancel() {
|
||||
$(this.$refs.modal).modal('hide');
|
||||
},
|
||||
appendImage(item) {
|
||||
let imageBlob = item.getAsFile();
|
||||
if (imageBlob) {
|
||||
var canvas = document.getElementById('clipboardPreview');
|
||||
var canvas = this.$refs.preview;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
|
@ -63,12 +113,12 @@
|
|||
let extension = imageBlob.name.slice(
|
||||
(Math.max(0, imageBlob.name.lastIndexOf('.')) || Infinity) + 1
|
||||
);
|
||||
$('#image-name').html('.' + extension); // add extension near file name
|
||||
this.extension = extension;
|
||||
this.imageBlob = imageBlob;
|
||||
}
|
||||
},
|
||||
uploadImage() {
|
||||
let newName = $('#clipboardImageName').val();
|
||||
let newName = this.fileName;
|
||||
let imageBlog = this.imageBlob;
|
||||
// check if the name is set
|
||||
if (newName && newName.length > 0) {
|
||||
|
@ -82,7 +132,7 @@
|
|||
this.imageBlob = new File([blob], name, { type: imageBlog.type });
|
||||
}
|
||||
$(this.$refs.modal).modal('hide');
|
||||
this.$emit('files', this.imageBlob);
|
||||
this.$emit('files', this.imageBlob, this.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
showClipboardPasteModal: false,
|
||||
pasteImages: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('paste', this.handlePaste);
|
||||
},
|
||||
unmounted() {
|
||||
document.removeEventListener('paste', this.handlePaste);
|
||||
},
|
||||
methods: {
|
||||
handlePaste(e) {
|
||||
if ( e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' ) return;
|
||||
this.pasteImages = this.getImagesFromClipboard(e);
|
||||
if (this.pasteImages && this.firstObjectInViewport()) this.showClipboardPasteModal = true;
|
||||
},
|
||||
getImagesFromClipboard(e) {
|
||||
let image = null;
|
||||
if (e.clipboardData) {
|
||||
for (let i = 0; i < e.clipboardData.items.length; i++) {
|
||||
if (e.clipboardData.items[i].type.indexOf('image') !== -1) {
|
||||
image = e.clipboardData.items[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
},
|
||||
},
|
||||
};
|
|
@ -3454,9 +3454,11 @@ en:
|
|||
drop_label: 'Drop to add to Step'
|
||||
file_label: 'File'
|
||||
from_clipboard:
|
||||
modal_title: 'Add image from clipboard'
|
||||
image_preview: 'Image preview'
|
||||
add_image: 'Add'
|
||||
modal_title: 'Insert image from clipboard'
|
||||
image_preview: 'Pasted image preview'
|
||||
select_step: 'Select to which step you’d like to attach image'
|
||||
select_result: 'Select to which result you’d like to attach image'
|
||||
add_image: 'Insert'
|
||||
file_name: 'File name'
|
||||
file_name_placeholder: 'Image'
|
||||
placeholder:
|
||||
|
|
Loading…
Reference in a new issue