✨ Added new command palette
This commit is contained in:
@@ -93,6 +93,7 @@
|
|||||||
"swiper": "^8.4.7",
|
"swiper": "^8.4.7",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
|
"tinykeys": "^2.1.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tslib": "^2.5.0",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "~5.0.4",
|
"typescript": "~5.0.4",
|
||||||
|
|||||||
Generated
+11
-1
@@ -1,4 +1,4 @@
|
|||||||
lockfileVersion: '6.1'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
@@ -247,6 +247,9 @@ devDependencies:
|
|||||||
thumbhash:
|
thumbhash:
|
||||||
specifier: ^0.1.1
|
specifier: ^0.1.1
|
||||||
version: 0.1.1
|
version: 0.1.1
|
||||||
|
tinykeys:
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0
|
||||||
tippy.js:
|
tippy.js:
|
||||||
specifier: ^6.3.7
|
specifier: ^6.3.7
|
||||||
version: 6.3.7
|
version: 6.3.7
|
||||||
@@ -5674,6 +5677,13 @@ packages:
|
|||||||
globrex: 0.1.2
|
globrex: 0.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tinykeys@2.1.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-/MESnqBD1xItZJn5oGQ4OsNORQgJfPP96XSGoyu4eLpwpL0ifO0SYR5OD76u0YMhMXsqkb0UqvI9+yXTh4xv8Q==
|
||||||
|
}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/tinyqueue@2.0.3:
|
/tinyqueue@2.0.3:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
<!--
|
||||||
|
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { tinykeys } from '$lib/tinykeys';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import MiniSearch from 'minisearch';
|
||||||
|
|
||||||
|
let open = false;
|
||||||
|
let input = '';
|
||||||
|
let bg_text = '';
|
||||||
|
let title_ms: MiniSearch;
|
||||||
|
let command_ms: MiniSearch;
|
||||||
|
let selected: null | number = null;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
type ActionFunction = (args: string[]) => void;
|
||||||
|
const actions: {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
command?: string;
|
||||||
|
args?: string[];
|
||||||
|
action: ActionFunction;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
title: 'Close CommandPalette',
|
||||||
|
description: 'Closes CommandPalette',
|
||||||
|
command: 'close',
|
||||||
|
action: () => close_cp(undefined)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Create Quiz',
|
||||||
|
description: 'Opens editor to create a new quiz',
|
||||||
|
command: 'newquiz',
|
||||||
|
args: ['title'],
|
||||||
|
action: (args) => window.location.assign(`/create?title=${args.join(' ')}`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Import a Quiz',
|
||||||
|
description: 'Opens the import page',
|
||||||
|
command: 'import',
|
||||||
|
args: ['url'],
|
||||||
|
action: (args) => window.location.assign(`/import?url=${args?.[0] ?? ''}`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'Create Quiztivity',
|
||||||
|
description: 'Opens the editor for quiztivities',
|
||||||
|
command: 'newquiztivity',
|
||||||
|
args: ['title'],
|
||||||
|
action: (args) => window.location.assign(`/quiztivity/create?title=${args.join(' ')}`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: 'View Results',
|
||||||
|
description: 'Opens the Results viewer',
|
||||||
|
command: 'results',
|
||||||
|
action: () => window.location.assign('/results')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'Explore Quizzes',
|
||||||
|
description: 'Opens the Explore-page',
|
||||||
|
command: 'explore',
|
||||||
|
action: () => window.location.assign('/explore')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'Dashboard',
|
||||||
|
description: 'Go to Dashboard',
|
||||||
|
command: 'dash',
|
||||||
|
action: () => window.location.assign('/dashboard')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: 'Docs',
|
||||||
|
description: 'Go to documentation',
|
||||||
|
command: 'docs',
|
||||||
|
action: () => window.location.assign('/docs')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
title: 'Settings',
|
||||||
|
description: 'Opens the Settings page',
|
||||||
|
command: 'settings',
|
||||||
|
action: () => window.location.assign('/account/settings')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let visible_items = actions;
|
||||||
|
|
||||||
|
const toggle_open = (e: KeyboardEvent | undefined) => {
|
||||||
|
e.preventDefault();
|
||||||
|
open = !open;
|
||||||
|
console.log('TOGGLE!');
|
||||||
|
};
|
||||||
|
|
||||||
|
const close_cp = (e: KeyboardEvent | undefined) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const close_on_outside = (e: Event) => {
|
||||||
|
if (e.target == e.currentTarget) {
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const execute_action = () => {
|
||||||
|
let args = [];
|
||||||
|
const entry = visible_items[selected];
|
||||||
|
if (input.startsWith('/')) {
|
||||||
|
const tokens = input.split(' ');
|
||||||
|
args = tokens.slice(1);
|
||||||
|
}
|
||||||
|
console.log(args);
|
||||||
|
entry.action(args);
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = (term: string) => {
|
||||||
|
if (!command_ms || !title_ms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (term === '' || term === '/') {
|
||||||
|
selected = 0;
|
||||||
|
visible_items = actions;
|
||||||
|
bg_text = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let suggestions;
|
||||||
|
let res;
|
||||||
|
if (term.startsWith('/')) {
|
||||||
|
term = term.substring(1);
|
||||||
|
suggestions = command_ms.autoSuggest(term, { boost: { command: 2 }, prefix: true });
|
||||||
|
res = command_ms.search(term, { boost: { command: 2 }, prefix: true });
|
||||||
|
bg_text = suggestions[0]?.suggestion;
|
||||||
|
bg_text ??= '';
|
||||||
|
bg_text = `/${bg_text}`;
|
||||||
|
} else {
|
||||||
|
suggestions = title_ms.autoSuggest(term, { boost: { command: 2 }, prefix: true });
|
||||||
|
res = title_ms.search(term, { boost: { command: 2 }, prefix: true });
|
||||||
|
bg_text = suggestions[0]?.suggestion;
|
||||||
|
bg_text ??= '';
|
||||||
|
}
|
||||||
|
|
||||||
|
visible_items = [];
|
||||||
|
|
||||||
|
console.log(res);
|
||||||
|
for (const quiz_data of res) {
|
||||||
|
visible_items.push(actions[quiz_data.id]);
|
||||||
|
}
|
||||||
|
visible_items = visible_items;
|
||||||
|
if (visible_items.length === 1) {
|
||||||
|
selected = 0;
|
||||||
|
}
|
||||||
|
if (visible_items.length === 0) {
|
||||||
|
selected = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const autocomplete_on_tab = (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
input = bg_text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const on_arrow_down = (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (visible_items.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selected + 1 === visible_items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selected += 1;
|
||||||
|
};
|
||||||
|
const on_arrow_up = (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (visible_items.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selected === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selected -= 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const on_enter = (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (selected === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
execute_action();
|
||||||
|
input = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
$: search(input);
|
||||||
|
// $: input = lower_input(input)
|
||||||
|
$: input = input.toLowerCase();
|
||||||
|
onMount(async () => {
|
||||||
|
tinykeys(window, {
|
||||||
|
'$mod+k': toggle_open,
|
||||||
|
Escape: close_cp,
|
||||||
|
Tab: autocomplete_on_tab,
|
||||||
|
ArrowDown: on_arrow_down,
|
||||||
|
ArrowUp: on_arrow_up,
|
||||||
|
Enter: on_enter
|
||||||
|
});
|
||||||
|
title_ms = new MiniSearch<any>({
|
||||||
|
fields: ['title'],
|
||||||
|
storeFields: ['id']
|
||||||
|
});
|
||||||
|
title_ms.addAll(actions);
|
||||||
|
command_ms = new MiniSearch<any>({
|
||||||
|
fields: ['command'],
|
||||||
|
storeFields: ['id']
|
||||||
|
});
|
||||||
|
command_ms.addAll(actions);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if open}
|
||||||
|
<div
|
||||||
|
class="fixed top-0 left-0 w-screen h-screen flex bg-black bg-opacity-50 z-50"
|
||||||
|
on:click={close_on_outside}
|
||||||
|
transition:fade={{ duration: 60 }}
|
||||||
|
>
|
||||||
|
<div class="m-auto w-1/3 h-2/3 rounded bg-black flex flex-col">
|
||||||
|
<div class="grid grid-cols-1 grid-rows-1 border-b border-b-white">
|
||||||
|
<p
|
||||||
|
class="col-start-1 row-start-1 w-full p-4 outline-none bg-gray-700 rounded-t text-gray-400"
|
||||||
|
>
|
||||||
|
{bg_text}
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autofocus
|
||||||
|
class="col-start-1 row-start-1 bg-transparent w-full p-4 outline-none bg-gray-700 rounded"
|
||||||
|
bind:value={input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col p-2 gap-2 overflow-scroll">
|
||||||
|
{#each visible_items as vi, i}
|
||||||
|
<div
|
||||||
|
transition:fade|local={{ duration: 60 }}
|
||||||
|
class="p-2 transition rounded"
|
||||||
|
class:bg-[#B07156]={selected === i}
|
||||||
|
class:bg-gray-700={selected !== i}
|
||||||
|
on:mouseenter={() => (selected = i)}
|
||||||
|
on:mousedown={execute_action}
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
<h3 class="text-lg my-auto">{vi.title}</h3>
|
||||||
|
<p
|
||||||
|
class="font-mono my-auto ml-auto h-fit bg-black bg-opacity-50 rounded p-0.5"
|
||||||
|
>
|
||||||
|
/{vi.command}
|
||||||
|
{#if vi.args}
|
||||||
|
{#each vi.args as arg}
|
||||||
|
{<span class="text-indigo-400">{arg}</span
|
||||||
|
>}{/each}
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm">{vi.description}</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Jamie Kyle
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
COPIED FROM https://github.com/jamiebuilds/tinykeys/blob/111955cb6604fb5b8c4f152cb75b7f2cb63da913/src/tinykeys.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
type KeyBindingPress = [string[], string];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of keybinding strings to event handlers.
|
||||||
|
*/
|
||||||
|
export interface KeyBindingMap {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
[keybinding: string]: (event: KeyboardEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeyBindingHandlerOptions {
|
||||||
|
/**
|
||||||
|
* Keybinding sequences will wait this long between key presses before
|
||||||
|
* cancelling (default: 1000).
|
||||||
|
*
|
||||||
|
* **Note:** Setting this value too low (i.e. `300`) will be too fast for many
|
||||||
|
* of your users.
|
||||||
|
*/
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options to configure the behavior of keybindings.
|
||||||
|
*/
|
||||||
|
export interface KeyBindingOptions extends KeyBindingHandlerOptions {
|
||||||
|
/**
|
||||||
|
* Key presses will listen to this event (default: "keydown").
|
||||||
|
*/
|
||||||
|
event?: 'keydown' | 'keyup';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the modifier keys that change the meaning of keybindings.
|
||||||
|
*
|
||||||
|
* Note: Ignoring "AltGraph" because it is covered by the others.
|
||||||
|
*/
|
||||||
|
const KEYBINDING_MODIFIER_KEYS = ['Shift', 'Meta', 'Alt', 'Control'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keybinding sequences should timeout if individual key presses are more than
|
||||||
|
* 1s apart by default.
|
||||||
|
*/
|
||||||
|
const DEFAULT_TIMEOUT = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keybinding sequences should bind to this event by default.
|
||||||
|
*/
|
||||||
|
const DEFAULT_EVENT = 'keydown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform detection code.
|
||||||
|
* @see https://github.com/jamiebuilds/tinykeys/issues/184
|
||||||
|
*/
|
||||||
|
const PLATFORM = typeof navigator === 'object' ? navigator.platform : '';
|
||||||
|
const APPLE_DEVICE = /Mac|iPod|iPhone|iPad/.test(PLATFORM);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An alias for creating platform-specific keybinding aliases.
|
||||||
|
*/
|
||||||
|
const MOD = APPLE_DEVICE ? 'Meta' : 'Control';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meaning of `AltGraph`, from MDN:
|
||||||
|
* - Windows: Both Alt and Ctrl keys are pressed, or AltGr key is pressed
|
||||||
|
* - Mac: ⌥ Option key pressed
|
||||||
|
* - Linux: Level 3 Shift key (or Level 5 Shift key) pressed
|
||||||
|
* - Android: Not supported
|
||||||
|
* @see https://github.com/jamiebuilds/tinykeys/issues/185
|
||||||
|
*/
|
||||||
|
const ALT_GRAPH_ALIASES = PLATFORM === 'Win32' ? ['Control', 'Alt'] : APPLE_DEVICE ? ['Alt'] : [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There's a bug in Chrome that causes event.getModifierState not to exist on
|
||||||
|
* KeyboardEvent's for F1/F2/etc keys.
|
||||||
|
*/
|
||||||
|
function getModifierState(event: KeyboardEvent, mod: string) {
|
||||||
|
return typeof event.getModifierState === 'function'
|
||||||
|
? event.getModifierState(mod) ||
|
||||||
|
(ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState('AltGraph'))
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a "Key Binding String" into its parts
|
||||||
|
*
|
||||||
|
* grammar = `<sequence>`
|
||||||
|
* <sequence> = `<press> <press> <press> ...`
|
||||||
|
* <press> = `<key>` or `<mods>+<key>`
|
||||||
|
* <mods> = `<mod>+<mod>+...`
|
||||||
|
*/
|
||||||
|
export function parseKeybinding(str: string): KeyBindingPress[] {
|
||||||
|
return str
|
||||||
|
.trim()
|
||||||
|
.split(' ')
|
||||||
|
.map((press) => {
|
||||||
|
let mods = press.split(/\b\+/);
|
||||||
|
const key = mods.pop() as string;
|
||||||
|
mods = mods.map((mod) => (mod === '$mod' ? MOD : mod));
|
||||||
|
return [mods, key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tells us if a series of events matches a key binding sequence either
|
||||||
|
* partially or exactly.
|
||||||
|
*/
|
||||||
|
function match(event: KeyboardEvent, press: KeyBindingPress): boolean {
|
||||||
|
// prettier-ignore
|
||||||
|
return !(
|
||||||
|
// Allow either the `event.key` or the `event.code`
|
||||||
|
// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||||
|
// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
|
||||||
|
(
|
||||||
|
press[1].toUpperCase() !== event.key.toUpperCase() &&
|
||||||
|
press[1] !== event.code
|
||||||
|
) ||
|
||||||
|
|
||||||
|
// Ensure all the modifiers in the keybinding are pressed.
|
||||||
|
press[0].find(mod => {
|
||||||
|
return !getModifierState(event, mod)
|
||||||
|
}) ||
|
||||||
|
|
||||||
|
// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a
|
||||||
|
// keybinding. So if they are pressed but aren't part of the current
|
||||||
|
// keybinding press, then we don't have a match.
|
||||||
|
KEYBINDING_MODIFIER_KEYS.find(mod => {
|
||||||
|
return !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event listener for handling keybindings.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* import { createKeybindingsHandler } from "../src/keybindings"
|
||||||
|
*
|
||||||
|
* let handler = createKeybindingsHandler({
|
||||||
|
* "Shift+d": () => {
|
||||||
|
* alert("The 'Shift' and 'd' keys were pressed at the same time")
|
||||||
|
* },
|
||||||
|
* "y e e t": () => {
|
||||||
|
* alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
|
||||||
|
* },
|
||||||
|
* "$mod+d": () => {
|
||||||
|
* alert("Either 'Control+d' or 'Meta+d' were pressed")
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* window.addEvenListener("keydown", handler)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function createKeybindingsHandler(
|
||||||
|
keyBindingMap: KeyBindingMap,
|
||||||
|
options: KeyBindingHandlerOptions = {}
|
||||||
|
): EventListener {
|
||||||
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
||||||
|
|
||||||
|
const keyBindings = Object.keys(keyBindingMap).map((key) => {
|
||||||
|
return [parseKeybinding(key), keyBindingMap[key]] as const;
|
||||||
|
});
|
||||||
|
|
||||||
|
const possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>();
|
||||||
|
let timer: number | null = null;
|
||||||
|
|
||||||
|
return (event) => {
|
||||||
|
// Ensure and stop any event that isn't a full keyboard event.
|
||||||
|
// Autocomplete option navigation and selection would fire a instanceof Event,
|
||||||
|
// instead of the expected KeyboardEvent
|
||||||
|
if (!(event instanceof KeyboardEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBindings.forEach((keyBinding) => {
|
||||||
|
const sequence = keyBinding[0];
|
||||||
|
const callback = keyBinding[1];
|
||||||
|
|
||||||
|
const prev = possibleMatches.get(sequence);
|
||||||
|
const remainingExpectedPresses = prev ? prev : sequence;
|
||||||
|
const currentExpectedPress = remainingExpectedPresses[0];
|
||||||
|
|
||||||
|
const matches = match(event, currentExpectedPress);
|
||||||
|
|
||||||
|
if (!matches) {
|
||||||
|
// Modifier keydown events shouldn't break sequences
|
||||||
|
// Note: This works because:
|
||||||
|
// - non-modifiers will always return false
|
||||||
|
// - if the current keypress is a modifier then it will return true when we check its state
|
||||||
|
// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState
|
||||||
|
if (!getModifierState(event, event.key)) {
|
||||||
|
possibleMatches.delete(sequence);
|
||||||
|
}
|
||||||
|
} else if (remainingExpectedPresses.length > 1) {
|
||||||
|
possibleMatches.set(sequence, remainingExpectedPresses.slice(1));
|
||||||
|
} else {
|
||||||
|
possibleMatches.delete(sequence);
|
||||||
|
callback(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
timer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to keybindings.
|
||||||
|
*
|
||||||
|
* Returns an unsubscribe method.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* import { tinykeys } from "../src/tinykeys"
|
||||||
|
*
|
||||||
|
* tinykeys(window, {
|
||||||
|
* "Shift+d": () => {
|
||||||
|
* alert("The 'Shift' and 'd' keys were pressed at the same time")
|
||||||
|
* },
|
||||||
|
* "y e e t": () => {
|
||||||
|
* alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
|
||||||
|
* },
|
||||||
|
* "$mod+d": () => {
|
||||||
|
* alert("Either 'Control+d' or 'Meta+d' were pressed")
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function tinykeys(
|
||||||
|
target: Window | HTMLElement,
|
||||||
|
keyBindingMap: KeyBindingMap,
|
||||||
|
options: KeyBindingOptions = {}
|
||||||
|
): () => void {
|
||||||
|
const event = options.event ?? DEFAULT_EVENT;
|
||||||
|
const onKeyEvent = createKeybindingsHandler(keyBindingMap, options);
|
||||||
|
|
||||||
|
target.addEventListener(event, onKeyEvent);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
target.removeEventListener(event, onKeyEvent);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import { BrowserTracing } from '@sentry/tracing';
|
import { BrowserTracing } from '@sentry/tracing';
|
||||||
import { initLocalizationContext } from '$lib/i18n';
|
import { initLocalizationContext } from '$lib/i18n';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import CommandPalette from '$lib/components/commandpalette.svelte';
|
||||||
// import Alert from '$lib/modals/alert.svelte';
|
// import Alert from '$lib/modals/alert.svelte';
|
||||||
|
|
||||||
/* afterNavigate(() => {
|
/* afterNavigate(() => {
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<slot />
|
<slot />
|
||||||
{/if}
|
{/if}
|
||||||
|
<CommandPalette />
|
||||||
|
|
||||||
<!--{#if $alertModal.open ?? false}
|
<!--{#if $alertModal.open ?? false}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import { getLocalization } from '$lib/i18n';
|
import { getLocalization } from '$lib/i18n';
|
||||||
import { navbarVisible } from '$lib/stores';
|
import { navbarVisible } from '$lib/stores';
|
||||||
import type { Question } from '$lib/quiz_types';
|
import type { Question } from '$lib/quiz_types';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
navbarVisible.set(false);
|
navbarVisible.set(false);
|
||||||
|
|
||||||
@@ -30,10 +31,12 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
const from_localstorage = localStorage.getItem('create_game');
|
const from_localstorage = localStorage.getItem('create_game');
|
||||||
if (from_localstorage === null) {
|
if (from_localstorage === null) {
|
||||||
|
let title = $page.url.searchParams.get('title');
|
||||||
|
title ??= '';
|
||||||
data = {
|
data = {
|
||||||
description: '',
|
description: '',
|
||||||
public: false,
|
public: false,
|
||||||
title: '',
|
title,
|
||||||
questions: [
|
questions: [
|
||||||
/* {
|
/* {
|
||||||
type: QuizQuestionType.ABCD,
|
type: QuizQuestionType.ABCD,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getLocalization } from '$lib/i18n';
|
import { getLocalization } from '$lib/i18n';
|
||||||
import { navbarVisible } from '$lib/stores';
|
import { navbarVisible } from '$lib/stores';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
navbarVisible.set(true);
|
navbarVisible.set(true);
|
||||||
|
|
||||||
@@ -74,6 +76,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$: console.log(file_input);
|
$: console.log(file_input);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let url_from_path = $page.url.searchParams.get('url');
|
||||||
|
if (url_from_path === '') {
|
||||||
|
url_from_path = null;
|
||||||
|
}
|
||||||
|
url_input = url_from_path ?? '';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|||||||
@@ -7,8 +7,12 @@
|
|||||||
import type { Data } from '$lib/quiztivity/types';
|
import type { Data } from '$lib/quiztivity/types';
|
||||||
import Editor from '$lib/quiztivity/editor.svelte';
|
import Editor from '$lib/quiztivity/editor.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
let data: Data = { pages: [], id: undefined, title: '' };
|
let title = $page.url.searchParams.get('title');
|
||||||
|
title ??= '';
|
||||||
|
|
||||||
|
let data: Data = { pages: [], id: undefined, title };
|
||||||
let saving = false;
|
let saving = false;
|
||||||
|
|
||||||
const save_quiztivity = async () => {
|
const save_quiztivity = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user