Skip to content

Commit

Permalink
📝 [docs] Add example for nested leader keys
Browse files Browse the repository at this point in the history
  • Loading branch information
evan-liu committed Feb 14, 2025
1 parent 9adc650 commit 674a8bb
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 114 deletions.
64 changes: 17 additions & 47 deletions .github/actions/copy-examples-to-docs/copy-examples-to-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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)
}
Expand All @@ -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
}
20 changes: 10 additions & 10 deletions docs/src/editor/online-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `\
Expand Down
Original file line number Diff line number Diff line change
@@ -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'),
]),
Expand Down
50 changes: 0 additions & 50 deletions examples/modifier-keys/duo-modifiers.js

This file was deleted.

4 changes: 2 additions & 2 deletions examples/modifier-keys/duo-modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
:::
66 changes: 66 additions & 0 deletions examples/modifier-keys/duo-modifiers.ts
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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'),
Expand Down
Original file line number Diff line number Diff line change
@@ -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'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { layer, toPaste } from '../../src'

export const rules = () => [
let rules = [
layer('z', 'emoji').manipulators({
j: toPaste('😂'), // joy
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
Expand Down
3 changes: 3 additions & 0 deletions examples/vim/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"label": "Vim"
}
8 changes: 8 additions & 0 deletions examples/vim/leader.md
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions examples/vim/leader.ts
Original file line number Diff line number Diff line change
@@ -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)),
]),
]),
]

0 comments on commit 674a8bb

Please sign in to comment.