mirror of
https://github.com/gchq/CyberChef
synced 2025-03-14 05:46:56 +00:00
Add initial support for screenreaders in search
This commit is contained in:
parent
18159ce806
commit
bdeec065bc
3 changed files with 39 additions and 16 deletions
src/web
|
@ -43,17 +43,23 @@ class HTMLOperation {
|
||||||
/**
|
/**
|
||||||
* Renders the operation in HTML as a stub operation with no ingredients.
|
* Renders the operation in HTML as a stub operation with no ingredients.
|
||||||
*
|
*
|
||||||
|
* @param {boolean} removeIcon - show icon for removing operation
|
||||||
|
* @param {string} elementId - element ID for aria usage
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
toStubHtml(removeIcon) {
|
toStubHtml(removeIcon = false, elementId = null) {
|
||||||
let html = "<li class='operation'";
|
let html = "<li class='operation'";
|
||||||
|
|
||||||
|
if (elementId) {
|
||||||
|
html += ` id='${elementId}'`;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.description) {
|
if (this.description) {
|
||||||
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
||||||
|
|
||||||
html += ` data-container='body' data-toggle='popover' data-placement='right'
|
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'
|
||||||
data-boundary='viewport'`;
|
data-boundary='viewport' role='button'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += ">" + this.name;
|
html += ">" + this.name;
|
||||||
|
|
|
@ -173,7 +173,7 @@
|
||||||
Operations
|
Operations
|
||||||
<span class="op-count"></span>
|
<span class="op-count"></span>
|
||||||
</div>
|
</div>
|
||||||
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
|
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>" aria-owns="search-results">
|
||||||
<ul id="search-results" class="op-list"></ul>
|
<ul id="search-results" class="op-list"></ul>
|
||||||
<div id="categories" class="panel-group no-select"></div>
|
<div id="categories" class="panel-group no-select"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,17 +28,16 @@ class OperationsWaiter {
|
||||||
this.removeIntent = false;
|
this.removeIntent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for search events.
|
* Handler for search events.
|
||||||
* Finds operations which match the given search term and displays them under the search box.
|
* Finds operations which match the given search term and displays them under the search box.
|
||||||
*
|
*
|
||||||
* @param {event} e
|
* @param {KeyboardEvent | ClipboardEvent | Event} e
|
||||||
*/
|
*/
|
||||||
searchOperations(e) {
|
searchOperations(e) {
|
||||||
let ops, selected;
|
let ops, selected;
|
||||||
|
|
||||||
if (e.type === "search" || e.keyCode === 13) { // Search or Return
|
if ((e.type === "search" && e.target.value !== "") || e.keyCode === 13) { // Search (non-empty) or Return
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ops = document.querySelectorAll("#search-results li");
|
ops = document.querySelectorAll("#search-results li");
|
||||||
if (ops.length) {
|
if (ops.length) {
|
||||||
|
@ -49,27 +48,43 @@ class OperationsWaiter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the operation element with the correct attributes when selected
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
*/
|
||||||
|
const _selectOperation = (element) => {
|
||||||
|
element.classList.add("selected-op");
|
||||||
|
element.scrollIntoView({block: "nearest"});
|
||||||
|
$(element).popover("show");
|
||||||
|
e.target.setAttribute("aria-activedescendant", element.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the operation element with the correct attributes when deselected
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
*/
|
||||||
|
const _deselectOperation = (element) => {
|
||||||
|
element.classList.remove("selected-op");
|
||||||
|
$(element).popover("hide");
|
||||||
|
};
|
||||||
|
|
||||||
if (e.keyCode === 40) { // Down
|
if (e.keyCode === 40) { // Down
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ops = document.querySelectorAll("#search-results li");
|
ops = document.querySelectorAll("#search-results li");
|
||||||
if (ops.length) {
|
if (ops.length) {
|
||||||
selected = this.getSelectedOp(ops);
|
selected = this.getSelectedOp(ops);
|
||||||
if (selected > -1) {
|
if (selected > -1) _deselectOperation(ops[selected]);
|
||||||
ops[selected].classList.remove("selected-op");
|
|
||||||
}
|
|
||||||
if (selected === ops.length-1) selected = -1;
|
if (selected === ops.length-1) selected = -1;
|
||||||
ops[selected+1].classList.add("selected-op");
|
_selectOperation(ops[selected+1]);
|
||||||
}
|
}
|
||||||
} else if (e.keyCode === 38) { // Up
|
} else if (e.keyCode === 38) { // Up
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ops = document.querySelectorAll("#search-results li");
|
ops = document.querySelectorAll("#search-results li");
|
||||||
if (ops.length) {
|
if (ops.length) {
|
||||||
selected = this.getSelectedOp(ops);
|
selected = this.getSelectedOp(ops);
|
||||||
if (selected > -1) {
|
if (selected > -1) _deselectOperation(ops[selected]);
|
||||||
ops[selected].classList.remove("selected-op");
|
|
||||||
}
|
|
||||||
if (selected === 0) selected = ops.length;
|
if (selected === 0) selected = ops.length;
|
||||||
ops[selected-1].classList.add("selected-op");
|
_selectOperation(ops[selected-1]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const searchResultsEl = document.getElementById("search-results");
|
const searchResultsEl = document.getElementById("search-results");
|
||||||
|
@ -83,11 +98,13 @@ class OperationsWaiter {
|
||||||
searchResultsEl.removeChild(searchResultsEl.firstChild);
|
searchResultsEl.removeChild(searchResultsEl.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.querySelector("#search").removeAttribute("aria-activedescendant");
|
||||||
|
|
||||||
$("#categories .show").collapse("hide");
|
$("#categories .show").collapse("hide");
|
||||||
if (str) {
|
if (str) {
|
||||||
const matchedOps = this.filterOperations(str, true);
|
const matchedOps = this.filterOperations(str, true);
|
||||||
const matchedOpsHtml = matchedOps
|
const matchedOpsHtml = matchedOps
|
||||||
.map(v => v.toStubHtml())
|
.map((operation, idx) => operation.toStubHtml(false, `search-result-${idx}`))
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
searchResultsEl.innerHTML = matchedOpsHtml;
|
searchResultsEl.innerHTML = matchedOpsHtml;
|
||||||
|
@ -103,7 +120,7 @@ class OperationsWaiter {
|
||||||
* @param {string} searchStr
|
* @param {string} searchStr
|
||||||
* @param {boolean} highlight - Whether or not to highlight the matching string in the operation
|
* @param {boolean} highlight - Whether or not to highlight the matching string in the operation
|
||||||
* name and description
|
* name and description
|
||||||
* @returns {string[]}
|
* @returns {HTMLOperation[]}
|
||||||
*/
|
*/
|
||||||
filterOperations(inStr, highlight) {
|
filterOperations(inStr, highlight) {
|
||||||
const matchedOps = [];
|
const matchedOps = [];
|
||||||
|
|
Loading…
Add table
Reference in a new issue