mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-23 08:16:17 -04:00
statusbar popup keyboard navigation
This commit is contained in:
parent
85a3510454
commit
6fcf103760
3 changed files with 135 additions and 126 deletions
|
@ -478,6 +478,11 @@
|
||||||
background-color: #ddd
|
background-color: #ddd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Change color of dropup links on focus */
|
||||||
|
.cm-status-bar-select-content a:focus {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
/* Change the background color of the dropup button when the dropup content is shown */
|
/* Change the background color of the dropup button when the dropup content is shown */
|
||||||
.cm-status-bar-select:hover .cm-status-bar-select-btn {
|
.cm-status-bar-select:hover .cm-status-bar-select-btn {
|
||||||
background-color: #f1f1f1;
|
background-color: #f1f1f1;
|
||||||
|
|
|
@ -4,11 +4,8 @@
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { showPanel } from "@codemirror/view";
|
import {showPanel} from "@codemirror/view";
|
||||||
import {
|
import {CHR_ENC_SIMPLE_LOOKUP, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs";
|
||||||
CHR_ENC_SIMPLE_LOOKUP,
|
|
||||||
CHR_ENC_SIMPLE_REVERSE_LOOKUP,
|
|
||||||
} from "../../core/lib/ChrEnc.mjs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Status bar extension for CodeMirror
|
* A Status bar extension for CodeMirror
|
||||||
|
@ -44,10 +41,7 @@ class StatusBarPanel {
|
||||||
|
|
||||||
dom.className = "cm-status-bar";
|
dom.className = "cm-status-bar";
|
||||||
dom.setAttribute("data-help-title", `${this.label} status bar`);
|
dom.setAttribute("data-help-title", `${this.label} status bar`);
|
||||||
dom.setAttribute(
|
dom.setAttribute("data-help", `This status bar provides information about data in the ${this.label}. Help topics are available for each of the components by activating help when hovering over them.`);
|
||||||
"data-help",
|
|
||||||
`This status bar provides information about data in the ${this.label}. Help topics are available for each of the components by activating help when hovering over them.`
|
|
||||||
);
|
|
||||||
lhs.innerHTML = this.constructLHS();
|
lhs.innerHTML = this.constructLHS();
|
||||||
rhs.innerHTML = this.constructRHS();
|
rhs.innerHTML = this.constructRHS();
|
||||||
|
|
||||||
|
@ -58,25 +52,27 @@ class StatusBarPanel {
|
||||||
const eventHandler = this.showDropUp.bind(this);
|
const eventHandler = this.showDropUp.bind(this);
|
||||||
|
|
||||||
dom.querySelectorAll(".cm-status-bar-select-btn").forEach((el) => {
|
dom.querySelectorAll(".cm-status-bar-select-btn").forEach((el) => {
|
||||||
el.addEventListener("click", eventHandler, false),
|
el.addEventListener("click", eventHandler, false);
|
||||||
el.addEventListener("keydown", eventHandler, false);
|
|
||||||
});
|
});
|
||||||
dom.querySelector(".eol-select").addEventListener(
|
dom.querySelectorAll(".cm-status-bar-select-btn").forEach((el) => {
|
||||||
"click",
|
el.addEventListener("keydown", eventHandler, false);
|
||||||
this.eolSelectClick.bind(this),
|
});
|
||||||
false
|
|
||||||
);
|
const selectContent = dom.querySelectorAll(
|
||||||
dom.querySelector(".chr-enc-select").addEventListener(
|
".cm-status-bar-select-content"
|
||||||
"click",
|
|
||||||
this.chrEncSelectClick.bind(this),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
dom.querySelector(".cm-status-bar-filter-input").addEventListener(
|
|
||||||
"keyup",
|
|
||||||
this.chrEncFilter.bind(this),
|
|
||||||
false
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
selectContent.forEach((el) => {
|
||||||
|
const aTags = el.getElementsByTagName("a");
|
||||||
|
|
||||||
|
for (let i = 0; i < aTags.length; i++) {
|
||||||
|
aTags[i].addEventListener("keydown", arrowNav, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dom.querySelector(".eol-select").addEventListener("click", this.eolSelectClick.bind(this), false);
|
||||||
|
dom.querySelector(".chr-enc-select").addEventListener("click", this.chrEncSelectClick.bind(this), false);
|
||||||
|
dom.querySelector(".cm-status-bar-filter-input").addEventListener("keyup", this.chrEncFilter.bind(this), false);
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,14 +113,14 @@ class StatusBarPanel {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const eolLookup = {
|
const eolLookup = {
|
||||||
LF: "\u000a",
|
"LF": "\u000a",
|
||||||
VT: "\u000b",
|
"VT": "\u000b",
|
||||||
FF: "\u000c",
|
"FF": "\u000c",
|
||||||
CR: "\u000d",
|
"CR": "\u000d",
|
||||||
CRLF: "\u000d\u000a",
|
"CRLF": "\u000d\u000a",
|
||||||
NEL: "\u0085",
|
"NEL": "\u0085",
|
||||||
LS: "\u2028",
|
"LS": "\u2028",
|
||||||
PS: "\u2029",
|
"PS": "\u2029"
|
||||||
};
|
};
|
||||||
const eolval = eolLookup[e.target.getAttribute("data-val")];
|
const eolval = eolLookup[e.target.getAttribute("data-val")];
|
||||||
|
|
||||||
|
@ -198,9 +194,9 @@ class StatusBarPanel {
|
||||||
* @param {boolean} selectionSet
|
* @param {boolean} selectionSet
|
||||||
*/
|
*/
|
||||||
updateSelection(state, selectionSet) {
|
updateSelection(state, selectionSet) {
|
||||||
const selLen = state?.selection?.main
|
const selLen = state?.selection?.main ?
|
||||||
? state.selection.main.to - state.selection.main.from
|
state.selection.main.to - state.selection.main.from :
|
||||||
: 0;
|
0;
|
||||||
|
|
||||||
const selInfo = this.dom.querySelector(".sel-info"),
|
const selInfo = this.dom.querySelector(".sel-info"),
|
||||||
curOffsetInfo = this.dom.querySelector(".cur-offset-info");
|
curOffsetInfo = this.dom.querySelector(".cur-offset-info");
|
||||||
|
@ -218,12 +214,11 @@ class StatusBarPanel {
|
||||||
if (state.lineBreak.length !== 1) {
|
if (state.lineBreak.length !== 1) {
|
||||||
const fromLine = state.doc.lineAt(from).number;
|
const fromLine = state.doc.lineAt(from).number;
|
||||||
const toLine = state.doc.lineAt(to).number;
|
const toLine = state.doc.lineAt(to).number;
|
||||||
from += state.lineBreak.length * fromLine - fromLine - 1;
|
from += (state.lineBreak.length * fromLine) - fromLine - 1;
|
||||||
to += state.lineBreak.length * toLine - toLine - 1;
|
to += (state.lineBreak.length * toLine) - toLine - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selLen > 0) {
|
if (selLen > 0) { // Range
|
||||||
// Range
|
|
||||||
const start = this.dom.querySelector(".sel-start-value"),
|
const start = this.dom.querySelector(".sel-start-value"),
|
||||||
end = this.dom.querySelector(".sel-end-value"),
|
end = this.dom.querySelector(".sel-end-value"),
|
||||||
length = this.dom.querySelector(".sel-length-value");
|
length = this.dom.querySelector(".sel-length-value");
|
||||||
|
@ -233,8 +228,7 @@ class StatusBarPanel {
|
||||||
start.textContent = from;
|
start.textContent = from;
|
||||||
end.textContent = to;
|
end.textContent = to;
|
||||||
length.textContent = to - from;
|
length.textContent = to - from;
|
||||||
} else {
|
} else { // Position
|
||||||
// Position
|
|
||||||
const offset = this.dom.querySelector(".cur-offset-value");
|
const offset = this.dom.querySelector(".cur-offset-value");
|
||||||
|
|
||||||
selInfo.style.display = "none";
|
selInfo.style.display = "none";
|
||||||
|
@ -258,7 +252,7 @@ class StatusBarPanel {
|
||||||
"\u000d\u000a": ["CRLF", "Carriage Return + Line Feed"],
|
"\u000d\u000a": ["CRLF", "Carriage Return + Line Feed"],
|
||||||
"\u0085": ["NEL", "Next Line"],
|
"\u0085": ["NEL", "Next Line"],
|
||||||
"\u2028": ["LS", "Line Separator"],
|
"\u2028": ["LS", "Line Separator"],
|
||||||
"\u2029": ["PS", "Paragraph Separator"],
|
"\u2029": ["PS", "Paragraph Separator"]
|
||||||
};
|
};
|
||||||
|
|
||||||
const val = this.dom.querySelector(".eol-value");
|
const val = this.dom.querySelector(".eol-value");
|
||||||
|
@ -266,10 +260,7 @@ class StatusBarPanel {
|
||||||
const eolName = eolLookup[state.lineBreak];
|
const eolName = eolLookup[state.lineBreak];
|
||||||
val.textContent = eolName[0];
|
val.textContent = eolName[0];
|
||||||
button.setAttribute("title", `End of line sequence:<br>${eolName[1]}`);
|
button.setAttribute("title", `End of line sequence:<br>${eolName[1]}`);
|
||||||
button.setAttribute(
|
button.setAttribute("data-original-title", `End of line sequence:<br>${eolName[1]}`);
|
||||||
"data-original-title",
|
|
||||||
`End of line sequence:<br>${eolName[1]}`
|
|
||||||
);
|
|
||||||
this.eolVal = state.lineBreak;
|
this.eolVal = state.lineBreak;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,21 +271,13 @@ class StatusBarPanel {
|
||||||
const chrEncVal = this.chrEncGetter();
|
const chrEncVal = this.chrEncGetter();
|
||||||
if (chrEncVal === this.chrEncVal) return;
|
if (chrEncVal === this.chrEncVal) return;
|
||||||
|
|
||||||
const name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal]
|
const name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] : "Raw Bytes";
|
||||||
? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal]
|
|
||||||
: "Raw Bytes";
|
|
||||||
|
|
||||||
const val = this.dom.querySelector(".chr-enc-value");
|
const val = this.dom.querySelector(".chr-enc-value");
|
||||||
const button = val.closest(".cm-status-bar-select-btn");
|
const button = val.closest(".cm-status-bar-select-btn");
|
||||||
val.textContent = name;
|
val.textContent = name;
|
||||||
button.setAttribute(
|
button.setAttribute("title", `${this.label} character encoding:<br>${name}`);
|
||||||
"title",
|
button.setAttribute("data-original-title", `${this.label} character encoding:<br>${name}`);
|
||||||
`${this.label} character encoding:<br>${name}`
|
|
||||||
);
|
|
||||||
button.setAttribute(
|
|
||||||
"data-original-title",
|
|
||||||
`${this.label} character encoding:<br>${name}`
|
|
||||||
);
|
|
||||||
this.chrEncVal = chrEncVal;
|
this.chrEncVal = chrEncVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,9 +294,7 @@ class StatusBarPanel {
|
||||||
bakingTimeInfo.style.display = "inline-block";
|
bakingTimeInfo.style.display = "inline-block";
|
||||||
bakingTime.textContent = this.timing.duration(this.tabNumGetter());
|
bakingTime.textContent = this.timing.duration(this.tabNumGetter());
|
||||||
|
|
||||||
const info = this.timing
|
const info = this.timing.printStages(this.tabNumGetter()).replace(/\n/g, "<br>");
|
||||||
.printStages(this.tabNumGetter())
|
|
||||||
.replace(/\n/g, "<br>");
|
|
||||||
bakingTimeInfo.setAttribute("data-original-title", info);
|
bakingTimeInfo.setAttribute("data-original-title", info);
|
||||||
} else {
|
} else {
|
||||||
bakingTimeInfo.style.display = "none";
|
bakingTimeInfo.style.display = "none";
|
||||||
|
@ -326,11 +307,11 @@ class StatusBarPanel {
|
||||||
*/
|
*/
|
||||||
updateSizing(view) {
|
updateSizing(view) {
|
||||||
const viewHeight = view.contentDOM.parentNode.clientHeight;
|
const viewHeight = view.contentDOM.parentNode.clientHeight;
|
||||||
this.dom
|
this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach(
|
||||||
.querySelectorAll(".cm-status-bar-select-scroll")
|
el => {
|
||||||
.forEach((el) => {
|
el.style.maxHeight = (viewHeight - 50) + "px";
|
||||||
el.style.maxHeight = viewHeight - 50 + "px";
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -341,27 +322,19 @@ class StatusBarPanel {
|
||||||
|
|
||||||
if (this.htmlOutput?.html === "") {
|
if (this.htmlOutput?.html === "") {
|
||||||
// Enable all controls
|
// Enable all controls
|
||||||
this.dom.querySelectorAll(".disabled").forEach((el) => {
|
this.dom.querySelectorAll(".disabled").forEach(el => {
|
||||||
el.classList.remove("disabled");
|
el.classList.remove("disabled");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Disable chrenc, length, selection etc.
|
// Disable chrenc, length, selection etc.
|
||||||
this.dom
|
this.dom.querySelectorAll(".cm-status-bar-select-btn").forEach(el => {
|
||||||
.querySelectorAll(".cm-status-bar-select-btn")
|
el.classList.add("disabled");
|
||||||
.forEach((el) => {
|
});
|
||||||
el.classList.add("disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dom
|
this.dom.querySelector(".stats-length-value").parentNode.classList.add("disabled");
|
||||||
.querySelector(".stats-length-value")
|
this.dom.querySelector(".stats-lines-value").parentNode.classList.add("disabled");
|
||||||
.parentNode.classList.add("disabled");
|
|
||||||
this.dom
|
|
||||||
.querySelector(".stats-lines-value")
|
|
||||||
.parentNode.classList.add("disabled");
|
|
||||||
this.dom.querySelector(".sel-info").classList.add("disabled");
|
this.dom.querySelector(".sel-info").classList.add("disabled");
|
||||||
this.dom
|
this.dom.querySelector(".cur-offset-info").classList.add("disabled");
|
||||||
.querySelector(".cur-offset-info")
|
|
||||||
.classList.add("disabled");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +344,7 @@ class StatusBarPanel {
|
||||||
*/
|
*/
|
||||||
constructLHS() {
|
constructLHS() {
|
||||||
return `
|
return `
|
||||||
<span data-toggle="tooltip" tabindex="0" title="${this.label} length" data-help-title="${this.label} length" data-help="This number represents the number of characters in the ${this.label}.<br><br>The CRLF end of line separator is counted as two characters which impacts this value.">
|
<span data-toggle="tooltip" tabindex="0" title="${this.label} length" data-help-title="${this.label} length" data-help="This number represents the number of characters in the ${this.label}.<br><br>The CRLF end of line separator is counted as two characters which impacts this value.">
|
||||||
<i class="material-icons">abc</i>
|
<i class="material-icons">abc</i>
|
||||||
<span class="stats-length-value"></span>
|
<span class="stats-length-value"></span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -398,40 +371,33 @@ class StatusBarPanel {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
constructRHS() {
|
constructRHS() {
|
||||||
const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP)
|
const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP).map(name =>
|
||||||
.map(
|
`<a href="#" draggable="false" data-val="${CHR_ENC_SIMPLE_LOOKUP[name]}">${name}</a>`
|
||||||
(name) =>
|
).join("");
|
||||||
`<a href="#" draggable="false" data-val="${CHR_ENC_SIMPLE_LOOKUP[name]}">${name}</a>`
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
let chrEncHelpText = "",
|
let chrEncHelpText = "",
|
||||||
eolHelpText = "";
|
eolHelpText = "";
|
||||||
if (this.label === "Input") {
|
if (this.label === "Input") {
|
||||||
chrEncHelpText =
|
chrEncHelpText = "The input character encoding defines how the input text is encoded into bytes which are then processed by the Recipe.<br><br>The 'Raw bytes' option attempts to treat the input as individual bytes in the range 0-255. If it detects any characters with Unicode values above 255, it will treat the entire input as UTF-8. 'Raw bytes' is usually the best option if you are inputting binary data, such as a file.";
|
||||||
"The input character encoding defines how the input text is encoded into bytes which are then processed by the Recipe.<br><br>The 'Raw bytes' option attempts to treat the input as individual bytes in the range 0-255. If it detects any characters with Unicode values above 255, it will treat the entire input as UTF-8. 'Raw bytes' is usually the best option if you are inputting binary data, such as a file.";
|
eolHelpText = "The End of Line Sequence defines which bytes are considered EOL terminators. Pressing the return key will enter this value into the input and create a new line.<br><br>Changing the EOL sequence will not modify any existing data in the input but may change how previously entered line breaks are displayed. Lines added while a different EOL terminator was set may not now result in a new line, but may be displayed as control characters instead.";
|
||||||
eolHelpText =
|
|
||||||
"The End of Line Sequence defines which bytes are considered EOL terminators. Pressing the return key will enter this value into the input and create a new line.<br><br>Changing the EOL sequence will not modify any existing data in the input but may change how previously entered line breaks are displayed. Lines added while a different EOL terminator was set may not now result in a new line, but may be displayed as control characters instead.";
|
|
||||||
} else {
|
} else {
|
||||||
chrEncHelpText =
|
chrEncHelpText = "The output character encoding defines how the output bytes are decoded into text which can be displayed to you.<br><br>The 'Raw bytes' option treats the output data as individual bytes in the range 0-255.";
|
||||||
"The output character encoding defines how the output bytes are decoded into text which can be displayed to you.<br><br>The 'Raw bytes' option treats the output data as individual bytes in the range 0-255.";
|
eolHelpText = "The End of Line Sequence defines which bytes are considered EOL terminators.<br><br>Changing this value will not modify the value of the output, but may change how certain bytes are displayed and whether they result in a new line being created.";
|
||||||
eolHelpText =
|
|
||||||
"The End of Line Sequence defines which bytes are considered EOL terminators.<br><br>Changing this value will not modify the value of the output, but may change how certain bytes are displayed and whether they result in a new line being created.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<span class="baking-time-info" style="display: none" data-toggle="tooltip" tabindex="0" data-html="true" title="Baking time" data-help-title="Baking time" data-help="The baking time is the total time between data being read from the input, processed, and then displayed in the output.<br><br>The 'Threading overhead' value accounts for the transfer of data between different processing threads, as well as some garbage collection. It is not included in the overall bake time displayed in the status bar as it is largely influenced by background operating system and browser activity which can fluctuate significantly.">
|
<span class="baking-time-info" style="display: none" data-toggle="tooltip" tabindex="0" data-html="true" title="Baking time" data-help-title="Baking time" data-help="The baking time is the total time between data being read from the input, processed, and then displayed in the output.<br><br>The 'Threading overhead' value accounts for the transfer of data between different processing threads, as well as some garbage collection. It is not included in the overall bake time displayed in the status bar as it is largely influenced by background operating system and browser activity which can fluctuate significantly.">
|
||||||
<i class="material-icons">schedule</i>
|
<i class="material-icons">schedule</i>
|
||||||
<span class="baking-time-value"></span>ms
|
<span class="baking-time-value"></span>ms
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="cm-status-bar-select chr-enc-select" data-help-title="${this.label} character encoding" data-help="${chrEncHelpText}">
|
<div class="cm-status-bar-select chr-enc-select" data-help-title="${this.label} character encoding" data-help="${chrEncHelpText}">
|
||||||
<span class="cm-status-bar-select-btn" data-toggle="tooltip" tabindex="0" data-html="true" data-placement="left" title="${this.label} character encoding">
|
<span class="cm-status-bar-select-btn" data-toggle="tooltip" tabindex="0" data-html="true" data-placement="left" title="${this.label} character encoding">
|
||||||
<i class="material-icons">text_fields</i> <span class="chr-enc-value">Raw Bytes</span>
|
<i class="material-icons">text_fields</i> <span class="chr-enc-value">Raw Bytes</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="cm-status-bar-select-content">
|
<div class="cm-status-bar-select-content">
|
||||||
<div class="cm-status-bar-select-scroll no-select">
|
<div class="cm-status-bar-select-scroll no-select">
|
||||||
<a href="#" draggable="false" data-val="0">Raw Bytes</a>
|
<a href="#" draggable="false" data-val="0" tabindex="0">Raw Bytes</a>
|
||||||
${chrEncOptions}
|
${chrEncOptions}
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group cm-status-bar-filter-search">
|
<div class="input-group cm-status-bar-filter-search">
|
||||||
|
@ -449,15 +415,15 @@ class StatusBarPanel {
|
||||||
<span class="cm-status-bar-select-btn" data-toggle="tooltip" tabindex="0" data-html="true" data-placement="left" title="End of line sequence">
|
<span class="cm-status-bar-select-btn" data-toggle="tooltip" tabindex="0" data-html="true" data-placement="left" title="End of line sequence">
|
||||||
<i class="material-icons">keyboard_return</i> <span class="eol-value"></span>
|
<i class="material-icons">keyboard_return</i> <span class="eol-value"></span>
|
||||||
</span>
|
</span>
|
||||||
<div class="cm-status-bar-select-content no-select">
|
<div class="cm-status-bar-select-content no-select" tabindex="0">
|
||||||
<a href="#" draggable="false" data-val="LF">Line Feed, U+000A</a>
|
<a href="#" draggable="false" data-val="LF" tabindex="0">Line Feed, U+000A</a>
|
||||||
<a href="#" draggable="false" data-val="VT">Vertical Tab, U+000B</a>
|
<a href="#" draggable="false" data-val="VT" tabindex="0">Vertical Tab, U+000B</a>
|
||||||
<a href="#" draggable="false" data-val="FF">Form Feed, U+000C</a>
|
<a href="#" draggable="false" data-val="FF" tabindex="0">Form Feed, U+000C</a>
|
||||||
<a href="#" draggable="false" data-val="CR">Carriage Return, U+000D</a>
|
<a href="#" draggable="false" data-val="CR" tabindex="0">Carriage Return, U+000D</a>
|
||||||
<a href="#" draggable="false" data-val="CRLF">CR+LF, U+000D U+000A</a>
|
<a href="#" draggable="false" data-val="CRLF" tabindex="0">CR+LF, U+000D U+000A</a>
|
||||||
<!-- <a href="#" draggable="false" data-val="NL">Next Line, U+0085</a> This causes problems. -->
|
<!-- <a href="#" draggable="false" data-val="NL" tabindex="0">Next Line, U+0085</a> This causes problems. -->
|
||||||
<a href="#" draggable="false" data-val="LS">Line Separator, U+2028</a>
|
<a href="#" draggable="false" data-val="LS" tabindex="0">Line Separator, U+2028</a>
|
||||||
<a href="#" draggable="false" data-val="PS">Paragraph Separator, U+2029</a>
|
<a href="#" draggable="false" data-val="PS" tabindex="0">Paragraph Separator, U+2029</a>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
@ -476,7 +442,7 @@ function hideOnClickOutside(element, instantiatingEvent) {
|
||||||
* Closes element if click is outside it.
|
* Closes element if click is outside it.
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
*/
|
*/
|
||||||
const outsideClickListener = (event) => {
|
const outsideClickListener = event => {
|
||||||
// Don't trigger if we're clicking inside the element, or if the element
|
// Don't trigger if we're clicking inside the element, or if the element
|
||||||
// is not visible, or if this is the same click event that opened it.
|
// is not visible, or if this is the same click event that opened it.
|
||||||
if (
|
if (
|
||||||
|
@ -509,14 +475,30 @@ function hideOnMoveFocus(element, instantiatingEvent) {
|
||||||
* Closes element if key press is outside it.
|
* Closes element if key press is outside it.
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
*/
|
*/
|
||||||
const outsideClickListener = (event) => {
|
const outsidePressListener = (event) => {
|
||||||
// Don't trigger if we're pressing keys while inside the element, or if the element
|
// Don't trigger if we're pressing keys while inside the element, or if the element
|
||||||
// is not visible, or if this is the same click event that opened it.
|
// is not visible, or if this is the same click event that opened it.
|
||||||
if (
|
if (
|
||||||
!element.contains(event.target) &&
|
!element.contains(event.target) &&
|
||||||
|
event.timeStamp !== instantiatingEvent.timeStamp &&
|
||||||
|
event.key !== "ArrowUp"
|
||||||
|
) {
|
||||||
|
hideElement(element);
|
||||||
|
} else if (
|
||||||
|
event.key === "Escape" &&
|
||||||
event.timeStamp !== instantiatingEvent.timeStamp
|
event.timeStamp !== instantiatingEvent.timeStamp
|
||||||
) {
|
) {
|
||||||
hideElement(element);
|
hideElement(element);
|
||||||
|
} else if (
|
||||||
|
event.key === "ArrowUp" ||
|
||||||
|
(event.key === "ArrowDown" &&
|
||||||
|
event.timeStamp !== instantiatingEvent.timeStamp)
|
||||||
|
) {
|
||||||
|
const menuItems = element.getElementsByTagName("a");
|
||||||
|
menuItems[0].focus();
|
||||||
|
|
||||||
|
console.log("ev target:", event.target);
|
||||||
|
console.log("element", element);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -526,7 +508,7 @@ function hideOnMoveFocus(element, instantiatingEvent) {
|
||||||
element
|
element
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
elementsWithKeyDownListeners[element] = outsideClickListener;
|
elementsWithKeyDownListeners[element] = outsidePressListener;
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"keydown",
|
"keydown",
|
||||||
elementsWithKeyDownListeners[element],
|
elementsWithKeyDownListeners[element],
|
||||||
|
@ -535,6 +517,38 @@ function hideOnMoveFocus(element, instantiatingEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for menu item keydown events
|
||||||
|
* Moves focus to next/previous element based on arrow direction.
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
const arrowNav = (event) => {
|
||||||
|
const currentElement = event.target;
|
||||||
|
if (event.key === "ArrowDown") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const nextElement = currentElement.nextElementSibling;
|
||||||
|
if (nextElement === null) {
|
||||||
|
currentElement.parentElement.firstElementChild.focus();
|
||||||
|
} else {
|
||||||
|
nextElement.focus();
|
||||||
|
}
|
||||||
|
} else if (event.key === "ArrowUp") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const prevElement = currentElement.previousElementSibling;
|
||||||
|
if (prevElement === null) {
|
||||||
|
currentElement.parentElement.lastElementChild.focus();
|
||||||
|
} else {
|
||||||
|
prevElement.focus();
|
||||||
|
}
|
||||||
|
} else if (event.key === "Tab") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
currentElement.parentElement.closest(".cm-status-bar-select-content").previousElementSibling.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the specified element and removes the click or keydown listener for it
|
* Hides the specified element and removes the click or keydown listener for it
|
||||||
* @param {Element} element
|
* @param {Element} element
|
||||||
|
|
|
@ -152,9 +152,7 @@ class ControlsWaiter {
|
||||||
|
|
||||||
const params = [
|
const params = [
|
||||||
includeRecipe ? ["recipe", recipeStr] : undefined,
|
includeRecipe ? ["recipe", recipeStr] : undefined,
|
||||||
includeInput && input.length
|
includeInput && input.length ? ["input", Utils.escapeHtml(input)] : undefined,
|
||||||
? ["input", Utils.escapeHtml(input)]
|
|
||||||
: undefined,
|
|
||||||
inputChrEnc !== 0 ? ["ienc", inputChrEnc] : undefined,
|
inputChrEnc !== 0 ? ["ienc", inputChrEnc] : undefined,
|
||||||
outputChrEnc !== 0 ? ["oenc", outputChrEnc] : undefined,
|
outputChrEnc !== 0 ? ["oenc", outputChrEnc] : undefined,
|
||||||
inputEOLSeq !== "\n" ? ["ieol", inputEOLSeq] : undefined,
|
inputEOLSeq !== "\n" ? ["ieol", inputEOLSeq] : undefined,
|
||||||
|
@ -255,9 +253,7 @@ class ControlsWaiter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedRecipes = localStorage.savedRecipes
|
const savedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : [];
|
||||||
? JSON.parse(localStorage.savedRecipes)
|
|
||||||
: [];
|
|
||||||
let recipeId = localStorage.recipeId || 0;
|
let recipeId = localStorage.recipeId || 0;
|
||||||
|
|
||||||
savedRecipes.push({
|
savedRecipes.push({
|
||||||
|
@ -287,9 +283,7 @@ class ControlsWaiter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add recipes to select
|
// Add recipes to select
|
||||||
const savedRecipes = localStorage.savedRecipes
|
const savedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : [];
|
||||||
? JSON.parse(localStorage.savedRecipes)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
for (i = 0; i < savedRecipes.length; i++) {
|
for (i = 0; i < savedRecipes.length; i++) {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
|
@ -316,9 +310,7 @@ class ControlsWaiter {
|
||||||
if (!this.app.isLocalStorageAvailable()) return false;
|
if (!this.app.isLocalStorageAvailable()) return false;
|
||||||
|
|
||||||
const id = parseInt(document.getElementById("load-name").value, 10);
|
const id = parseInt(document.getElementById("load-name").value, 10);
|
||||||
const rawSavedRecipes = localStorage.savedRecipes
|
const rawSavedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : [];
|
||||||
? JSON.parse(localStorage.savedRecipes)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const savedRecipes = rawSavedRecipes.filter((r) => r.id !== id);
|
const savedRecipes = rawSavedRecipes.filter((r) => r.id !== id);
|
||||||
|
|
||||||
|
@ -333,9 +325,7 @@ class ControlsWaiter {
|
||||||
if (!this.app.isLocalStorageAvailable()) return false;
|
if (!this.app.isLocalStorageAvailable()) return false;
|
||||||
|
|
||||||
const el = e.target;
|
const el = e.target;
|
||||||
const savedRecipes = localStorage.savedRecipes
|
const savedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : [];
|
||||||
? JSON.parse(localStorage.savedRecipes)
|
|
||||||
: [];
|
|
||||||
const id = parseInt(el.value, 10);
|
const id = parseInt(el.value, 10);
|
||||||
|
|
||||||
const recipe = savedRecipes.find((r) => r.id === id);
|
const recipe = savedRecipes.find((r) => r.id === id);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue