mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-08 04:47:22 +08:00
Merge pull request #6272 from aignatov-bio/ai-sci-9340-update-checklists-interactions
Refactor checklist [SCI-9340]
This commit is contained in:
commit
a19a2d16e9
9 changed files with 166 additions and 168 deletions
|
@ -10,22 +10,28 @@ module StepElements
|
|||
before_action :check_toggle_permissions, only: %i(toggle)
|
||||
before_action :check_manage_permissions, only: %i(create update destroy)
|
||||
|
||||
def create
|
||||
checklist_item = @checklist.checklist_items.build(checklist_item_params.merge!(created_by: current_user))
|
||||
def index
|
||||
render json: @checklist.checklist_items, each_serializer: ChecklistItemSerializer, user: current_user
|
||||
end
|
||||
|
||||
def create
|
||||
checklist_item = @checklist.checklist_items.new(checklist_item_params.merge!(created_by: current_user))
|
||||
new_items = []
|
||||
ActiveRecord::Base.transaction do
|
||||
checklist_item.save!
|
||||
log_activity(
|
||||
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_added",
|
||||
{
|
||||
checklist_item: checklist_item.text,
|
||||
checklist_name: @checklist.name
|
||||
}
|
||||
)
|
||||
checklist_item_annotation(@step, checklist_item)
|
||||
new_items = checklist_item.save_multiline!
|
||||
new_items.each do |item|
|
||||
log_activity(
|
||||
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_added",
|
||||
{
|
||||
checklist_item: item.text,
|
||||
checklist_name: @checklist.name
|
||||
}
|
||||
)
|
||||
checklist_item_annotation(@step, item)
|
||||
end
|
||||
end
|
||||
|
||||
render json: checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
||||
render json: new_items, each_serializer: ChecklistItemSerializer, user: current_user
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render json: { errors: checklist_item.errors }, status: :unprocessable_entity
|
||||
end
|
||||
|
@ -35,17 +41,20 @@ module StepElements
|
|||
@checklist_item.assign_attributes(
|
||||
checklist_item_params.except(:position, :id).merge(last_modified_by: current_user)
|
||||
)
|
||||
|
||||
if @checklist_item.save!
|
||||
log_activity(
|
||||
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_edited",
|
||||
checklist_item: @checklist_item.text,
|
||||
checklist_name: @checklist.name
|
||||
)
|
||||
checklist_item_annotation(@step, @checklist_item, old_text)
|
||||
new_items = []
|
||||
ActiveRecord::Base.transaction do
|
||||
new_items = @checklist_item.save_multiline!
|
||||
new_items.each_with_index do |item, i|
|
||||
log_activity(
|
||||
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_#{i.zero? ? 'edited' : 'added'}",
|
||||
checklist_item: item.text,
|
||||
checklist_name: @checklist.name
|
||||
)
|
||||
checklist_item_annotation(@step, item, old_text)
|
||||
end
|
||||
end
|
||||
|
||||
render json: @checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
||||
render json: new_items, each_serializer: ChecklistItemSerializer, user: current_user
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render json: { errors: @checklist_item.errors }, status: :unprocessable_entity
|
||||
end
|
||||
|
@ -96,7 +105,6 @@ module StepElements
|
|||
checklist_item = @checklist.checklist_items.find(checklist_item_params[:id])
|
||||
ActiveRecord::Base.transaction do
|
||||
checklist_item.insert_at(checklist_item_params[:position])
|
||||
@checklist.touch
|
||||
end
|
||||
render json: params[:checklist_item_positions], status: :ok
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
|
@ -118,7 +126,7 @@ module StepElements
|
|||
end
|
||||
|
||||
def checklist_item_params
|
||||
params.require(:attributes).permit(:text, :position, :id)
|
||||
params.require(:attributes).permit(:text, :position, :id, :with_paragraphs)
|
||||
end
|
||||
|
||||
def checklist_toggle_item_params
|
||||
|
|
|
@ -431,7 +431,7 @@
|
|||
secondaryNavigation.style.zIndex= 251;
|
||||
} else {
|
||||
secondaryNavigation.style.boxShadow = 'none';
|
||||
secondaryNavigation.style.zIndex= 0;
|
||||
if (secondaryNavigationTop > 10) secondaryNavigation.style.zIndex= 0;
|
||||
}
|
||||
|
||||
if (protocolHeaderTop - 5 < protocolHeaderHeight) { // When secondary navigation touch protocol header
|
||||
|
@ -457,7 +457,7 @@
|
|||
secondaryNavigation.style.top = newSecondaryTop + 'px'; // Secondary navigation starts slowly disappear
|
||||
protocolHeader.style.top = newSecondaryTop + secondaryNavigationHeight - 1 + 'px'; // Protocol header starts getting offset to compensate secondary navigation position
|
||||
// -1 to compensate small gap between protocol header and secondary navigation
|
||||
if (newSecondaryTop <= 1) secondaryNavigation.style.zIndex= 0;
|
||||
if (newSecondaryTop * -1 >= secondaryNavigationHeight) secondaryNavigation.style.zIndex= 0;
|
||||
}
|
||||
} else {
|
||||
// Just reset secondary navigation and protocol header styles to initial state
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
>
|
||||
<ChecklistItem
|
||||
v-for="checklistItem in orderedChecklistItems"
|
||||
:key="checklistItem.attributes.id"
|
||||
:key="checklistItem.id"
|
||||
:checklistItem="checklistItem"
|
||||
:locked="locked"
|
||||
:reorderChecklistItemUrl="element.attributes.orderable.urls.reorder_url"
|
||||
|
@ -55,14 +55,13 @@
|
|||
@toggle="saveItemChecked"
|
||||
@removeItem="removeItem"
|
||||
@component:delete="removeItem"
|
||||
@multilinePaste="handleMultilinePaste"
|
||||
/>
|
||||
</Draggable>
|
||||
<div v-if="element.attributes.orderable.urls.create_item_url"
|
||||
class="flex items-center gap-1 text-sn-blue cursor-pointer mb-2 mt-1"
|
||||
<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"
|
||||
@click="addItem">
|
||||
@keyup.enter="addItem(orderedChecklistItems.length + 1)"
|
||||
@click="addItem(orderedChecklistItems.length + 1)">
|
||||
<i class="sn-icon sn-icon-new-task w-6 text-center inline-block"></i>
|
||||
{{ i18n.t('protocols.steps.insert.checklist_item') }}
|
||||
</div>
|
||||
|
@ -117,36 +116,37 @@
|
|||
data() {
|
||||
return {
|
||||
checklistItems: [],
|
||||
linesToPaste: 0,
|
||||
editingName: false,
|
||||
reordering: false,
|
||||
editingItem: false
|
||||
editingItem: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initChecklistItems();
|
||||
this.loadChecklistItems();
|
||||
|
||||
if (this.isNew) {
|
||||
this.addItem();
|
||||
this.addItem(orderedChecklistItems.length + 1);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
element() {
|
||||
this.initChecklistItems();
|
||||
this.loadChecklistItems();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
orderedChecklistItems() {
|
||||
return this.checklistItems.map((item, index) => {
|
||||
return { attributes: {...item.attributes, position: index } }
|
||||
});
|
||||
},
|
||||
pastingMultiline() {
|
||||
return this.linesToPaste > 0;
|
||||
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.reordering || this.editingName || !this.element.attributes.orderable.urls.update_url
|
||||
},
|
||||
addingNewItem() {
|
||||
return this.checklistItems.find((item) => item.attributes.isNew);
|
||||
},
|
||||
actionMenu() {
|
||||
let menu = [];
|
||||
if (this.element.attributes.orderable.urls.update_url) {
|
||||
|
@ -177,57 +177,46 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
initChecklistItems() {
|
||||
this.checklistItems = this.element.attributes.orderable.checklist_items.map((item, index) => {
|
||||
return { attributes: {...item, position: index } }
|
||||
loadChecklistItems(insertAfter) {
|
||||
$.get(this.element.attributes.orderable.urls.checklist_items_url, (result) => {
|
||||
this.checklistItems = result.data;
|
||||
if (insertAfter != null) {
|
||||
this.addItem(insertAfter);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateName(name) {
|
||||
this.element.attributes.orderable.name = name;
|
||||
this.editingName = false;
|
||||
this.update(false);
|
||||
},
|
||||
update(skipRequest = true) {
|
||||
this.element.attributes.orderable.checklist_items =
|
||||
this.checklistItems.map((i) => i.attributes);
|
||||
|
||||
this.$emit('update', this.element, skipRequest);
|
||||
},
|
||||
postItem(item, callback) {
|
||||
console.log(this.element.attributes.orderable.urls.create_item_url)
|
||||
postItem(item) {
|
||||
item.attributes.position = item.attributes.position - 1;
|
||||
$.post(this.element.attributes.orderable.urls.create_item_url, item).done((result) => {
|
||||
this.checklistItems.splice(
|
||||
result.data.attributes.position,
|
||||
1,
|
||||
{ attributes: { ...result.data.attributes, id: result.data.id } }
|
||||
);
|
||||
|
||||
if(callback) callback();
|
||||
this.loadChecklistItems(result.data[result.data.length - 1].attributes.position)
|
||||
}).fail((e) => {
|
||||
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
|
||||
});
|
||||
|
||||
this.update();
|
||||
// Fake element during loading
|
||||
item.id = 'new' + Math.floor(Math.random() * 1000000000);
|
||||
this.checklistItems.push(item);
|
||||
|
||||
},
|
||||
saveItem(item) {
|
||||
if (item.attributes.id) {
|
||||
saveItem(item, key) {
|
||||
if (item.id > 0) {
|
||||
let insertAfter = key === 'Enter' ? item.attributes.position : null;
|
||||
$.ajax({
|
||||
url: item.attributes.urls.update_url,
|
||||
type: 'PATCH',
|
||||
data: item,
|
||||
success: (result) => {
|
||||
let updatedItem = this.checklistItems[item.attributes.position]
|
||||
updatedItem.attributes = result.data.attributes
|
||||
updatedItem.attributes.id = item.attributes.id
|
||||
this.$set(this.checklistItems, item.attributes.position, updatedItem)
|
||||
success: () => {
|
||||
this.loadChecklistItems(insertAfter)
|
||||
},
|
||||
error: (xhr) => setFlashErrors(xhr.responseJSON.errors)
|
||||
});
|
||||
} else {
|
||||
// create item, then append next one
|
||||
this.postItem(item, this.addItem);
|
||||
this.postItem(item, key);
|
||||
}
|
||||
this.update(true);
|
||||
},
|
||||
saveItemChecked(item) {
|
||||
$.ajax({
|
||||
|
@ -235,29 +224,28 @@
|
|||
type: 'PATCH',
|
||||
data: { attributes: { checked: item.attributes.checked } },
|
||||
success: (result) => {
|
||||
let updatedItem = this.checklistItems[item.attributes.position]
|
||||
updatedItem.attributes = result.data.attributes
|
||||
updatedItem.attributes.id = item.attributes.id
|
||||
this.$set(this.checklistItems, item.attributes.position, updatedItem)
|
||||
this.checklistItems.find(
|
||||
(i) => i.id === item.id
|
||||
).attributes.checked = result.data.attributes.checked;
|
||||
},
|
||||
error: () => HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger')
|
||||
});
|
||||
},
|
||||
addItem() {
|
||||
addItem(insertAfter) {
|
||||
this.checklistItems.push(
|
||||
{
|
||||
attributes: {
|
||||
text: '',
|
||||
checked: false,
|
||||
position: this.checklistItems.length,
|
||||
position: insertAfter,
|
||||
isNew: true
|
||||
}
|
||||
}
|
||||
);
|
||||
this.checklistItems = this.orderedChecklistItems;
|
||||
},
|
||||
removeItem(position) {
|
||||
this.checklistItems.splice(position, 1);
|
||||
this.update();
|
||||
this.checklistItems = this.orderedChecklistItems.filter((item) => item.attributes.position !== position);
|
||||
},
|
||||
startReorder() {
|
||||
this.reordering = true;
|
||||
|
@ -266,10 +254,12 @@
|
|||
this.reordering = false;
|
||||
if(
|
||||
Number.isInteger(event.newIndex)
|
||||
&& Number.isInteger(event.newIndex)
|
||||
&& Number.isInteger(event.oldIndex)
|
||||
&& event.newIndex !== event.oldIndex
|
||||
){
|
||||
const { id, position } = this.orderedChecklistItems[event.newIndex]?.attributes
|
||||
let position = this.orderedChecklistItems[event.newIndex]?.attributes.position;
|
||||
let id = this.checklistItems[event.oldIndex]?.id;
|
||||
this.checklistItems[event.oldIndex].attributes.position = position + (event.newIndex > event.oldIndex ? 1 : -1);
|
||||
this.saveItemOrder(id, position);
|
||||
}
|
||||
},
|
||||
|
@ -281,31 +271,9 @@
|
|||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors),
|
||||
success: () => this.update()
|
||||
success: () => this.loadChecklistItems()
|
||||
});
|
||||
},
|
||||
handleMultilinePaste(data) {
|
||||
this.linesToPaste = data.length;
|
||||
let nextPosition = this.checklistItems.length - 1;
|
||||
|
||||
// we need to post items to API in the right order, to avoid positions breaking
|
||||
let synchronousPost = (index) => {
|
||||
if(index === data.length) return;
|
||||
|
||||
let item = {
|
||||
attributes: {
|
||||
text: data[index],
|
||||
checked: false,
|
||||
position: nextPosition + index
|
||||
}
|
||||
};
|
||||
|
||||
this.linesToPaste -= 1;
|
||||
this.postItem(item, () => synchronousPost(index + 1));
|
||||
};
|
||||
|
||||
synchronousPost(0);
|
||||
},
|
||||
setFlashErrors(errors) {
|
||||
for(const key in errors){
|
||||
HelperModule.flashAlertMsg(
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
<div class="content__checklist-item">
|
||||
<div class="checklist-item-header flex rounded pl-10 ml-[-2.325rem] items-center relative w-full group/checklist-item-header" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
|
||||
<div v-if="reorderChecklistItemUrl"
|
||||
class="absolute h-6 cursor-grab justify-center left-0 top-1 px-2 tw-hidden text-sn-grey element-grip step-element-grip--draggable"
|
||||
class="absolute h-6 cursor-grab justify-center left-0 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) }"
|
||||
>
|
||||
<i class="sn-icon sn-icon-drag"></i>
|
||||
</div>
|
||||
<div class="flex items-start gap-2 grow pt-[1px]" :class="{ 'done': checklistItem.attributes.checked }">
|
||||
<div v-if="!inRepository" class="sci-checkbox-container my-1.5 border-y border-transparent border-solid w-6" :class="{ 'disabled': !toggleUrl }">
|
||||
<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 }">
|
||||
<input ref="checkbox"
|
||||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
|
@ -18,7 +18,11 @@
|
|||
</span>
|
||||
</div>
|
||||
<div v-else class="w-6"></div>
|
||||
<div class="pr-10 grow relative flex items-start max-w-[90ch]" :class="{ 'pointer-events-none': !checklistItem.attributes.isNew && !updateUrl }">
|
||||
<div class="pr-24 relative flex items-start max-w-[90ch]"
|
||||
:class="{
|
||||
'pointer-events-none': !checklistItem.attributes.isNew && !updateUrl,
|
||||
'flex-grow': editingText,
|
||||
}">
|
||||
<InlineEdit
|
||||
:value="checklistItem.attributes.text"
|
||||
:sa_value="checklistItem.attributes.sa_text"
|
||||
|
@ -28,18 +32,16 @@
|
|||
:singleLine="false"
|
||||
:autofocus="editingText"
|
||||
:attributeName="`${i18n.t('ChecklistItem')} ${i18n.t('name')}`"
|
||||
:multilinePaste="true"
|
||||
:editOnload="checklistItem.attributes.isNew"
|
||||
:smartAnnotation="true"
|
||||
:saveOnEnter="false"
|
||||
:allowNewLine="true"
|
||||
@editingEnabled="enableTextEdit"
|
||||
@editingDisabled="disableTextEdit"
|
||||
@update="updateText"
|
||||
@delete="removeItem()"
|
||||
@multilinePaste="(data) => { $emit('multilinePaste', data) && removeItem() }"
|
||||
@keypress="keyPressHandler"
|
||||
@blur="editingText = false"
|
||||
/>
|
||||
<span v-if="!checklistItem.attributes.urls || deleteUrl" class="absolute right-0 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>
|
||||
|
@ -81,7 +83,8 @@
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
editingText: false
|
||||
editingText: false,
|
||||
deleting: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -116,6 +119,8 @@
|
|||
},
|
||||
disableTextEdit() {
|
||||
if (this.checklistItem.attributes.isNew) {
|
||||
if (this.deleting) return
|
||||
|
||||
this.removeItem();
|
||||
this.$emit('editEnd');
|
||||
this.editingText = false;
|
||||
|
@ -129,25 +134,30 @@
|
|||
this.checklistItem.attributes.checked = this.$refs.checkbox.checked;
|
||||
this.$emit('toggle', this.checklistItem);
|
||||
},
|
||||
updateText(text) {
|
||||
updateText(text, withKey) {
|
||||
if (text.length === 0) {
|
||||
this.disableTextEdit();
|
||||
this.removeItem();
|
||||
} else {
|
||||
this.checklistItem.attributes.text = text;
|
||||
this.update();
|
||||
this.update(withKey);
|
||||
}
|
||||
},
|
||||
removeItem() {
|
||||
this.deleting = true;
|
||||
if (this.deleteUrl) {
|
||||
this.deleteElement();
|
||||
} else {
|
||||
this.$emit('removeItem', this.checklistItem.attributes.position);
|
||||
}
|
||||
},
|
||||
update() {
|
||||
this.$emit('update', this.checklistItem);
|
||||
}
|
||||
update(withKey) {
|
||||
this.$emit('update', this.checklistItem, withKey);
|
||||
},
|
||||
keyPressHandler(e) {
|
||||
if (e.key === 'Enter' && e.shiftKey) {
|
||||
this.checklistItem.attributes.with_paragraphs = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -12,13 +12,12 @@
|
|||
}"
|
||||
v-model="newValue"
|
||||
@keydown="handleKeypress"
|
||||
@paste="handlePaste"
|
||||
@blur="handleBlur"
|
||||
@keyup.escape="cancelEdit"
|
||||
@focus="setCaretAtEnd"/>
|
||||
<textarea v-else
|
||||
ref="input"
|
||||
class="overflow-hidden leading-5 inline-block outline-none px-0 py-1 border-0 border-solid border-y w-full border-t-transparent mb-[1px]"
|
||||
class="overflow-hidden leading-5 inline-block outline-none px-0 py-1 border-0 border-solid border-y w-full border-t-transparent mb-0.5"
|
||||
:class="{
|
||||
'inline-edit-placeholder text-sn-grey caret-black': isBlank,
|
||||
'border-sn-delete-red': error,
|
||||
|
@ -27,7 +26,6 @@
|
|||
:placeholder="placeholder"
|
||||
v-model="newValue"
|
||||
@keydown="handleKeypress"
|
||||
@paste="handlePaste"
|
||||
@blur="handleBlur"
|
||||
@keyup.escape="cancelEdit"
|
||||
@focus="setCaretAtEnd"/>
|
||||
|
@ -36,7 +34,7 @@
|
|||
v-else
|
||||
ref="view"
|
||||
class="grid sci-cursor-edit leading-5 border-0 py-1 outline-none inline-block border-solid border-y border-transparent"
|
||||
:class="{ 'text-sn-grey font-normal': isBlank }"
|
||||
:class="{ 'text-sn-grey font-normal': isBlank, 'whitespace-pre': !singleLine }"
|
||||
@click="enableEdit($event)"
|
||||
>
|
||||
<span :class="{'truncate': singleLine}" v-if="smartAnnotation" v-html="sa_value || placeholder" ></span>
|
||||
|
@ -151,6 +149,7 @@
|
|||
},
|
||||
handleBlur() {
|
||||
if ($('.atwho-view:visible').length) return;
|
||||
this.$emit('blur');
|
||||
if (this.allowBlank || !this.isBlank) {
|
||||
this.$nextTick(this.update);
|
||||
} else {
|
||||
|
@ -198,37 +197,6 @@
|
|||
this.newValue = this.value || '';
|
||||
this.$emit('editingDisabled');
|
||||
},
|
||||
handlePaste(e) {
|
||||
e.preventDefault();
|
||||
this.dirty = true;
|
||||
|
||||
const clipboardData = (e.clipboardData || window.clipboardData).getData("text");
|
||||
let lines = clipboardData.split(/[\n\r]/).filter((l) => l).map((l) => l.trim());
|
||||
|
||||
const selection = window.getSelection();
|
||||
if (!selection.rangeCount) return;
|
||||
|
||||
selection.deleteFromDocument();
|
||||
|
||||
let textNode = document.createTextNode(lines[0]);
|
||||
selection.getRangeAt(0).insertNode(textNode);
|
||||
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, textNode.length);
|
||||
range.setEnd(textNode, textNode.length);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.newValue = e.target.textContent;
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
// Handle multi-line paste
|
||||
if (this.multilinePaste && lines.length > 1) {
|
||||
this.$emit('multilinePaste', lines);
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleInput(e) {
|
||||
this.dirty = true;
|
||||
|
||||
|
@ -242,15 +210,16 @@
|
|||
handleKeypress(e) {
|
||||
if (e.key == 'Escape') {
|
||||
this.cancelEdit();
|
||||
} else if (e.key == 'Enter' && this.saveOnEnter) {
|
||||
} else if (e.key == 'Enter' && this.saveOnEnter && e.shiftKey == false) {
|
||||
e.preventDefault()
|
||||
this.update();
|
||||
this.update(e.key);
|
||||
} else {
|
||||
if (!this.error) this.$emit('error-cleared');
|
||||
this.dirty = true;
|
||||
}
|
||||
this.$emit('keypress', e)
|
||||
},
|
||||
update() {
|
||||
update(withKey = null) {
|
||||
this.refreshTexareaHeight();
|
||||
|
||||
if (!this.dirty && !this.isBlank) {
|
||||
|
@ -264,11 +233,13 @@
|
|||
|
||||
this.editing = false;
|
||||
this.$emit('editingDisabled');
|
||||
this.$emit('update', this.newValue);
|
||||
this.$emit('update', this.newValue, withKey);
|
||||
},
|
||||
refreshTexareaHeight() {
|
||||
if (this.editing && !this.singleLine) {
|
||||
this.$nextTick(() => {
|
||||
if (!this.$refs.input) return;
|
||||
|
||||
this.$refs.input.style.height = '0px';
|
||||
this.$refs.input.style.height = this.$refs.input.scrollHeight + 'px';
|
||||
});
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
class ChecklistItem < ApplicationRecord
|
||||
|
||||
attr_accessor :with_paragraphs
|
||||
|
||||
auto_strip_attributes :text, nullify: false
|
||||
validates :text,
|
||||
presence: true,
|
||||
|
@ -24,6 +27,45 @@ class ChecklistItem < ApplicationRecord
|
|||
after_save :touch_checklist
|
||||
after_touch :touch_checklist
|
||||
|
||||
def save_multiline!
|
||||
if with_paragraphs
|
||||
if new_record?
|
||||
original_position = position
|
||||
self.position = nil
|
||||
save!
|
||||
insert_at(original_position + 1)
|
||||
else
|
||||
save!
|
||||
end
|
||||
return [self]
|
||||
end
|
||||
|
||||
items = []
|
||||
if new_record?
|
||||
start_position = position
|
||||
text.split("\n").compact.each do |line|
|
||||
new_item = checklist.checklist_items.create!(text: line)
|
||||
new_item.insert_at(start_position + 1)
|
||||
start_position = new_item.position
|
||||
items.push(new_item)
|
||||
end
|
||||
else
|
||||
item = self
|
||||
text.split("\n").compact.each_with_index do |line, index|
|
||||
if index.zero?
|
||||
update!(text: line)
|
||||
items.push(self)
|
||||
else
|
||||
new_item = checklist.checklist_items.create!(text: line)
|
||||
new_item.insert_at(item.position + 1)
|
||||
item = new_item
|
||||
items.push(new_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
items
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def touch_checklist
|
||||
|
|
|
@ -6,7 +6,11 @@ class ChecklistItemSerializer < ActiveModel::Serializer
|
|||
include ApplicationHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
attributes :id, :text, :checked, :position, :urls, :sa_text
|
||||
attributes :id, :text, :checked, :position, :urls, :sa_text, :with_paragraphs
|
||||
|
||||
def with_paragraphs
|
||||
object.text.include?("\n")
|
||||
end
|
||||
|
||||
def sa_text
|
||||
@user = scope[:user] || @instance_options[:user]
|
||||
|
|
|
@ -16,12 +16,6 @@ class ChecklistSerializer < ActiveModel::Serializer
|
|||
:step
|
||||
end
|
||||
|
||||
def checklist_items
|
||||
object.checklist_items.map do |item|
|
||||
ChecklistItemSerializer.new(item, scope: { user: scope[:user] || @instance_options[:user] }).as_json
|
||||
end
|
||||
end
|
||||
|
||||
def sa_name
|
||||
@user = scope[:user] || @instance_options[:user]
|
||||
custom_auto_link(object.name,
|
||||
|
@ -34,6 +28,7 @@ class ChecklistSerializer < ActiveModel::Serializer
|
|||
return {} if object.destroyed? || !can_manage_step?(scope[:user] || @instance_options[:user], object.step)
|
||||
|
||||
{
|
||||
checklist_items_url: step_checklist_checklist_items_path(object.step, object),
|
||||
duplicate_url: duplicate_step_checklist_path(object.step, object),
|
||||
delete_url: step_checklist_path(object.step, object),
|
||||
update_url: step_checklist_path(object.step, object),
|
||||
|
|
|
@ -582,7 +582,7 @@ Rails.application.routes.draw do
|
|||
post :move
|
||||
post :duplicate
|
||||
end
|
||||
resources :checklist_items, controller: 'step_elements/checklist_items', only: %i(create update destroy) do
|
||||
resources :checklist_items, controller: 'step_elements/checklist_items', only: %i(index create update destroy) do
|
||||
patch :toggle, on: :member
|
||||
post :reorder, on: :collection
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue