/** * TODO: add arrows, tab, enter tests for Google Sheets * */ /// declare const PluginBase: IPluginBase; export function backendPressKey(...keyWModifiers: string[][]) { chrome.runtime.sendMessage({ type: "pressKeys", payload: { keyWModifiers, nonChar: true }, }); } function pressKey(name: string): boolean { backendPressKey([name]); return true; // const activeEle = document.activeElement; // console.log(activeEle); // if (activeEle) { // const code = key.charCodeAt(0); // const evtDeets = { // bubbles: true, // cancelable: true, // key, // code: key, // location: 0, // // @ts-ignore // keyCode: code, // // deprecated, but we include it // which: code, // } // activeEle.dispatchEvent(new KeyboardEvent("keydown", evtDeets)); // activeEle.dispatchEvent(new KeyboardEvent("keyup", evtDeets)); // activeEle.dispatchEvent(new KeyboardEvent("keypress", evtDeets)); // return true; // } // return false; } /** * console.log(['Ctrl+P', 'ctrl++', 'ctrl+home+end', 'ctrl+shift+t', '+++', '++Ctrl'].map(x => splitByPlusses(x))) */ function splitBySingleCharSeparator(s: string, separator = "+") { const parts: string[] = []; let indexOfSplitter = -1; do { const prevIndex = indexOfSplitter + 1; indexOfSplitter = s.indexOf(separator, prevIndex + 1); let part: string; if (indexOfSplitter !== -1) { part = s.substring(prevIndex, indexOfSplitter); } else { part = s.substring(prevIndex); } parts.push(part); } while (indexOfSplitter !== -1); return parts; } /** * Showing modifier keys is coming soon: * * https://github.com/wesbos/keycodes/issues/290 */ async function keyComboTest(keysStrSeq: string, t, say, client) { // console.log("1", t, "2", say, "3", client, "4", keys); await client.url(`https://keycode.info/`); say(`press ${keysStrSeq}`); const code = (await (await client.$(".keycode-display")).getText()) .trim() .toLowerCase(); const key = ( await (await client.$(".card.item-key .main-description")).getText() ) .trim() .toLowerCase(); // const keyCodeAndMod = keyStrSeqToCodeAndMod(keysStrSeq); // // console.log("keyCodeAndMod", keyCodeAndMod, "key", key, "code", code); // t.is(keyCodeAndMod.key, key); // t.is(keyCodeAndMod.code, code); } export default { ...PluginBase, ...{ niceName: "Keyboard", description: "For pressing individual keyboard buttons with your voice.", version: "4.10.0", apiVersion: 2, match: /.*/, authors: "Miko", homophones: { // causes issues with "press tab" // 'preston': 'press down', pressed: "press", dress: "press", "present tab": "press tab", }, commands: [ { name: "Press Key Combination", description: 'Simulate pressing keyboard keys. Keys should separated by the "+" symbol (e.g. "press ctrl+p" or "press alt+shift+tab"). Examples of special keys: left arrow, enter, tab, home, end, page down, ctrl, alt, shift, f1, backspace, delete.', match: "press *", pageFn: (transcript, { preTs, normTs }: TsData) => { console.log("pressing", preTs); const keys = splitBySingleCharSeparator(preTs); // debugger backendPressKey(keys); }, test: { // manually tested: // passed: // ctrl+shift+tab, all arrows, home, end, enter, page up, page down, // backspace, delete, ctrl+shift+a, f keys // failed: // alt+tab, escape, print screen "Ctrl+P": keyComboTest.bind(null, "Ctrl+P"), "Cmd+J": keyComboTest.bind(null, "Cmd+J"), "Down Arrow": keyComboTest.bind(null, "ArrowDown"), }, }, { name: "Press Tab", description: "Equivalent of hitting the tab key.", match: "press tab", pageFn: () => { if (!pressKey("tab")) backendPressKey(["tab"]); }, test: { "go to next form field": async (t, say, client) => { await client.url(`${t.context.localPageDomain}/forms.html`); await (await client.$("#simple input")).click(); await say(); t.is( await ( await client.$(await client.getActiveElement()) ).getAttribute("type"), "password" ); }, }, }, { name: "Press Enter", description: "Equivalent of hitting the enter key.", match: "press enter", pageFn: () => { if (!pressKey("enter")) backendPressKey(["enter"]); }, test: { contenteditables: async (t, say, client) => { await client.url(`${t.context.localPageDomain}/text-input.html`); const editor = await client.$("#editor"); await editor.clearValue(); const [before, _] = await Promise.all([ t.context.getInnerText(client, "#editor"), editor.click(), ]); await say(); const after = await t.context.getInnerText(client, "#editor"); t.true( after.includes("\n"), `before: "${before}" after: "${after}"` ); t.not(before, after); }, "google search": async (t, say, client) => { await client.url("https://www.google.com"); await say("lipsurf"); await say(); const newUrl = await client.getUrl(); t.true( newUrl.includes("/search?"), `seems search was not submitted` ); }, }, }, { name: "Press Down", description: "Equivalent of hitting the down arrow key.", match: "press down", pageFn: () => { // gmail down arrow needs forcus when selecting recipient if (!pressKey("down")) // not sure of the use case for this backendPressKey(["down"]); }, test: { "press down": keyComboTest.bind(null, "press down"), }, }, { name: "Press Up", description: "Equivalent of hitting the up arrow key.", match: "press up", pageFn: () => { if (!pressKey("up")) backendPressKey(["up"]); }, }, { name: "Press Left", description: "Equivalent of hitting the left arrow key.", match: "press left", pageFn: () => { if (!pressKey("left")) backendPressKey(["left"]); }, }, { name: "Press Right", description: "Equivalent of hitting the right arrow key.", match: "press right", pageFn: () => { if (!pressKey("right")) backendPressKey(["right"]); }, }, ], }, };