mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Refactor checklist [SCI-9959]
This commit is contained in:
parent
cfb64a125c
commit
1fff6da45d
|
@ -18,7 +18,7 @@ module StepElements
|
|||
checklist_item = @checklist.checklist_items.new(checklist_item_params.merge!(created_by: current_user))
|
||||
new_items = []
|
||||
ActiveRecord::Base.transaction do
|
||||
new_items = checklist_item.save_multiline!
|
||||
new_items = checklist_item.save_multiline!(after_id: params[:after_id])
|
||||
new_items.each do |item|
|
||||
log_activity(
|
||||
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_added",
|
||||
|
@ -102,9 +102,10 @@ module StepElements
|
|||
end
|
||||
|
||||
def reorder
|
||||
checklist_item = @checklist.checklist_items.find(checklist_item_params[:id])
|
||||
checklist_item = @checklist.checklist_items.find(params[:id])
|
||||
ActiveRecord::Base.transaction do
|
||||
checklist_item.insert_at(checklist_item_params[:position])
|
||||
insert_at = (@checklist.checklist_items.find_by(id: params[:after_id])&.position || 0)
|
||||
checklist_item.insert_at(insert_at)
|
||||
end
|
||||
render json: params[:checklist_item_positions], status: :ok
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
@delete="showDeleteModal"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
<div v-if="element.attributes.orderable.urls.create_item_url || orderedChecklistItems.length > 0" :class="{ 'pointer-events-none': locked }">
|
||||
<div v-if="element.attributes.orderable.urls.create_item_url || checklistItems.length > 0" :class="{ 'pointer-events-none': locked }">
|
||||
<Draggable
|
||||
v-model="checklistItems"
|
||||
:ghostClass="'checklist-item-ghost'"
|
||||
|
@ -63,8 +63,8 @@
|
|||
<div v-if="element.attributes.orderable.urls.create_item_url && !addingNewItem"
|
||||
class="flex items-center gap-1 text-sn-blue cursor-pointer mb-2 mt-1 "
|
||||
tabindex="0"
|
||||
@keyup.enter="addItem(orderedChecklistItems.length + 1)"
|
||||
@click="addItem(orderedChecklistItems.length + 1)">
|
||||
@keyup.enter="addItem(checklistItems[checklistItems.length - 1]?.id)"
|
||||
@click="addItem(checklistItems[checklistItems.length - 1]?.id)">
|
||||
<i class="sn-icon sn-icon-new-task w-6 text-center inline-block"></i>
|
||||
{{ i18n.t('protocols.steps.insert.checklist_item') }}
|
||||
</div>
|
||||
|
@ -81,6 +81,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
/* global HelperModule I18n */
|
||||
|
||||
import Draggable from 'vuedraggable';
|
||||
import DeleteMixin from './mixins/delete.js';
|
||||
import MoveMixin from './mixins/move.js';
|
||||
|
@ -90,6 +93,7 @@ import InlineEdit from '../inline_edit.vue';
|
|||
import ChecklistItem from './checklistItem.vue';
|
||||
import moveElementModal from './modal/move.vue';
|
||||
import MenuDropdown from '../menu_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'Checklist',
|
||||
|
@ -128,7 +132,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
if (this.isNew) {
|
||||
this.addItem(1);
|
||||
this.addItem();
|
||||
} else {
|
||||
this.loadChecklistItems();
|
||||
}
|
||||
|
@ -139,13 +143,6 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
orderedChecklistItems() {
|
||||
return this.checklistItems.sort((a, b) => a.attributes.position - b.attributes.position || b.id - a.id)
|
||||
.map((item, index) => {
|
||||
item.attributes.position = index + 1;
|
||||
return item;
|
||||
});
|
||||
},
|
||||
locked() {
|
||||
return this.editingName || !this.element.attributes.orderable.urls.update_url;
|
||||
},
|
||||
|
@ -199,20 +196,23 @@ export default {
|
|||
this.update();
|
||||
},
|
||||
postItem(item) {
|
||||
item.attributes.position = item.attributes.position - 1;
|
||||
$.post(this.element.attributes.orderable.urls.create_item_url, item).done((result) => {
|
||||
this.loadChecklistItems(result.data[result.data.length - 1].attributes.position);
|
||||
}).fail((e) => {
|
||||
const position = this.checklistItems.findIndex((i) => i.id === item.id);
|
||||
let afterId = null;
|
||||
if (position > 0) {
|
||||
afterId = this.checklistItems[position - 1].id;
|
||||
}
|
||||
axios.post(this.element.attributes.orderable.urls.create_item_url, {
|
||||
attributes: item.attributes,
|
||||
after_id: afterId
|
||||
}).then((result) => {
|
||||
this.loadChecklistItems(result.data.data[result.data.data.length - 1].id);
|
||||
}).catch(() => {
|
||||
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
|
||||
});
|
||||
|
||||
// Fake element during loading
|
||||
item.id = `new${Math.floor(Math.random() * 1000000000)}`;
|
||||
this.checklistItems.push(item);
|
||||
},
|
||||
saveItem(item, key) {
|
||||
if (item.id > 0) {
|
||||
const insertAfter = key === 'Enter' ? item.attributes.position : null;
|
||||
const insertAfter = key === 'Enter' ? item.id : null;
|
||||
$.ajax({
|
||||
url: item.attributes.urls.update_url,
|
||||
type: 'PATCH',
|
||||
|
@ -220,7 +220,7 @@ export default {
|
|||
success: () => {
|
||||
this.loadChecklistItems(insertAfter);
|
||||
},
|
||||
error: (xhr) => setFlashErrors(xhr.responseJSON.errors)
|
||||
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors)
|
||||
});
|
||||
} else {
|
||||
this.postItem(item, key);
|
||||
|
@ -240,20 +240,23 @@ export default {
|
|||
});
|
||||
},
|
||||
addItem(insertAfter) {
|
||||
this.checklistItems.push(
|
||||
const afterIndex = this.checklistItems.findIndex((i) => i.id === insertAfter);
|
||||
this.checklistItems.splice(
|
||||
afterIndex + 1,
|
||||
0,
|
||||
{
|
||||
id: `new${Math.floor(Math.random() * 1000000000)}`,
|
||||
attributes: {
|
||||
text: '',
|
||||
checked: false,
|
||||
position: insertAfter,
|
||||
isNew: true
|
||||
isNew: true,
|
||||
with_paragraphs: false
|
||||
}
|
||||
}
|
||||
);
|
||||
this.checklistItems = this.orderedChecklistItems;
|
||||
},
|
||||
removeItem(position) {
|
||||
this.checklistItems = this.orderedChecklistItems.filter((item) => item.attributes.position !== position);
|
||||
this.checklistItems = this.checklistItems.filter((item) => item.attributes.position !== position);
|
||||
},
|
||||
startReorder() {
|
||||
this.reordering = true;
|
||||
|
@ -265,21 +268,26 @@ export default {
|
|||
&& Number.isInteger(event.oldIndex)
|
||||
&& event.newIndex !== event.oldIndex
|
||||
) {
|
||||
const position = this.orderedChecklistItems[event.newIndex]?.attributes.position;
|
||||
const id = this.checklistItems[event.oldIndex]?.id;
|
||||
this.checklistItems[event.oldIndex].attributes.position = position + (event.newIndex > event.oldIndex ? 1 : -1);
|
||||
this.saveItemOrder(id, position);
|
||||
let afterId = null;
|
||||
if (event.newIndex > 0) {
|
||||
if (event.newIndex > event.oldIndex) {
|
||||
afterId = this.checklistItems[event.newIndex - 1].id;
|
||||
} else {
|
||||
afterId = this.checklistItems[event.newIndex + 1].id;
|
||||
}
|
||||
}
|
||||
const id = this.checklistItems[event.newIndex]?.id;
|
||||
this.saveItemOrder(id, afterId);
|
||||
}
|
||||
},
|
||||
saveItemOrder(id, position) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.element.attributes.orderable.urls.reorder_url,
|
||||
data: JSON.stringify({ attributes: { id, position } }),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors),
|
||||
success: () => this.loadChecklistItems()
|
||||
saveItemOrder(id, afterId) {
|
||||
axios.post(this.element.attributes.orderable.urls.reorder_url, {
|
||||
id,
|
||||
after_id: afterId
|
||||
}).then(() => {
|
||||
this.loadChecklistItems();
|
||||
}).catch((e) => {
|
||||
this.setFlashErrors(e.response.errors);
|
||||
});
|
||||
},
|
||||
setFlashErrors(errors) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="content__checklist-item pl-10 ml-[-2.325rem]">
|
||||
<div class="checklist-item-header flex rounded items-center relative w-full group/checklist-item-header" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
|
||||
<div class="content__checklist-item pl-10 ml-[-2.325rem] group/checklist-item-header">
|
||||
<div class="checklist-item-header flex rounded items-center relative w-full" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
|
||||
<div v-if="reorderChecklistItemUrl"
|
||||
class="absolute h-6 cursor-grab justify-center left-[-2.325rem] top-0.5 px-2 tw-hidden text-sn-grey element-grip step-element-grip--draggable"
|
||||
:class="{ 'group-hover/checklist-item-header:flex': (!locked && !editingText && draggable) }"
|
||||
|
@ -8,7 +8,8 @@
|
|||
<i class="sn-icon sn-icon-drag"></i>
|
||||
</div>
|
||||
<div class="flex items-start gap-2 grow" :class="{ 'done': checklistItem.attributes.checked }">
|
||||
<div v-if="!inRepository" class="sci-checkbox-container my-1.5 border-0 border-y border-transparent border-solid" :class="{ 'disabled': !toggleUrl }" :style="toggleUrl && 'pointer-events: initial'">
|
||||
<div v-if="!inRepository" class="sci-checkbox-container my-1.5 border-0 border-y border-transparent border-solid"
|
||||
:class="{ 'disabled': !toggleUrl }" :style="toggleUrl && 'pointer-events: initial'">
|
||||
<input ref="checkbox"
|
||||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
|
@ -42,9 +43,10 @@
|
|||
@delete="removeItem()"
|
||||
@keypress="keyPressHandler"
|
||||
@blur="onBlurHandler"
|
||||
@paste="pasteHandler"
|
||||
/>
|
||||
<span v-if="!editingText && (!checklistItem.attributes.urls || deleteUrl)" class="absolute right-0 top-0.5 leading-6 tw-hidden group-hover/checklist-item-header:inline-block !text-sn-blue cursor-pointer" @click="showDeleteModal" tabindex="0">
|
||||
<span v-if="!editingText && (!checklistItem.attributes.urls || deleteUrl)"
|
||||
class="absolute right-0 top-0.5 leading-6 tw-hidden group-hover/checklist-item-header:inline-block !text-sn-blue cursor-pointer"
|
||||
@click="showDeleteModal" tabindex="0">
|
||||
<i class="sn-icon sn-icon-delete"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -127,8 +129,7 @@ export default {
|
|||
disableTextEdit() {
|
||||
if (this.checklistItem.attributes.isNew) {
|
||||
if (this.deleting) return;
|
||||
|
||||
this.removeItem();
|
||||
if (this.checklistItem.attributes.text.length === 0) this.removeItem();
|
||||
this.$emit('editEnd');
|
||||
}
|
||||
},
|
||||
|
@ -162,15 +163,9 @@ export default {
|
|||
this.$emit('update', this.checklistItem, withKey);
|
||||
},
|
||||
keyPressHandler(e) {
|
||||
if (
|
||||
((e.shiftKey || e.metaKey) && e.key === 'Enter')
|
||||
|| ((e.ctrlKey || e.metaKey) && e.key === 'v')
|
||||
) {
|
||||
if ((e.shiftKey || e.metaKey) && e.key === 'Enter') {
|
||||
this.checklistItem.attributes.with_paragraphs = true;
|
||||
}
|
||||
},
|
||||
pasteHandler() {
|
||||
this.checklistItem.attributes.with_paragraphs = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -270,8 +270,8 @@ export default {
|
|||
this.newValue = this.$refs.input.value.trim(); // Fix for smart annotation
|
||||
|
||||
this.editing = false;
|
||||
this.$emit('editingDisabled');
|
||||
this.$emit('update', this.newValue, withKey);
|
||||
this.$emit('editingDisabled');
|
||||
},
|
||||
refreshTexareaHeight() {
|
||||
if (this.editing && !this.singleLine) {
|
||||
|
|
|
@ -27,13 +27,13 @@ class ChecklistItem < ApplicationRecord
|
|||
after_save :touch_checklist
|
||||
after_touch :touch_checklist
|
||||
|
||||
def save_multiline!
|
||||
def save_multiline!(after_id: nil)
|
||||
at_position = checklist.checklist_items.find_by(id: after_id).position if after_id
|
||||
|
||||
if with_paragraphs
|
||||
if new_record?
|
||||
original_position = position
|
||||
self.position = nil
|
||||
save!
|
||||
insert_at(original_position + 1)
|
||||
insert_at(at_position + 1) || 0
|
||||
else
|
||||
save!
|
||||
end
|
||||
|
@ -42,7 +42,7 @@ class ChecklistItem < ApplicationRecord
|
|||
|
||||
items = []
|
||||
if new_record?
|
||||
start_position = position
|
||||
start_position = at_position || 0
|
||||
text.split("\n").compact.each do |line|
|
||||
new_item = checklist.checklist_items.create!(text: line)
|
||||
new_item.insert_at(start_position + 1)
|
||||
|
|
Loading…
Reference in a new issue