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))
|
checklist_item = @checklist.checklist_items.new(checklist_item_params.merge!(created_by: current_user))
|
||||||
new_items = []
|
new_items = []
|
||||||
ActiveRecord::Base.transaction do
|
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|
|
new_items.each do |item|
|
||||||
log_activity(
|
log_activity(
|
||||||
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_added",
|
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_added",
|
||||||
|
@ -102,9 +102,10 @@ module StepElements
|
||||||
end
|
end
|
||||||
|
|
||||||
def reorder
|
def reorder
|
||||||
checklist_item = @checklist.checklist_items.find(checklist_item_params[:id])
|
checklist_item = @checklist.checklist_items.find(params[:id])
|
||||||
ActiveRecord::Base.transaction do
|
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
|
end
|
||||||
render json: params[:checklist_item_positions], status: :ok
|
render json: params[:checklist_item_positions], status: :ok
|
||||||
rescue ActiveRecord::RecordInvalid
|
rescue ActiveRecord::RecordInvalid
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
@delete="showDeleteModal"
|
@delete="showDeleteModal"
|
||||||
></MenuDropdown>
|
></MenuDropdown>
|
||||||
</div>
|
</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
|
<Draggable
|
||||||
v-model="checklistItems"
|
v-model="checklistItems"
|
||||||
:ghostClass="'checklist-item-ghost'"
|
:ghostClass="'checklist-item-ghost'"
|
||||||
|
@ -63,8 +63,8 @@
|
||||||
<div v-if="element.attributes.orderable.urls.create_item_url && !addingNewItem"
|
<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 "
|
class="flex items-center gap-1 text-sn-blue cursor-pointer mb-2 mt-1 "
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keyup.enter="addItem(orderedChecklistItems.length + 1)"
|
@keyup.enter="addItem(checklistItems[checklistItems.length - 1]?.id)"
|
||||||
@click="addItem(orderedChecklistItems.length + 1)">
|
@click="addItem(checklistItems[checklistItems.length - 1]?.id)">
|
||||||
<i class="sn-icon sn-icon-new-task w-6 text-center inline-block"></i>
|
<i class="sn-icon sn-icon-new-task w-6 text-center inline-block"></i>
|
||||||
{{ i18n.t('protocols.steps.insert.checklist_item') }}
|
{{ i18n.t('protocols.steps.insert.checklist_item') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,6 +81,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
/* global HelperModule I18n */
|
||||||
|
|
||||||
import Draggable from 'vuedraggable';
|
import Draggable from 'vuedraggable';
|
||||||
import DeleteMixin from './mixins/delete.js';
|
import DeleteMixin from './mixins/delete.js';
|
||||||
import MoveMixin from './mixins/move.js';
|
import MoveMixin from './mixins/move.js';
|
||||||
|
@ -90,6 +93,7 @@ import InlineEdit from '../inline_edit.vue';
|
||||||
import ChecklistItem from './checklistItem.vue';
|
import ChecklistItem from './checklistItem.vue';
|
||||||
import moveElementModal from './modal/move.vue';
|
import moveElementModal from './modal/move.vue';
|
||||||
import MenuDropdown from '../menu_dropdown.vue';
|
import MenuDropdown from '../menu_dropdown.vue';
|
||||||
|
import axios from '../../../packs/custom_axios.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Checklist',
|
name: 'Checklist',
|
||||||
|
@ -128,7 +132,7 @@ export default {
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.isNew) {
|
if (this.isNew) {
|
||||||
this.addItem(1);
|
this.addItem();
|
||||||
} else {
|
} else {
|
||||||
this.loadChecklistItems();
|
this.loadChecklistItems();
|
||||||
}
|
}
|
||||||
|
@ -139,13 +143,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
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() {
|
locked() {
|
||||||
return this.editingName || !this.element.attributes.orderable.urls.update_url;
|
return this.editingName || !this.element.attributes.orderable.urls.update_url;
|
||||||
},
|
},
|
||||||
|
@ -199,20 +196,23 @@ export default {
|
||||||
this.update();
|
this.update();
|
||||||
},
|
},
|
||||||
postItem(item) {
|
postItem(item) {
|
||||||
item.attributes.position = item.attributes.position - 1;
|
const position = this.checklistItems.findIndex((i) => i.id === item.id);
|
||||||
$.post(this.element.attributes.orderable.urls.create_item_url, item).done((result) => {
|
let afterId = null;
|
||||||
this.loadChecklistItems(result.data[result.data.length - 1].attributes.position);
|
if (position > 0) {
|
||||||
}).fail((e) => {
|
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');
|
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) {
|
saveItem(item, key) {
|
||||||
if (item.id > 0) {
|
if (item.id > 0) {
|
||||||
const insertAfter = key === 'Enter' ? item.attributes.position : null;
|
const insertAfter = key === 'Enter' ? item.id : null;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: item.attributes.urls.update_url,
|
url: item.attributes.urls.update_url,
|
||||||
type: 'PATCH',
|
type: 'PATCH',
|
||||||
|
@ -220,7 +220,7 @@ export default {
|
||||||
success: () => {
|
success: () => {
|
||||||
this.loadChecklistItems(insertAfter);
|
this.loadChecklistItems(insertAfter);
|
||||||
},
|
},
|
||||||
error: (xhr) => setFlashErrors(xhr.responseJSON.errors)
|
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.postItem(item, key);
|
this.postItem(item, key);
|
||||||
|
@ -240,20 +240,23 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addItem(insertAfter) {
|
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: {
|
attributes: {
|
||||||
text: '',
|
text: '',
|
||||||
checked: false,
|
checked: false,
|
||||||
position: insertAfter,
|
isNew: true,
|
||||||
isNew: true
|
with_paragraphs: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.checklistItems = this.orderedChecklistItems;
|
|
||||||
},
|
},
|
||||||
removeItem(position) {
|
removeItem(position) {
|
||||||
this.checklistItems = this.orderedChecklistItems.filter((item) => item.attributes.position !== position);
|
this.checklistItems = this.checklistItems.filter((item) => item.attributes.position !== position);
|
||||||
},
|
},
|
||||||
startReorder() {
|
startReorder() {
|
||||||
this.reordering = true;
|
this.reordering = true;
|
||||||
|
@ -265,21 +268,26 @@ export default {
|
||||||
&& Number.isInteger(event.oldIndex)
|
&& Number.isInteger(event.oldIndex)
|
||||||
&& event.newIndex !== event.oldIndex
|
&& event.newIndex !== event.oldIndex
|
||||||
) {
|
) {
|
||||||
const position = this.orderedChecklistItems[event.newIndex]?.attributes.position;
|
let afterId = null;
|
||||||
const id = this.checklistItems[event.oldIndex]?.id;
|
if (event.newIndex > 0) {
|
||||||
this.checklistItems[event.oldIndex].attributes.position = position + (event.newIndex > event.oldIndex ? 1 : -1);
|
if (event.newIndex > event.oldIndex) {
|
||||||
this.saveItemOrder(id, position);
|
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) {
|
saveItemOrder(id, afterId) {
|
||||||
$.ajax({
|
axios.post(this.element.attributes.orderable.urls.reorder_url, {
|
||||||
type: 'POST',
|
id,
|
||||||
url: this.element.attributes.orderable.urls.reorder_url,
|
after_id: afterId
|
||||||
data: JSON.stringify({ attributes: { id, position } }),
|
}).then(() => {
|
||||||
contentType: 'application/json',
|
this.loadChecklistItems();
|
||||||
dataType: 'json',
|
}).catch((e) => {
|
||||||
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors),
|
this.setFlashErrors(e.response.errors);
|
||||||
success: () => this.loadChecklistItems()
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setFlashErrors(errors) {
|
setFlashErrors(errors) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="content__checklist-item pl-10 ml-[-2.325rem]">
|
<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 group/checklist-item-header" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
|
<div class="checklist-item-header flex rounded items-center relative w-full" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
|
||||||
<div v-if="reorderChecklistItemUrl"
|
<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="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) }"
|
:class="{ 'group-hover/checklist-item-header:flex': (!locked && !editingText && draggable) }"
|
||||||
|
@ -8,7 +8,8 @@
|
||||||
<i class="sn-icon sn-icon-drag"></i>
|
<i class="sn-icon sn-icon-drag"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start gap-2 grow" :class="{ 'done': checklistItem.attributes.checked }">
|
<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"
|
<input ref="checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="sci-checkbox"
|
class="sci-checkbox"
|
||||||
|
@ -42,9 +43,10 @@
|
||||||
@delete="removeItem()"
|
@delete="removeItem()"
|
||||||
@keypress="keyPressHandler"
|
@keypress="keyPressHandler"
|
||||||
@blur="onBlurHandler"
|
@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>
|
<i class="sn-icon sn-icon-delete"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,8 +129,7 @@ export default {
|
||||||
disableTextEdit() {
|
disableTextEdit() {
|
||||||
if (this.checklistItem.attributes.isNew) {
|
if (this.checklistItem.attributes.isNew) {
|
||||||
if (this.deleting) return;
|
if (this.deleting) return;
|
||||||
|
if (this.checklistItem.attributes.text.length === 0) this.removeItem();
|
||||||
this.removeItem();
|
|
||||||
this.$emit('editEnd');
|
this.$emit('editEnd');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -162,15 +163,9 @@ export default {
|
||||||
this.$emit('update', this.checklistItem, withKey);
|
this.$emit('update', this.checklistItem, withKey);
|
||||||
},
|
},
|
||||||
keyPressHandler(e) {
|
keyPressHandler(e) {
|
||||||
if (
|
if ((e.shiftKey || e.metaKey) && e.key === 'Enter') {
|
||||||
((e.shiftKey || e.metaKey) && e.key === 'Enter')
|
|
||||||
|| ((e.ctrlKey || e.metaKey) && e.key === 'v')
|
|
||||||
) {
|
|
||||||
this.checklistItem.attributes.with_paragraphs = true;
|
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.newValue = this.$refs.input.value.trim(); // Fix for smart annotation
|
||||||
|
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
this.$emit('editingDisabled');
|
|
||||||
this.$emit('update', this.newValue, withKey);
|
this.$emit('update', this.newValue, withKey);
|
||||||
|
this.$emit('editingDisabled');
|
||||||
},
|
},
|
||||||
refreshTexareaHeight() {
|
refreshTexareaHeight() {
|
||||||
if (this.editing && !this.singleLine) {
|
if (this.editing && !this.singleLine) {
|
||||||
|
|
|
@ -27,13 +27,13 @@ class ChecklistItem < ApplicationRecord
|
||||||
after_save :touch_checklist
|
after_save :touch_checklist
|
||||||
after_touch :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 with_paragraphs
|
||||||
if new_record?
|
if new_record?
|
||||||
original_position = position
|
|
||||||
self.position = nil
|
|
||||||
save!
|
save!
|
||||||
insert_at(original_position + 1)
|
insert_at(at_position + 1) || 0
|
||||||
else
|
else
|
||||||
save!
|
save!
|
||||||
end
|
end
|
||||||
|
@ -42,7 +42,7 @@ class ChecklistItem < ApplicationRecord
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
if new_record?
|
if new_record?
|
||||||
start_position = position
|
start_position = at_position || 0
|
||||||
text.split("\n").compact.each do |line|
|
text.split("\n").compact.each do |line|
|
||||||
new_item = checklist.checklist_items.create!(text: line)
|
new_item = checklist.checklist_items.create!(text: line)
|
||||||
new_item.insert_at(start_position + 1)
|
new_item.insert_at(start_position + 1)
|
||||||
|
|
Loading…
Reference in a new issue