diff --git a/.github/actions/copy-examples-to-docs/copy-examples-to-docs.js b/.github/actions/copy-examples-to-docs/copy-examples-to-docs.js index 76bbf8d..7845112 100644 --- a/.github/actions/copy-examples-to-docs/copy-examples-to-docs.js +++ b/.github/actions/copy-examples-to-docs/copy-examples-to-docs.js @@ -4,7 +4,6 @@ import { join } from 'node:path' const rootDir = process.cwd() const examplesDir = join(rootDir, 'examples') -const distDir = join(rootDir, 'dist') const examplesInDocs = join(rootDir, 'docs/docs/examples') const examplesFile = join(rootDir, 'docs/static/examples.json') const examplesMap = {} @@ -26,7 +25,7 @@ async function copyDir(dirFullPath, dirLevels = []) { return toCopy.dirs.push(item) } - const codeMatched = item.match(/^(.*)\.(md|js)$/) + const codeMatched = item.match(/^(.*)\.(md|ts)$/) if (!codeMatched) { return toCopy.files.push(item) } @@ -53,64 +52,35 @@ async function copyDir(dirFullPath, dirLevels = []) { } async function copyCode(name, formats, dirFullPath, dirLevels) { - const { jsCode, jsonCode, rules } = formats.includes('js') - ? await readJsCode(name, dirFullPath, dirLevels) - : {} + const code = formats.includes('ts') + ? await readCode(name, dirFullPath, dirLevels) + : null - let mdCode = formats.includes('md') - ? await readFile(join(dirFullPath, `${name}.md`), 'utf8') - : `---\ntitle: ${rules[0].description}\n---` - - if (jsCode) { + if (!formats.includes('md')) return + let markdown = await readFile(join(dirFullPath, `${name}.md`), 'utf8') + if (code) { let key = dirLevels.concat(name).join('/') - mdCode += ` -\`\`\`typescript -${jsCode.replace('export const rules = () =>', 'let rules =')} -\`\`\` + markdown += ` +Example code: ( [Open in the online editor →](/editor?example=${key}) ) -Open and edit the code in [the online editor](/editor?example=${key}), -or copy the JSON below and [add it to Karabiner-Elements](https://karabiner-elements.pqrs.org/docs/manual/configuration/add-your-own-complex-modifications/) without changes: - -\`\`\`json -${jsonCode} +\`\`\`typescript +${code} \`\`\` ` const mdFile = join(examplesInDocs, ...dirLevels, `${name}.md`) - await writeFile(mdFile, mdCode) + await writeFile(mdFile, markdown) } } -async function readJsCode(name, dirFullPath, dirLevels) { - const fileName = `${name}.js` +async function readCode(name, dirFullPath, dirLevels) { + const fileName = `${name}.ts` const fullPath = join(dirFullPath, fileName) const srcCode = await readFile(fullPath, 'utf-8') const matched = srcCode.match(/^(import[\s\S]*?)from '.*?'\s*([\s\S]*)$/m) if (!matched) throw new Error(`Cannot parse ${dirLevels.concat(fileName).join('/')}`) - const [, imports, jsCode] = matched - addExample(name, dirLevels, jsCode) - - const fileInDist = join(distDir, fileName) - await writeFile(fileInDist, `${imports}from './index.js'\n${jsCode}`) - - const { complexModifications } = await import(join(distDir, 'index.js')) - const jsModule = await import(fileInDist) - const { rules } = complexModifications(jsModule.rules()) - const config = rules.reduce( - (r, v) => ({ - description: r.description - ? `${r.description}; ${v.description}` - : v.description, - manipulators: r.manipulators.concat(v.manipulators), - }), - { description: '', manipulators: [] }, - ) - const jsonCode = JSON.stringify(config, null, 2) - return { jsCode, jsonCode, rules } -} - -function addExample(name, dirLevels, code) { - const key = dirLevels.concat(name).join('/') - examplesMap[key] = code.replace('export const rules = () =>', 'let rules =') + const code = matched[2] + examplesMap[dirLevels.concat(name).join('/')] = code + return code } diff --git a/docs/src/editor/online-editor.tsx b/docs/src/editor/online-editor.tsx index e7b93a2..55c0498 100644 --- a/docs/src/editor/online-editor.tsx +++ b/docs/src/editor/online-editor.tsx @@ -11,16 +11,16 @@ import { } from 'react' let importsCode = `import { -// rule and layers -rule, layer, simlayer, hyperLayer, modifierLayer, duoLayer, -// from / map() -map, mapConsumerKey, mapPointingButton, mapSimultaneous, mapDoubleTap, mouseMotionToScroll, -// to -toKey, toConsumerKey, toPointingButton, toHyper, toSuperHyper, toMeh, to$, toApp, toPaste, toTypeSequence, toNone, toNotificationMessage, toRemoveNotificationMessage, toInputSource, toSetVar, toMouseKey, toMouseCursorPosition, toStickyModifier, toCgEventDoubleClick, toSleepSystem, -// conditions -ifApp, ifDevice, ifVar, ifDeviceExists, ifInputSource, ifKeyboardType, ifEventChanged, -// utils -withCondition, withMapper, withModifier, modifierKeyAliases, multiModifierAliases + // rule and layers + rule, layer, simlayer, hyperLayer, modifierLayer, duoLayer, + // from / map() + map, mapConsumerKey, mapPointingButton, mapSimultaneous, mapDoubleTap, mouseMotionToScroll, + // to + toKey, toConsumerKey, toPointingButton, toHyper, toSuperHyper, toMeh, to$, toApp, toPaste, toTypeSequence, toNone, toNotificationMessage, toRemoveNotificationMessage, toInputSource, toSetVar, toUnsetVar, toMouseKey, toMouseCursorPosition, toStickyModifier, toCgEventDoubleClick, toSleepSystem, + // conditions + ifApp, ifDevice, ifVar, ifDeviceExists, ifInputSource, ifKeyboardType, ifEventChanged, + // utils + withCondition, withMapper, withModifier, modifierKeyAliases, multiModifierAliases, LetterKeyCode, KeyAlias, ModifierKeyAlias, MultiModifierAlias } from 'karabiner.ts'` let playgroundCode = `\ diff --git a/examples/modifier-keys/caps_lock-to-hyper.js b/examples/modifier-keys/caps_lock-to-hyper.ts similarity index 83% rename from examples/modifier-keys/caps_lock-to-hyper.js rename to examples/modifier-keys/caps_lock-to-hyper.ts index 8d5e1e8..0f6b4ca 100644 --- a/examples/modifier-keys/caps_lock-to-hyper.js +++ b/examples/modifier-keys/caps_lock-to-hyper.ts @@ -1,6 +1,6 @@ import { map, rule } from '../../src' -export const rules = () => [ +let rules = [ rule('Caps Lock → Hyper').manipulators([ map('caps_lock').toHyper().toIfAlone('caps_lock'), ]), diff --git a/examples/modifier-keys/duo-modifiers.js b/examples/modifier-keys/duo-modifiers.js deleted file mode 100644 index ef5b157..0000000 --- a/examples/modifier-keys/duo-modifiers.js +++ /dev/null @@ -1,50 +0,0 @@ -import { - mapSimultaneous, - modifierKeyAliases, - multiModifierAliases, - rule, -} from '../../src' - -function duoModifier(keys, modifier) { - const [firstMod, ...restMods] = - modifier in modifierKeyAliases - ? [modifierKeyAliases[modifier]] - : multiModifierAliases[modifier] - return mapSimultaneous(keys.split('')).to(`left_${firstMod}`, restMods) -} - -export const rules = () => [ - rule('duo-modifiers').manipulators([ - duoModifier('fd', '⌘'), - duoModifier('fs', '⌃'), - duoModifier('fa', '⌥'), - - duoModifier('ds', '⇧'), - - duoModifier('gd', '⌘⇧'), - duoModifier('gs', '⌃⇧'), - duoModifier('ga', '⌥⇧'), - - duoModifier('vc', '⌘⌥'), - duoModifier('vx', '⌘⌃'), - duoModifier('cx', '⌥⌃'), - - duoModifier('vz', '⌘⌥⌃'), - - duoModifier('jk', '⌘'), - duoModifier('jl', '⌃'), - duoModifier('j;', '⌥'), - - duoModifier('kl', '⇧'), - - duoModifier('hk', '⌘⇧'), - duoModifier('hl', '⌃⇧'), - duoModifier('h;', '⌥⇧'), - - duoModifier('m,', '⌘⌥'), - duoModifier('m.', '⌘⌃'), - duoModifier(',.', '⌥⌃'), - - duoModifier('m/', '⌘⌥⌃'), - ]), -] diff --git a/examples/modifier-keys/duo-modifiers.md b/examples/modifier-keys/duo-modifiers.md index ff5a063..ce305c2 100644 --- a/examples/modifier-keys/duo-modifiers.md +++ b/examples/modifier-keys/duo-modifiers.md @@ -15,6 +15,6 @@ Adjust `basic.simultaneous_threshold_milliseconds` to your typing speed and habi ::: :::tip -When get started, it can be very useful to show a notification when the -mod(s) are activated, see [an example here](https://github.com/evan-liu/karabiner-config/blob/fab052dc5e738c91adfae46aabeb17b38f4959fe/utils.ts#L85). +When get started, it can be very useful to show a notification when the mod(s) +are activated. ::: diff --git a/examples/modifier-keys/duo-modifiers.ts b/examples/modifier-keys/duo-modifiers.ts new file mode 100644 index 0000000..ed8dffd --- /dev/null +++ b/examples/modifier-keys/duo-modifiers.ts @@ -0,0 +1,66 @@ +import { + KeyAlias, + LetterKeyCode, + mapSimultaneous, + ModifierKeyAlias, + modifierKeyAliases, + MultiModifierAlias, + multiModifierAliases, + rule, + toRemoveNotificationMessage, +} from '../../src' + +let rules = [ + rule('duo-modifiers').manipulators( + duoModifiers({ + '⌘': ['fd', 'jk'], // ⌘ first as used the most + '⌃': ['fs', 'jl'], // ⌃ second as Vim uses it + '⌥': ['fa', 'j;'], // ⌥ last as used the least + + '⇧': ['ds', 'kl'], + + '⌘⇧': ['gd', 'hk'], + '⌃⇧': ['gs', 'hl'], + '⌥⇧': ['ga', 'h;'], + + '⌘⌥': ['vc', 'm,'], + '⌘⌃': ['vx', 'm.'], + '⌥⌃': ['cx', ',.'], + + '⌘⌥⌃': ['vz', 'm/'], + }), + ), +] + +function duoModifiers( + v: Partial< + Record< + '⌘' | '⌥' | '⌃' | '⇧' | MultiModifierAlias, + `${LetterKeyCode | KeyAlias}${LetterKeyCode | KeyAlias}`[] + > + >, +) { + let result = [] + + for (let [m, k] of Object.entries(v)) { + for (let keys of k) { + let id = k + m + let [firstMod, ...restMods] = ( + m in modifierKeyAliases + ? [modifierKeyAliases[m as ModifierKeyAlias]] + : multiModifierAliases[m as MultiModifierAlias] + ) as Array<'command' | 'control' | 'option' | 'shift'> + + let to_after_key_up = [toRemoveNotificationMessage(id)] + result.push( + mapSimultaneous(keys.split('') as (LetterKeyCode | KeyAlias)[], { + to_after_key_up, + }) + .toNotificationMessage(id, m) // Must go first or to() doesn't work + .to(`left_${firstMod}`, restMods), + ) + } + } + + return result +} diff --git a/examples/os-functionality/launch-apps-layer.js b/examples/os-functionality/launch-apps-layer.ts similarity index 83% rename from examples/os-functionality/launch-apps-layer.js rename to examples/os-functionality/launch-apps-layer.ts index cca5b02..cd3baa6 100644 --- a/examples/os-functionality/launch-apps-layer.js +++ b/examples/os-functionality/launch-apps-layer.ts @@ -1,6 +1,6 @@ import { layer, toApp } from '../../src' -export const rules = () => [ +let rules = [ layer('l', 'launch-app').manipulators({ c: toApp('Calendar'), f: toApp('Finder'), diff --git a/examples/os-functionality/launch-apps-modifier.js b/examples/os-functionality/launch-apps-modifier.ts similarity index 87% rename from examples/os-functionality/launch-apps-modifier.js rename to examples/os-functionality/launch-apps-modifier.ts index f2aa397..77e8196 100644 --- a/examples/os-functionality/launch-apps-modifier.js +++ b/examples/os-functionality/launch-apps-modifier.ts @@ -1,6 +1,6 @@ import { rule, toApp, withModifier } from '../../src' -export const rules = () => [ +let rules = [ rule('Launch Apps').manipulators([ withModifier('right_control')({ c: toApp('Calendar'), diff --git a/examples/text-input/emoji.js b/examples/text-input/emoji.ts similarity index 80% rename from examples/text-input/emoji.js rename to examples/text-input/emoji.ts index 87bc7b8..5f6eeb4 100644 --- a/examples/text-input/emoji.js +++ b/examples/text-input/emoji.ts @@ -1,6 +1,6 @@ import { layer, toPaste } from '../../src' -export const rules = () => [ +let rules = [ layer('z', 'emoji').manipulators({ j: toPaste('😂'), // joy }), diff --git a/examples/text-input/symbols.js b/examples/text-input/symbols.ts similarity index 93% rename from examples/text-input/symbols.js rename to examples/text-input/symbols.ts index de4416b..a9dba0f 100644 --- a/examples/text-input/symbols.js +++ b/examples/text-input/symbols.ts @@ -1,6 +1,6 @@ import { layer, map, toPaste, withMapper } from '../../src' -export const rules = () => [ +let rules = [ layer('z', 'symbols').manipulators([ withMapper(['←', '→', '↑', '↓', '␣', '⏎', '⌫', '⌦'])((k) => map(k).toPaste(k), diff --git a/examples/vim/_category_.json b/examples/vim/_category_.json new file mode 100644 index 0000000..69c8032 --- /dev/null +++ b/examples/vim/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Vim" +} diff --git a/examples/vim/leader.md b/examples/vim/leader.md new file mode 100644 index 0000000..5244ca8 --- /dev/null +++ b/examples/vim/leader.md @@ -0,0 +1,8 @@ +--- +title: Leader key +--- + +To nest leader keys, use variables on [leaderMode()](/rules/leader-mode). + +- `leader` `o` `f`: Open Finder +- `leader` `r` `e`: Raycast Emoji Picker diff --git a/examples/vim/leader.ts b/examples/vim/leader.ts new file mode 100644 index 0000000..2ca992f --- /dev/null +++ b/examples/vim/leader.ts @@ -0,0 +1,33 @@ +import { + hyperLayer, + ifVar, + map, + toUnsetVar, + withCondition, + withMapper, +} from '../../src' + +let escapeLeader = ['__layer', 'leader', 'leader--'].map(toUnsetVar) +let raycastEmoji = 'emoji-symbols/search-emoji-symbols' + +let rules = [ + hyperLayer('l', 'leader') + .leaderMode({ sticky: true, escape: [] }) + .manipulators([ + map('escape').to(escapeLeader), + + map('o').toVar('leader--', 'o'), // Open + withCondition(ifVar('leader--', 'o'))([ + withMapper([ + map('f').toApp('Finder'), // Open Finder + ])((x) => x.to(escapeLeader)), + ]), + + map('r').toVar('leader--', 'r'), // Raycast + withCondition(ifVar('leader--', 'r'))([ + withMapper([ + map('e').to$(`open raycast://extensions/raycast/${raycastEmoji}`), + ])((x) => x.to(escapeLeader)), + ]), + ]), +]