This commit is contained in:
j264415 2024-05-03 10:59:54 +01:00 committed by GitHub
commit 68ef0103d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 188 additions and 7 deletions

View File

@ -39,7 +39,7 @@ class HTMLCategory {
*/
toHtml() {
const catName = "cat" + this.name.replace(/[\s/\-:_]/g, "");
let html = `<div class="panel category">
let html = `<div class="panel category" tabIndex="0">
<a class="category-title" data-toggle="collapse" data-target="#${catName}">
${this.name}
</a>

View File

@ -46,20 +46,20 @@ class HTMLOperation {
* @returns {string}
*/
toStubHtml(removeIcon) {
let html = "<li class='operation'";
let html = "<li tabIndex='0' class='operation'";
if (this.description) {
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
html += ` data-container='body' data-toggle='popover' data-placement='right'
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover focus'
data-boundary='viewport'`;
}
html += ">" + this.name;
if (removeIcon) {
html += "<i class='material-icons remove-icon op-icon'>delete</i>";
html += "<i class='material-icons remove-icon op-icon' tabindex='0'>delete</i>";
}
html += "</li>";

View File

@ -148,6 +148,9 @@ class Manager {
this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops);
document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops));
document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops));
document.getElementById("edit-favourites").addEventListener("keydown", this.ops.editFavouritesKeyPress.bind(this.ops));
document.getElementById("categories").addEventListener("keydown", this.ops.onKeyPress.bind(this.ops));
this.addDynamicListener(".op-list li.operation", "keydown", this.ops.keyboardPopulateRecipe.bind(this.ops));
document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops));
this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);

View File

@ -539,7 +539,7 @@
<div class="modal-body" id="favourites-body">
<ul>
<li><span style="font-weight: bold">To add:</span> drag the operation over the favourites category and drop it</li>
<li><span style="font-weight: bold">To reorder:</span> drag up and down in the list below</li>
<li><span style="font-weight: bold">To reorder:</span> drag up and down in the list below or focus on operation and press Ctrl + Up/Down Arrow to reorder using keyboard</li>
<li><span style="font-weight: bold">To remove:</span> hit the delete button or drag out of the list below</li>
</ul>
<br>

View File

@ -8,7 +8,6 @@ import HTMLOperation from "../HTMLOperation.mjs";
import Sortable from "sortablejs";
import {fuzzyMatch, calcMatchRanges} from "../../core/lib/FuzzyMatch.mjs";
/**
* Waiter to handle events related to the operations.
*/
@ -237,9 +236,16 @@ class OperationsWaiter {
}
const editFavouritesList = document.getElementById("edit-favourites-list");
const editFavouritesListElements = editFavouritesList.getElementsByTagName("li");
editFavouritesList.innerHTML = html;
this.removeIntent = false;
for (let i = 0; i < editFavouritesListElements.length; i++) {
editFavouritesListElements[i].setAttribute("tabindex", "0");
editFavouritesListElements[i].addEventListener("keydown", this.ArrowNavFavourites.bind(this), false);
editFavouritesListElements[i].firstElementChild.addEventListener("keydown", this.deleteFavourite.bind(this), false);
}
const editableList = Sortable.create(editFavouritesList, {
filter: ".remove-icon",
onFilter: function (evt) {
@ -270,6 +276,66 @@ class OperationsWaiter {
}
/**
* Handler for navigation key press events.
* Navigates through the favourites list and corresponding delete buttons.
* Move favourites elements up and down with Ctrl + Arrow keys to imitate drag and drop mouse functionality.
*/
ArrowNavFavourites(event) {
const currentElement = event.target;
const nextElement = currentElement.nextElementSibling;
const prevElement = currentElement.previousElementSibling;
const favouritesList = currentElement.parentNode;
event.preventDefault();
event.stopPropagation();
if (event.key === "ArrowDown" && !event.ctrlKey) {
if (nextElement === null) {
currentElement.parentElement.firstElementChild.focus();
} else {
nextElement.focus();
}
} else if (event.key === "ArrowUp" && !event.ctrlKey) {
if (prevElement === null) {
currentElement.parentElement.lastElementChild.focus();
} else {
prevElement.focus();
}
} else if (event.key === "Tab") {
currentElement.parentElement.closest(".modal-body").nextElementSibling.getElementsByTagName("Button")[0].focus();
} else if (event.key === "ArrowRight") {
if (currentElement.firstElementChild !== null) {
currentElement.firstElementChild.focus();
}
} else if (event.key === "ArrowLeft" && (currentElement.classList.contains("remove-icon"))) {
currentElement.parentElement.focus();
} else if (event.ctrlKey && event.key === "ArrowDown") {
if (nextElement === null) {
favouritesList.insertBefore(currentElement, currentElement.parentElement.firstElementChild);
} else {
favouritesList.insertBefore(currentElement, nextElement.nextElementSibling);
}
currentElement.focus();
} else if (event.ctrlKey && event.key === "ArrowUp") {
favouritesList.insertBefore(currentElement, prevElement);
currentElement.focus();
}
}
/**
* Handler for delete favourites keydown events.
* delete the selected favourite from the list.
*/
deleteFavourite(event) {
if (event.key === "Enter" || event.key === " ") {
const el = event.target;
if (el && el.parentNode) {
el.parentNode.remove();
}
}
}
/**
* Handler for save favourites click events.
* Saves the selected favourites and reloads them.
@ -284,7 +350,6 @@ class OperationsWaiter {
this.manager.recipe.initialiseOperationDragNDrop();
}
/**
* Handler for reset favourites click events.
* Resets favourites to their defaults.
@ -293,6 +358,119 @@ class OperationsWaiter {
this.app.resetFavourites();
}
/**
* Handler that allows users to open favourite modal by "Enter/Space".
* This codes mimics editFavouritesClick event handler.
* @param {Event} ev
*/
editFavouritesKeyPress(ev) {
if (ev.key === "Enter" || ev.key === "Space" || ev.key === " ") {
ev.preventDefault();
ev.stopPropagation();
const favCat = this.app.categories.filter(function (c) {
return c.name === "Favourites";
})[0];
let html = "";
for (let i = 0; i < favCat.ops.length; i++) {
const opName = favCat.ops[i];
const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
html += operation.toStubHtml(true);
}
const editFavouritesList = document.getElementById("edit-favourites-list");
const editFavouritesListElements = editFavouritesList.getElementsByTagName("li");
editFavouritesList.innerHTML = html;
this.removeIntent = false;
for (let i = 0; i < editFavouritesListElements.length; i++) {
editFavouritesListElements[i].setAttribute("tabindex", "0");
editFavouritesListElements[i].addEventListener("keydown", this.ArrowNavFavourites.bind(this), false);
editFavouritesListElements[i].firstElementChild.addEventListener("keydown", this.deleteFavourite.bind(this), false);
}
const editableList = Sortable.create(editFavouritesList, {
filter: ".remove-icon",
onFilter: function (evt) {
const el = editableList.closest(evt.item);
if (el && el.parentNode) {
$(el).popover("dispose");
el.parentNode.removeChild(el);
}
},
onEnd: function (evt) {
if (this.removeIntent) {
$(evt.item).popover("dispose");
evt.item.remove();
}
}.bind(this),
});
$("#edit-favourites-list [data-toggle=popover]").popover();
$("#favourites-modal").modal();
}
}
/**
* Handler for on key press events.
* Get the children of categories and add event listener to them.
*/
onKeyPress() {
const cat = document.getElementById("categories");
for (let i = 0; i < cat.children.length; i++) {
cat.children[i].addEventListener("keydown", this.keyboardEventHandler, false);
}
}
/**
* Handler for keyboard enter/space events.
* Uses "Enter" or "Space" to mimic the click function and open the operations panels .
* @param {Event} ev
*/
keyboardEventHandler(ev) {
if (ev.key === "Enter" || ev.key === "Space" || ev.key === " ") {
ev.preventDefault();
for (let i = 0; i < ev.target.childNodes.length; i++) {
const targetChild = ev.target.childNodes[i].classList;
if (targetChild !== undefined && targetChild.value.includes("panel-collapse collapse")) {
if (!targetChild.contains("show")) {
targetChild.add("show");
} else if (targetChild.contains("show")) {
targetChild.remove("show");
}
}
}
}
}
/**
* Handler to populate recipe.
* Get the children of op-list and add event listener to them.
*/
operationPopulateRecipe() {
const cat = document.querySelectorAll(".op-list li.operation");
for (let i = 0; i < cat.children.length; i++) {
cat.children[i].addEventListener("keydown", this.keyboardPopulateRecipe, false);
}
}
/**
* Handler to add operators to recipe with keyboard.
* Uses keyboard shortcut "CTRl + Enter" to mimic operationDblClick handler function
* @param {Event} ev
*/
keyboardPopulateRecipe(ev) {
if (ev.ctrlKey && ev.key === "Enter") {
const li = ev.target;
this.manager.recipe.addOperation(li.textContent);
}
}
}
export default OperationsWaiter;