Merge branch 'develop' into kostia/feat/drag_and_drop

This commit is contained in:
Kostiantyn Stoliarskyi 2023-05-31 14:15:28 +03:00
commit 3e36bf8df4
12 changed files with 254 additions and 36 deletions

View File

@ -9,7 +9,11 @@ from .contributor import (
)
from .collection import CreateCollectionForm, EditCollectionForm
from .section import CreateSectionForm, EditSectionForm
from .interpretation import CreateInterpretationForm, EditInterpretationForm
from .interpretation import (
CreateInterpretationForm,
EditInterpretationForm,
DeleteInterpretationForm,
)
from .comment import CreateCommentForm
from .vote import VoteForm
from .comment import CreateCommentForm, DeleteCommentForm, EditCommentForm

View File

@ -27,3 +27,8 @@ class CreateInterpretationForm(BaseInterpretationForm):
class EditInterpretationForm(BaseInterpretationForm):
interpretation_id = StringField("Interpretation ID", [DataRequired()])
submit = SubmitField("Edit")
class DeleteInterpretationForm(FlaskForm):
interpretation_id = StringField("Interpretation ID", [DataRequired()])
submit = SubmitField("Delete")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,8 @@
{% if current_user.is_authenticated %}
{% include 'book/modals/approve_interpretation_modal.html' %}
{% include 'book/modals/edit_interpretation_modal.html' %}
{% include 'book/modals/delete_interpretation_modal.html' %}
{% block right_sidebar %}
{% endblock %}
{% endif %}
@ -129,6 +131,31 @@
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm13.36-1.814a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" clip-rule="evenodd" />
</svg>
</div>
<!--Edit & Delete interpretation-->
<div class="relative mt-1">
<button id="callEditInterpretationModal" data-popover-target="popover-edit" data-edit-interpretation-id="{{interpretation.id}}" data-edit-interpretation-text="{{interpretation.text}}" type="button" data-modal-target="edit_interpretation_modal" data-modal-toggle="edit_interpretation_modal" class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /> </svg>
</button>
<div data-popover id="popover-edit" role="tooltip" class="absolute z-10 invisible inline-block w-64 text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
<div class="px-3 py-2">
<p>Edit this interpretation</p>
</div>
<div data-popper-arrow></div>
</div>
</div>
<div class="relative mt-1">
<button id="callDeleteInterpretationModal" data-popover-target="popover-delete" data-interpretation-id="{{interpretation.id}}" type="button" data-modal-target="delete_interpretation_modal" data-modal-toggle="delete_interpretation_modal" class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /> </svg>
</button>
<div data-popover id="popover-delete" role="tooltip" class="absolute z-10 invisible inline-block w-64 text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
<div class="px-3 py-2">
<p>Delete this interpretation</p>
</div>
<div data-popper-arrow></div>
</div>
</div>
{% endif %}
</div>
<!-- prettier-ignore -->

View File

@ -1,17 +1,19 @@
<!-- prettier-ignore-->
<div id="delete_interpretation_modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div id="delete-interpretation-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form
action="{{ url_for('book.interpretation_delete', book_id=book.id, interpretation_id=interpretation.id) }}"
id="delete_interpretation_modal_form"
action="{{ url_for('book.interpretation_delete', book_id=book.id,interpretation_id=0) }}"
method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
{{ form_hidden_tag() }}
<!-- Modal header -->
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Delete Interpretation </h3>
<button id="modalAddCloseButton" data-modal-hide="delete_interpretation_modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
<button id="modalDeleteInterpretationCloseButton" data-modal-hide="delete_interpretation_modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
</div>
<!-- Modal body -->
<input type="hidden" name="interpretation_id" id="delete_interpretation_modal_interpretation_id" value="" />
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">

View File

@ -3,25 +3,26 @@
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form
action="{{ url_for('book.interpretation_edit', book_id=book.id, interpretation_id=interpretation.id) }}"
id="edit_interpretation_modal_form"
action="{{ url_for('book.interpretation_edit', book_id=book.id, interpretation_id=0) }}"
method="post"
class="prevent-submit-on-enter relative bg-white rounded-lg shadow dark:bg-gray-700"
>
{{ form_hidden_tag() }}
<input type="hidden" name="interpretation_id" id="interpretation_id" value="{{interpretation.id}}" />
<input type="hidden" name="text" id="interpretation-text-input" value="{{interpretation.text}}" />
<input type="hidden" name="interpretation_id" id="edit_interpretation_modal_interpretation_id" value="" />
<!-- Modal header -->
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Edit Interpretation </h3>
<button id="modalAddCloseButton" data-modal-hide="edit_interpretation_modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
<button id="modalEditInterpretationCloseButton" data-modal-hide="edit_interpretation_modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
</div>
<!-- Modal body -->
<input type="hidden" name="text" id="edit-interpretation-text-input" value="" />
<div class="p-5">
<div class="w-full max-w-6xl mx-auto rounded-xl bg-gray-50 dark:bg-gray-600 shadow-lg text-white-900">
<div class="overflow-hidden rounded-md bg-gray-50 [&>*]:dark:bg-gray-600 text-black [&>*]:!border-none [&>*]:!stroke-black dark:text-white dark:[&>*]:!stroke-white">
<div id="interpretation-text" class="quill-editor dark:text-white h-40">
{{ display_tags(interpretation.text)|safe }}
<div id="edit-interpretation-text" class="quill-editor dark:text-white h-40">
</div>
</div>
</div>

View File

@ -109,16 +109,29 @@ def interpretation_edit(
book_id: int,
interpretation_id: int,
):
interpretation: m.Interpretation = db.session.get(
m.Interpretation, interpretation_id
)
form = f.EditInterpretationForm()
redirect_url = url_for(
"book.qa_view", book_id=book_id, interpretation_id=interpretation_id
)
if form.validate_on_submit():
text = form.text.data
interpretation_id = form.interpretation_id.data
interpretation: m.Interpretation = db.session.get(
m.Interpretation, interpretation_id
)
redirect_url = url_for(
"book.interpretation_view",
book_id=book_id,
section_id=interpretation.section_id,
)
if not interpretation or interpretation.is_deleted:
log(log.WARNING, "Interpretation with id [%s] not found", interpretation_id)
flash("Interpretation not found", "danger")
return redirect(
url_for(
"book.interpretation_view",
book_id=book_id,
section_id=interpretation.section_id,
)
)
plain_text = clean_html(text).lower()
tags = current_app.config["TAG_REGEX"].findall(text)
for tag in tags:
@ -149,18 +162,40 @@ def interpretation_edit(
)
@register_book_verify_route(bp.name)
@login_required
def interpretation_delete(book_id: int, interpretation_id: int):
def interpretation_delete(
book_id: int,
interpretation_id: int,
):
form = f.DeleteInterpretationForm()
interpretation_id = form.interpretation_id.data
interpretation: m.Interpretation = db.session.get(
m.Interpretation, interpretation_id
)
if not interpretation or interpretation.is_deleted:
log(log.WARNING, "Interpretation with id [%s] not found", interpretation_id)
flash("Interpretation not found", "danger")
return redirect(
url_for(
"book.interpretation_view",
book_id=book_id,
section_id=interpretation.section_id,
)
)
form = f.DeleteInterpretationForm()
if form.validate_on_submit():
interpretation.is_deleted = True
delete_nested_interpretation_entities(interpretation)
log(log.INFO, "Delete interpretation [%s]", interpretation)
interpretation.save()
interpretation.is_deleted = True
delete_nested_interpretation_entities(interpretation)
log(log.INFO, "Delete interpretation [%s]", interpretation)
interpretation.save()
flash("Success!", "success")
flash("Success!", "success")
return redirect(
url_for(
"book.interpretation_view",
book_id=book_id,
section_id=interpretation.section_id,
)
)
return redirect(
url_for(
"book.collection_view",

View File

@ -0,0 +1,64 @@
import {Modal} from 'flowbite';
import type {ModalOptions, ModalInterface} from 'flowbite';
export function deleteInterpretation() {
const deleteInterpretationModal: HTMLElement = document.querySelector(
'#delete-interpretation-modal',
);
const deleteInterpretationModalBtns = document.querySelectorAll(
'#callDeleteInterpretationModal',
);
const interpretationIdInDeleteInterpretationModal: HTMLInputElement =
document.querySelector('#delete_interpretation_modal_interpretation_id');
const deleteInterpretationForm: HTMLFormElement = document.querySelector(
'#delete_interpretation_modal_form',
);
if (
deleteInterpretationModal &&
deleteInterpretationModalBtns &&
interpretationIdInDeleteInterpretationModal &&
deleteInterpretationForm
) {
const defaultActionPath = deleteInterpretationForm.getAttribute('action');
const deleteModalCloseBtn = document.querySelector(
'#modalDeleteInterpretationCloseButton',
);
if (deleteModalCloseBtn) {
deleteModalCloseBtn.addEventListener('click', () => {
interpretationDeleteModal.hide();
});
}
deleteInterpretationModalBtns.forEach(btn =>
btn.addEventListener('click', () => {
const interpretationId = btn.getAttribute('data-interpretation-id');
interpretationIdInDeleteInterpretationModal.value = interpretationId;
let newActionPath: string = '';
newActionPath = defaultActionPath.replace(
'0/interpretation_delete',
`${interpretationId}/interpretation_delete`,
);
deleteInterpretationForm.setAttribute('action', `${newActionPath}`);
interpretationDeleteModal.show();
}),
);
const modalOptions: ModalOptions = {
placement: 'bottom-right',
closable: true,
onHide: () => {
deleteInterpretationForm.setAttribute('action', '');
},
onShow: () => {},
onToggle: () => {},
};
const interpretationDeleteModal: ModalInterface = new Modal(
deleteInterpretationModal,
modalOptions,
);
}
}

View File

@ -0,0 +1,75 @@
import {Modal} from 'flowbite';
import type {ModalOptions, ModalInterface} from 'flowbite';
export function editInterpretations() {
const editInterpretationModal: HTMLElement = document.querySelector(
'#edit_interpretation_modal',
);
const editInterpretationModalBtns = document.querySelectorAll(
'#callEditInterpretationModal',
);
const interpretationIdInEditInterpretationModal: HTMLInputElement =
document.querySelector('#edit_interpretation_modal_interpretation_id');
const interpretationTextInEditInterpretationModal: HTMLInputElement =
document.querySelector('#edit-interpretation-text-input');
const editInterpretationForm: HTMLFormElement = document.querySelector(
'#edit_interpretation_modal_form',
);
const editInterpretationTextQuillOnModal: HTMLInputElement =
document.querySelector('#edit-interpretation-text');
// edit
const modalOptions: ModalOptions = {
placement: 'bottom-right',
closable: true,
onHide: () => {
editInterpretationForm.setAttribute('action', '');
},
onShow: () => {},
onToggle: () => {},
};
const interpretationEditModal: ModalInterface = new Modal(
editInterpretationModal,
modalOptions,
);
if (
editInterpretationModal &&
editInterpretationModalBtns &&
interpretationIdInEditInterpretationModal &&
interpretationTextInEditInterpretationModal &&
editInterpretationForm &&
editInterpretationTextQuillOnModal
) {
const defaultActionPath = editInterpretationForm.getAttribute('action');
const editModalCloseBtn = document.querySelector(
'#modalEditInterpretationCloseButton',
);
if (editModalCloseBtn) {
editModalCloseBtn.addEventListener('click', () => {
interpretationEditModal.hide();
});
}
editInterpretationModalBtns.forEach(btn =>
btn.addEventListener('click', () => {
const interpretationId = btn.getAttribute(
'data-edit-interpretation-id',
);
interpretationIdInEditInterpretationModal.value = interpretationId;
const interpretationText = btn.getAttribute(
'data-edit-interpretation-text',
);
interpretationTextInEditInterpretationModal.value = interpretationText;
editInterpretationTextQuillOnModal.innerHTML = interpretationText;
let newActionPath: string = '';
newActionPath = defaultActionPath.replace(
'0/interpretation_edit',
`${interpretationId}/interpretation_edit`,
);
editInterpretationForm.setAttribute('action', `${newActionPath}`);
interpretationEditModal.show();
}),
);
}
}

View File

@ -26,6 +26,8 @@ import {quickSearch} from './quickSearch';
import {flash} from './flash';
import {slashSearch} from './slashSearch';
import {initDnD} from './drag_and_drop';
import {editInterpretations} from './editInterpretations';
import {deleteInterpretation} from './deleteInterpretation';
initQuillReadOnly();
initBooks();
@ -55,3 +57,5 @@ quickSearch();
flash();
slashSearch();
initDnD();
editInterpretations();
deleteInterpretation();

View File

@ -886,7 +886,6 @@ def test_crud_interpretation(client: FlaskClient, runner: FlaskCliRunner):
section_id=section_in_collection.id
).first()
new_label = "Test Interpretation #1 Label(edited)"
new_text = "Test Interpretation #1 Text(edited)"
response: Response = client.post(
@ -908,7 +907,7 @@ def test_crud_interpretation(client: FlaskClient, runner: FlaskCliRunner):
response: Response = client.post(
f"/book/{book.id}/999/edit_interpretation",
data=dict(
interpretation_id=interpretation.id,
interpretation_id="999",
text=new_text,
),
follow_redirects=True,
@ -928,6 +927,7 @@ def test_crud_interpretation(client: FlaskClient, runner: FlaskCliRunner):
(
f"/book/{book.id}/{section_in_subcollection.interpretations[0].id}/delete_interpretation"
),
data=dict(interpretation_id=section_in_subcollection.interpretations[0].id),
follow_redirects=True,
)
@ -944,17 +944,10 @@ def test_crud_interpretation(client: FlaskClient, runner: FlaskCliRunner):
(
f"/book/{book.id}/{section_in_collection.interpretations[0].id}/delete_interpretation"
),
data=dict(interpretation_id=section_in_subcollection.interpretations[0].id),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
deleted_interpretation: m.Interpretation = db.session.get(
m.Interpretation, section_in_collection.interpretations[0].id
)
assert deleted_interpretation.is_deleted
check_if_nested_interpretation_entities_is_deleted(deleted_interpretation)
def test_crud_comment(client: FlaskClient, runner: FlaskCliRunner):