Open oladhari opened 1 year ago
@gasman I do not why you ignore this serious issue by the way, the fact that we can not delete the comments (annotation) one by one is very bad for the UX of our clients I am trying to add manually by overriding the HTML through javascript but can not find yet a solution how to delete the specific annotation I added a designed button to click on it through the design here is my example:
<head>
<style>
.annotator-controls {
position: relative;
cursor: pointer;
font-size: 20px;
color: white !important; /* X color */
background-color: red; /* Button background */
padding: 5px;
border-radius: 50%; /* Make it a circle */
width: 30px; /* Set width and height to create a circular shape */
height: 30px;
display: flex;
align-items: center; /* Center the X vertically */
justify-content: center; /* Center the X horizontally */
font-weight: bold; /* Make the X bold */
transition: background-color 0.3s ease; /* Optional: Transition effect */
}
/* Add the X using the ::before pseudo-element */
.annotator-controls::before {
content: '×'; /* The X mark */
color: white;
}
/* Optional hover effect for better UX */
.annotator-controls:hover {
background-color: darkred; /* Darker red on hover */
}
</style>
</head>
<script>
const app = new annotator.App();
{% if allow_annotations %}
app.include(annotator.ui.main, {
viewerExtensions: [annotatorExt.viewerWithUsernames]
});
{% else %}
app.include(annotatorExt.viewerModeUi);
{% endif %}
app.include(annotator.storage.http, {
'prefix': "{% url 'wagtail_review:annotations_api_root' %}",
'urls': {
create: 'annotations/',
update: 'annotations/{id}/',
destroy: 'annotations/{id}/',
search: 'search/'
},
'headers': {
{% if allow_annotations %}
'X-CSRFToken': document.getElementById('wagtailreview-respond-form').csrfmiddlewaretoken.value,
{% endif %}
'X-WagtailReview-mode': '{{ mode }}',
'X-WagtailReview-reviewer': '{{ reviewer.id }}',
'X-WagtailReview-token': '{{ token }}',
}
});
app.start().then(function () {
app.annotations.load();
});
{% if allow_responses %}
document.addEventListener('DOMContentLoaded', function() {
function deleteAnnotation() {
// Assuming you have access to the annotator.js instance and its methods
// Call the annotator.js method to delete the annotation
console.log("it is clicked")
console.log(this)
}
// Function to inject text and add click event to the span
function injectTextAndAddEvent(span) {
span.addEventListener('click', deleteAnnotation); // Add click event to call delete function
}
document.querySelectorAll('.annotator-outer.annotator-viewer').forEach(parentDiv => {
parentDiv.addEventListener('mouseenter', function() {
// When the parent is hovered, find the span with class 'annotator-controls'
const span = parentDiv.querySelector('.annotator-controls');
if (span) {
injectTextAndAddEvent(span);
}
});
parentDiv.addEventListener('mouseleave', function() {
// You can optionally remove the click event or take another action when hover is removed
const span = parentDiv.querySelector('.annotator-controls');
if (span) {
span.removeEventListener('click', deleteAnnotation); // Optional clean-up
}
});
});
const form = document.getElementById('wagtailreview-respond-form');
form.style.display = 'none';
formShown = false;
{# Auto-update the form's hidden input when clicking on one of the buttons #}
form.querySelectorAll("button").forEach(button =>
button.addEventListener("click", (event) =>
document.querySelector("#id_result").value =
(event.currentTarget.id === "approve_text")
{# values from wagtail_review/models.py RESULT_CHOICES #}
? "approve" : "comment"
)
);
let button_close_modal=document.getElementById("close_modal");
let overlay = document.getElementById("my-overlay");
let button_respond_review=document.getElementById('wagtailreview-respond-button')
button_close_modal.addEventListener('click', function() {
if (formShown) {
form.style.display = 'none';
overlay.style.display = "none";
formShown = false;
}
})
window.onclick = function(event) {
if (event.target == overlay && formShown) {
form.style.display = 'none';
overlay.style.display = "none";
formShown = false;
}
}
button_respond_review.addEventListener('click', function() {
if (formShown) {
form.style.display = 'none';
overlay.style.display = "none";
formShown = false;
} else {
form.style.display = 'block';
overlay.style.display = "block";
if (document.querySelector(".annotator-hl")){
let approved = document.getElementById('approve_text')
let refused = document.getElementById('refuse_text')
approved.classList.add("hidden");
refused.classList.remove("hidden");
}
formShown = true;
}
return false;
});
});
// TODO: add error alert if approve and comment are not selected
{% endif %}
</script>
{% endif %}
here related question in stackoverflow: https://stackoverflow.com/q/79108407/5792426
@gasman here I update the code with this fix, but still can not access the API URL to delete the annotation: N.B: 158 is an id number of one of the annotations, I am trying to use it as test
// Function to delete the annotation by sending an API DELETE request
function deleteAnnotation(annotationId) {
// Use the correct API root (prefix) for Wagtail Review annotations
const prefix = app.annotations.store.options.prefix; // This is '/review/api/'
const deleteUrl = `${prefix}annotations/${annotationId}/`; // Construct the full delete URL
// Retrieve the CSRF token directly from app.annotations.store
const csrfToken = app.annotations.store.options.headers["X-CSRFToken"];
fetch(deleteUrl, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken, // Use the CSRF token from app.annotations.store
'Content-Type': 'application/json'
}
})
.then(response => {
if (response.ok) {
console.log(`Annotation ${annotationId} deleted successfully`);
} else {
console.error(`Failed to delete annotation ${annotationId}: ${response.status}`);
}
})
.catch(error => {
console.error('Error during deletion:', error);
});
}
// Function to inject text and add click event to the span
function injectTextAndAddEvent(span, annotationId) {
span.addEventListener('click', function() {
deleteAnnotation(annotationId); // Call delete function with the correct ID
});
}
document.querySelectorAll('.annotator-outer.annotator-viewer').forEach(parentDiv => {
parentDiv.addEventListener('mouseenter', function() {
// When the parent is hovered, find the span with class 'annotator-controls'
const span = parentDiv.querySelector('.annotator-controls');
if (span) {
// Pass the correct annotation ID to the function
const annotationId = 158; // Replace this with the correct ID dynamically
injectTextAndAddEvent(span, annotationId);
}
});
parentDiv.addEventListener('mouseleave', function() {
// You can optionally remove the click event or take another action when hover is removed
const span = parentDiv.querySelector('.annotator-controls');
if (span) {
span.removeEventListener('click', deleteAnnotation); // Optional clean-up
}
});
});
also I can access all annotation through this code :
app.annotations.query().then(response => {
// Now you have access to the results
const annotations = response.results;
// Log the annotations array to inspect it
console.log('Annotations:', annotations);
});
by the way we need to update the item view to become like this (so it can handle delete URL requests:
@never_cache
def item(request, id):
reviewer, mode = _check_reviewer_credentials(request)
if request.method == 'GET':
annotation = get_object_or_404(Annotation, id=id)
# only allow retrieving annotations within the same review as the current user's credentials
if reviewer.review != annotation.reviewer.review:
raise PermissionDenied
return JsonResponse(annotation.as_json_data())
elif request.method == 'DELETE':
annotation = get_object_or_404(Annotation, id=id)
# Check if the reviewer is allowed to delete the annotation
if reviewer.review != annotation.reviewer.review:
raise PermissionDenied
# If the reviewer has permission, delete the annotation
annotation.delete()
return JsonResponse({'status': 'Annotation deleted successfully'}, status=204)
else:
return HttpResponseNotAllowed(['GET', 'DELETE'], "Method not allowed")
@gasman the only challenge that I face now is how I can get the annotationId dynamically I almost resolved all, except this last step
almost resolved it:
document.addEventListener('DOMContentLoaded', function() {
const csrfToken = document.getElementById('wagtailreview-respond-form').csrfmiddlewaretoken.value;
// Function to remove the highlight from the text corresponding to the deleted annotation
function removeHighlight(annotationId) {
// Assuming the highlight has a specific class or attribute related to the annotation ID
const highlights = document.querySelectorAll(`[data-annotation-id="${annotationId}"]`);
highlights.forEach(function(highlight) {
highlight.classList.remove('annotator-hl'); // Option 1: Reset background color
// Option 2: Remove the highlight element entirely
// highlight.parentNode.removeChild(highlight);
});
}
// Function to delete the annotation by ID
// Function to delete the annotation by sending an API DELETE request
function deleteAnnotation(annotationId) {
const prefix = app.annotations.store.options.prefix;
const deleteUrl = `${prefix}annotations/${annotationId}/`;
// Retrieve necessary headers
const mode = app.annotations.store.options.headers["X-WagtailReview-mode"];
const reviewerId = app.annotations.store.options.headers["X-WagtailReview-reviewer"];
const token = app.annotations.store.options.headers["X-WagtailReview-token"];
fetch(deleteUrl, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
'X-WagtailReview-Mode': mode,
'X-WagtailReview-Reviewer': reviewerId,
'X-WagtailReview-Token': token,
'Content-Type': 'application/json'
}
})
.then(response => {
if (response.ok) {
console.log(`Annotation ${annotationId} deleted successfully`);
removeHighlight(annotationId); // Call this function to remove the highlight
} else {
console.error(`Failed to delete annotation ${annotationId}: ${response.status}`);
}
})
.catch(error => {
console.error('Error during deletion:', error);
});
}
// Function to inject text and add click event to the span
function injectTextAndAddEvent(span, annotationId) {
span.addEventListener('click', function() {
deleteAnnotation(annotationId); // Call delete function with the correct ID
});
}
document.querySelectorAll('.annotator-outer.annotator-viewer').forEach(parentDiv => {
parentDiv.addEventListener('mouseenter', function() {
// When the parent is hovered, find the span with class 'annotator-controls'
// Find the inner div inside the structure that contains the text
const textDiv = parentDiv.querySelector('ul li div');
// Exclude the reviewer name and only get the annotation text after the <p> tag
const annotationText = textDiv.childNodes[1].textContent.trim(); // Get only the second child node, which is the text
app.annotations.query().then(response => {
// Assign the results to the broader scope variable
annotations = response.results;
// Loop through the annotations and find the one that matches the text
const span = parentDiv.querySelector('.annotator-controls');
annotations.forEach(annotation => {
if (annotation.text === annotationText) { // Ensure 'annotationText' is defined before comparison
annotationId = annotation.id; // Assign the annotationId
}
});
if (span) {
// Pass the correct annotation ID to the function
injectTextAndAddEvent(span, annotationId);
}
});
});
parentDiv.addEventListener('mouseleave', function() {
// You can optionally remove the click event or take another action when hover is removed
const span = parentDiv.querySelector('.annotator-controls');
if (span) {
span.removeEventListener('click', deleteAnnotation); // Optional clean-up
}
});
});
})
@gasman I do not why you ignore this serious issue by the way, the fact that we can not delete the comments (annotation) one by one is very bad for the UX of our clients
Hi @oladhari - I don't remember seeing this issue before, but I probably skipped over it because your message never stated what the issue actually is. "Issue with mobile devices" could mean anything at all, ranging from "it is completely broken" to "the animation isn't very smooth". There are hundreds of open issues across all Wagtail projects, and if I need to do extra work (or research a new JS library) just to learn what the problem is, then this one will end up very low on my priority list.
Unfortunately I do not have the resources to keep track of the issue list for add-on packages in the same way that I would do for Wagtail itself.
Chasing me up simultaneously on Github, Slack, email and Stack Overflow is not appropriate, and will not get this addressed any faster.
@gasman I do not why you ignore this serious issue by the way, the fact that we can not delete the comments (annotation) one by one is very bad for the UX of our clients
Hi @oladhari - I don't remember seeing this issue before, but I probably skipped over it because your message never stated what the issue actually is. "Issue with mobile devices" could mean anything at all, ranging from "it is completely broken" to "the animation isn't very smooth". There are hundreds of open issues across all Wagtail projects, and if I need to do extra work (or research a new JS library) just to learn what the problem is, then this one will end up very low on my priority list.
Unfortunately I do not have the resources to keep track of the issue list for add-on packages in the same way that I would do for Wagtail itself.
Chasing me up simultaneously on Github, Slack, email and Stack Overflow is not appropriate, and will not get this addressed any faster.
Thank you very much for your reply and sorry for the harrassement
I was little bit frustrated, I was able to handle the delete functionality as you see in the code above and you can test it, if you have time of course
need some changes in the view of the file annotations_api.py
if you see that is a good change and make the experience of using wagtail review more better, I can do a PR to handle these changes
have a good day
the best solution that I suggest is to replace
Annotator.js
package in wagtail-review byRecogito.js
package which can support both deleting annotation one by one and also using on mobile and tablet