Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with mobile devices and deleting annotation one by one #51

Open
oladhari opened this issue Feb 14, 2023 · 9 comments
Open

Issue with mobile devices and deleting annotation one by one #51

oladhari opened this issue Feb 14, 2023 · 9 comments

Comments

@oladhari
Copy link

the best solution that I suggest is to replace Annotator.js package in wagtail-review by Recogito.js package which can support both deleting annotation one by one and also using on mobile and tablet

@oladhari
Copy link
Author

oladhari commented Oct 21, 2024

@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 %}

@oladhari
Copy link
Author

Screencast.from.10-21-2024.09.51.14.AM.webm

@oladhari
Copy link
Author

here related question in stackoverflow: https://stackoverflow.com/q/79108407/5792426

@oladhari
Copy link
Author

oladhari commented Oct 21, 2024

@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);
                
                });
                ```

@oladhari
Copy link
Author

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")

@oladhari
Copy link
Author

@gasman
the only challenge that I face now is how I can get the annotationId dynamically
I almost resolved all, except this last step

@oladhari
Copy link
Author

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
Copy link
Contributor

gasman commented Oct 21, 2024

@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.

@oladhari
Copy link
Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants