- Add PUT /anime/{key} endpoint for updating anime key, tmdb_id, tvdb_id
- Add NFO diagnostics and repair endpoints (GET/POST /nfo/diagnostics)
- Add edit modal UI with context menu integration
- Add frontend JS modules for context-menu and edit-modal
- Add comprehensive tests for edit, rename, and NFO repair flows
124 lines
3.4 KiB
JavaScript
124 lines
3.4 KiB
JavaScript
/**
|
|
* AniWorld - Context Menu Component
|
|
*
|
|
* Right-click context menu for anime series cards.
|
|
* Provides quick access to edit metadata.
|
|
*
|
|
* Dependencies: ui-utils.js, edit-modal.js
|
|
*/
|
|
|
|
var AniWorld = window.AniWorld || {};
|
|
|
|
AniWorld.ContextMenu = (function() {
|
|
'use strict';
|
|
|
|
let menuElement = null;
|
|
let currentSeriesKey = null;
|
|
|
|
/**
|
|
* Initialize the context menu system.
|
|
* Attaches global dismissal listeners.
|
|
*/
|
|
function init() {
|
|
// Dismiss on click outside
|
|
document.addEventListener('click', function(e) {
|
|
if (menuElement && !menuElement.contains(e.target)) {
|
|
hide();
|
|
}
|
|
});
|
|
|
|
// Dismiss on Escape
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
hide();
|
|
}
|
|
});
|
|
|
|
// Dismiss on scroll or resize
|
|
window.addEventListener('scroll', hide, true);
|
|
window.addEventListener('resize', hide);
|
|
|
|
// Attach context menu via event delegation on the series grid
|
|
const grid = document.getElementById('series-grid');
|
|
if (grid) {
|
|
grid.addEventListener('contextmenu', function(e) {
|
|
const card = e.target.closest('.series-card');
|
|
if (card) {
|
|
e.preventDefault();
|
|
const key = card.getAttribute('data-key');
|
|
if (key) {
|
|
show(e, key);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show context menu at cursor position.
|
|
* @param {MouseEvent} event - The contextmenu event
|
|
* @param {string} seriesKey - The series key to operate on
|
|
*/
|
|
function show(event, seriesKey) {
|
|
hide(); // Remove any existing menu first
|
|
|
|
currentSeriesKey = seriesKey;
|
|
|
|
menuElement = document.createElement('div');
|
|
menuElement.className = 'context-menu';
|
|
menuElement.innerHTML = `
|
|
<div class="context-menu-item" data-action="edit">
|
|
<i class="fa-solid fa-pen-to-square"></i>
|
|
<span>Edit Metadata</span>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(menuElement);
|
|
|
|
// Position within viewport bounds
|
|
const x = event.clientX;
|
|
const y = event.clientY;
|
|
const menuRect = menuElement.getBoundingClientRect();
|
|
const viewportWidth = window.innerWidth;
|
|
const viewportHeight = window.innerHeight;
|
|
|
|
let posX = x;
|
|
let posY = y;
|
|
|
|
if (x + menuRect.width > viewportWidth) {
|
|
posX = viewportWidth - menuRect.width - 8;
|
|
}
|
|
if (y + menuRect.height > viewportHeight) {
|
|
posY = viewportHeight - menuRect.height - 8;
|
|
}
|
|
|
|
menuElement.style.left = posX + 'px';
|
|
menuElement.style.top = posY + 'px';
|
|
|
|
// Attach action handlers
|
|
menuElement.querySelector('[data-action="edit"]').addEventListener('click', function() {
|
|
hide();
|
|
if (AniWorld.EditModal) {
|
|
AniWorld.EditModal.open(currentSeriesKey);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hide and remove the context menu from DOM.
|
|
*/
|
|
function hide() {
|
|
if (menuElement) {
|
|
menuElement.remove();
|
|
menuElement = null;
|
|
currentSeriesKey = null;
|
|
}
|
|
}
|
|
|
|
return {
|
|
init: init,
|
|
show: show,
|
|
hide: hide
|
|
};
|
|
})();
|