Compare commits
3 Commits
0ca34c2a69
...
a2c258a627
Author | SHA1 | Date |
---|---|---|
Bauke | a2c258a627 | |
Bauke | b27dd3fc96 | |
Bauke | 26f9aa5c24 |
|
@ -3,14 +3,47 @@ import {render} from "preact";
|
||||||
import {log, querySelectorAll} from "../../utilities/exports.js";
|
import {log, querySelectorAll} from "../../utilities/exports.js";
|
||||||
import {
|
import {
|
||||||
type MarkdownSnippet,
|
type MarkdownSnippet,
|
||||||
|
type ProcessedSnippetShortcut,
|
||||||
MarkdownSnippetMarker,
|
MarkdownSnippetMarker,
|
||||||
|
processSnippetShortcut,
|
||||||
} from "../../storage/exports.js";
|
} from "../../storage/exports.js";
|
||||||
|
|
||||||
|
/** Type shorthand for a snippet with its processed shortcut. */
|
||||||
|
type ProcessedSnippetTuple = [Value<MarkdownSnippet>, ProcessedSnippetShortcut];
|
||||||
|
|
||||||
export function runMarkdownToolbarFeature(
|
export function runMarkdownToolbarFeature(
|
||||||
snippets: Array<Value<MarkdownSnippet>>,
|
snippets: Array<Value<MarkdownSnippet>>,
|
||||||
) {
|
) {
|
||||||
const count = addToolbarsToTextareas(snippets);
|
const count = addToolbarsToTextareas(snippets);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
|
// Process all the snippet shortcuts outside of the keydown handler so we
|
||||||
|
// don't have to process them on every keydown event.
|
||||||
|
const snippetsWithProcessedShortcuts: ProcessedSnippetTuple[] = snippets
|
||||||
|
// Exclude snippets that don't have shortcuts defined.
|
||||||
|
.filter((snippet) => snippet.value.shortcut !== undefined)
|
||||||
|
// Map the result as a tuple of the snippet and the processed shortcut.
|
||||||
|
.map(
|
||||||
|
(snippet) =>
|
||||||
|
[
|
||||||
|
snippet,
|
||||||
|
processSnippetShortcut(snippet.value.shortcut!),
|
||||||
|
] satisfies ProcessedSnippetTuple,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only add the keydown listener if it hasn't already been added and if
|
||||||
|
// there are any snippets with shortcuts to listen for.
|
||||||
|
if (
|
||||||
|
!document.body.dataset.trxMarkdownToolbarKeydownListening &&
|
||||||
|
snippetsWithProcessedShortcuts.length > 0
|
||||||
|
) {
|
||||||
|
document.addEventListener("keydown", async (event: KeyboardEvent) => {
|
||||||
|
await keyDownHandler(event, snippetsWithProcessedShortcuts);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.dataset.trxMarkdownToolbarKeydownListening = "true";
|
||||||
|
log("Markdown Toolbar: Listening for keyboard shortcuts.");
|
||||||
|
}
|
||||||
|
|
||||||
log(`Markdown Toolbar: Initialized for ${count} textareas.`);
|
log(`Markdown Toolbar: Initialized for ${count} textareas.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +158,7 @@ function SnippetDropdown(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertSnippet(props: Required<Props>) {
|
function insertSnippet(props: Omit<Required<Props>, "allSnippets">) {
|
||||||
const {textarea, snippet} = props;
|
const {textarea, snippet} = props;
|
||||||
const {selectionStart, selectionEnd} = textarea;
|
const {selectionStart, selectionEnd} = textarea;
|
||||||
|
|
||||||
|
@ -188,3 +221,48 @@ function insertSnippet(props: Required<Props>) {
|
||||||
|
|
||||||
textarea.selectionEnd = selectionEnd + cursorPosition;
|
textarea.selectionEnd = selectionEnd + cursorPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global handler for the `keydown` event.
|
||||||
|
*
|
||||||
|
* Keydown is chosen over keypress or keyup because it can be used to prevent
|
||||||
|
* the browser's keyboard shortcuts using `event.preventDefault()`.
|
||||||
|
*/
|
||||||
|
async function keyDownHandler(
|
||||||
|
event: KeyboardEvent,
|
||||||
|
snippets: ProcessedSnippetTuple[],
|
||||||
|
): Promise<void> {
|
||||||
|
const textarea = event.target;
|
||||||
|
|
||||||
|
// Markdown toolbars are only ever added for `<textarea>` elements, if the
|
||||||
|
// user is typing in a different input field we don't want to check for
|
||||||
|
// anything.
|
||||||
|
if (!(textarea instanceof HTMLTextAreaElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [key, alt, ctrl, shift] = [
|
||||||
|
event.key.toLowerCase(),
|
||||||
|
event.altKey,
|
||||||
|
event.ctrlKey,
|
||||||
|
event.shiftKey,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [snippet, shortcut] of snippets) {
|
||||||
|
if (
|
||||||
|
shortcut.key === key &&
|
||||||
|
shortcut.alt === alt &&
|
||||||
|
shortcut.ctrl === ctrl &&
|
||||||
|
shortcut.shift === shift
|
||||||
|
) {
|
||||||
|
// Prevent browser keyboard shortcuts like `CTRL+S` from triggering.
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
insertSnippet({
|
||||||
|
snippet,
|
||||||
|
textarea,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -148,6 +148,15 @@ class App extends Component<Props, State> {
|
||||||
<b>Name</b>, the name of the snippet to display in the
|
<b>Name</b>, the name of the snippet to display in the
|
||||||
toolbar.
|
toolbar.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Shortcut</b>, an optional keyboard shortcut that will
|
||||||
|
insert the snippet when the combination of keys is pressed. A
|
||||||
|
shortcut is defined as a list of optional modifier keys and a
|
||||||
|
single final main key separated by plus signs, for example{" "}
|
||||||
|
<code>CTRL+ALT+S</code>. The available modifier keys are ALT,{" "}
|
||||||
|
CTRL and SHIFT. The modifier keys can be in any order however
|
||||||
|
the final key must come at the end.
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<b>Enable</b>, whether the snippet should be added to the
|
<b>Enable</b>, whether the snippet should be added to the
|
||||||
toolbar.
|
toolbar.
|
||||||
|
|
Loading…
Reference in New Issue