{"id":13468633,"url":"https://github.com/kentcdodds/genie","last_synced_at":"2025-04-13T11:47:44.874Z","repository":{"id":10243793,"uuid":"12348888","full_name":"kentcdodds/genie","owner":"kentcdodds","description":"Keyboard control for web applications (better than cryptic shortcuts). 3.5K minified \u0026 gzipped","archived":false,"fork":false,"pushed_at":"2021-01-25T23:14:02.000Z","size":2981,"stargazers_count":571,"open_issues_count":4,"forks_count":45,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-04-04T05:47:08.282Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://kentcdodds.github.io/genie","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kentcdodds.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-08-24T19:49:47.000Z","updated_at":"2025-02-20T09:12:40.000Z","dependencies_parsed_at":"2022-07-31T04:37:57.433Z","dependency_job_id":null,"html_url":"https://github.com/kentcdodds/genie","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kentcdodds%2Fgenie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kentcdodds%2Fgenie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kentcdodds%2Fgenie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kentcdodds%2Fgenie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kentcdodds","download_url":"https://codeload.github.com/kentcdodds/genie/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248710409,"owners_count":21149186,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T15:01:15.410Z","updated_at":"2025-04-13T11:47:44.845Z","avatar_url":"https://github.com/kentcdodds.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\u003ch1\u003eGenieJS 🧞\u003c/h1\u003e\n\n\u003cimg src=\"https://rawgit.com/kentcdodds/genie/master/other/logo.png\" alt=\"genie logo\" title=\"genie logo\" width=\"300\"\u003e\n\n\u003cp\u003eA JavaScript library committed to improving user experience by empowering users to interact with web apps using the keyboard (better than cryptic shortcuts).\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eGenie\u003c/i\u003e |ˈjēnē| (noun): a spirit of Arabian folklore, as traditionally depicted imprisoned\nwithin a bottle or oil lamp, and capable of granting wishes when summoned.\u003c/p\u003e\n\u003c/div\u003e\n\n---\n\n\u003c!-- prettier-ignore-start --\u003e\n[![Build Status][build-badge]][build]\n[![Code Coverage][coverage-badge]][coverage]\n[![version][version-badge]][package]\n[![downloads][downloads-badge]][npmtrends]\n[![MIT License][license-badge]][license]\n\n\u003c!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --\u003e\n[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:END --\u003e\n[![PRs Welcome][prs-badge]][prs]\n[![Code of Conduct][coc-badge]][coc]\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003e Old links:\n\u003e\n\u003e - [Demo](http://kentcdodds.github.io/genie/)\n\u003e   ([Demo with React and Downshift](https://codesandbox.io/s/jrlkrxwgl))\n\u003e - [Tests](http://kentcdodds.github.io/genie/tests/testrunner.html)\n\u003e - [Genie Workshop](http://kentcdodds.github.io/genie/workshop) - Terrific way\n\u003e   to learn how to use genie right in your browser.\n\u003e - [API Docs](http://kentcdodds.github.io/genie/autodoc)\n\u003e - [Chrome Extension](https://chrome.google.com/webstore/detail/genies-lamp/pimmaneflgfbknjkjdlnffagpgfeklko)\n\n## The problem\n\nYou want to enable users to power through your application with the keyboard,\nbut you're limited on the kinds of reasonable keyboard shortcuts you can use.\n\n## This solution\n\nGenieJS is a library to emulate the same kind of behavior seen in apps like\n[Alfred](http://www.alfredapp.com/). Essentially, you register actions\nassociated with keywords. Then you can request the genie to perform that action\nbased on the best keyword match for a given keyword.\n\nOver time, the genie will learn the actions more associated with specific\nkeywords and those will be come first when a list of matching actions is\nrequested. If that didn't make sense, don't worry, hopefully the tutorial,\ntests, and demo will help explain how it works.\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Vernacular](#vernacular)\n- [How to use it](#how-to-use-it)\n- [API](#api)\n  - [Objects](#objects)\n- [Special Wish Actions](#special-wish-actions)\n  - [Navigation](#navigation)\n- [About Matching Priority](#about-matching-priority)\n- [About Optimistic Anticipation](#about-optimistic-anticipation)\n- [About Context](#about-context)\n  - [Path Context](#path-context)\n- [Enabling \u0026 Disabling](#enabling--disabling)\n- [Merging Wishes](#merging-wishes)\n- [Inspiration](#inspiration)\n- [Other Solutions](#other-solutions)\n- [Issues](#issues)\n  - [🐛 Bugs](#-bugs)\n  - [💡 Feature Requests](#-feature-requests)\n- [Contributors ✨](#contributors-)\n- [LICENSE](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Vernacular\n\n_Wish_: An object with an id, action, and magic words.\n\n_Action_: What to call when this wish is to be executed.\n\n_Magic Word_: Keywords for a wish used to match it with given magic words.\n\n_On Deck_: The second wish of preference for a certain magic word which will be\nKing of the Hill if chosen again.\n\n_King of the Hill_: The wish which gets preference for a certain magic word\nuntil the On Deck wish is chosen again (it then becomes On Deck).\n\n## How to use it\n\nIf you're using [RequireJS](http://requirejs.org/) then you can simply\n`require('path/to/genie')`. Or you could simply include the regular script tag:\n\n```html\n\u003cscript src=\"./bower_components/genie.js\"\u003e\u003c/script\u003e\n\u003c!-- This will place `genie` on the global namespace for your delight. --\u003e\n```\n\n`genie` is a function with a few useful functions as properties of `genie`. The\nflow of using GenieJS is simple:\n\n```javascript\n/* Register wishes */\n// One magic word\nvar trashWish = genie({\n  magicWords: 'Take out the trash',\n  action: function () {\n    console.log('Yes! I love taking out the trash!')\n  },\n})\n// Multiple magic words\nvar vacuumWish = genie({\n  magicWords: ['Get dust out of the carpet', 'vacuum'],\n  action: function () {\n    console.log('Can NOT wait to get that dust out of that carpet!')\n  },\n})\n\n/* Get wishes based on magic word matches */\ngenie.getMatchingWishes('vacuum') // returns [vacuumWish];\ngenie.getMatchingWishes('out') // returns [trashWish, vacuumWish];\n\n// Make wish based on wish object or id of wish object\ngenie.makeWish(trashWish.id) // logs: 'Yes! I love taking out the trash!'\ngenie.makeWish(vacuumWish) // logs: 'Can NOT wait to get that dust out of that carpet!'\n```\n\nSo far it doesn't look too magical, but the true magic comes in the form of\ngenie giving preference to wishes that were recently chosen with a given\nkeyword. To do this, you need to provide genie with a magic word to associate\nthe wish with, like so:\n\n```javascript\ngenie.makeWish(vacuumWish, 'out') // logs as above\ngenie.getMatchingWishes('out') // returns [vacuumWish, trashWish]; \u003c-- Notice difference from above\n```\n\nAs you'll notice, the order of the two wishes is changed because genie gave\npreference to the `vacuumWish` because the last time `makeWish` was called with\nthe the `'out'` magic word, `vacuumWish` was the wish given.\n\nThis behavior simulates apps such as [Alfred](http://www.alfredapp.com/) which\nis the goal of this library!\n\n## API\n\nGenie is undergoing an overhaul on the API documentation using\n[autodocs](https://github.com/dtao/autodoc). It is still being worked on, but\nyou can see that documentation\n[here](http://kentcdodds.github.io/genie/autodoc).\n\nBelow you can see full documentation. It's just less enjoyable to read...\n\n### Objects\n\nThere are a few internal objects you may want to be aware of:\n\n```javascript\nvar wishObject = {\n  id: 'string',\n  data: {\n    timesMade: {\n      total: 0,\n      magicWords: {\n        'Magic Word': 1,\n      },\n    },\n  },\n  context: {\n    all: ['string'],\n    any: ['string'],\n    none: ['string'],\n  },\n  keywords: ['string'],\n  action: function (wish, magicWord) {},\n}\n\nvar enteredMagicWords = {\n  h: {\n    wishes: ['wishid1', 'wishid2'],\n    e: {\n      l: {\n        l: {\n          o: {\n            wishes: ['wishid3', 'wishid4'],\n          },\n        },\n        p: {\n          wishes: ['wishid5', 'wishid2'],\n        },\n      },\n    },\n  },\n}\n\nvar pathContext = {\n  paths: ['string'],\n  regexes: [/regex/gi],\n  contexts: 'The context to apply',\n}\n```\n\nYou have the following api to use at your discretion:\n\n```javascript\n/*\n * If no id is provided, one will be auto-generated via the previousId + 1\n * Genie adds a \"timesMade\" property to the \"data\" property\n *   This is incremented every time the wish is made\n *   (when the action is called)\n * You can also provide genie with an array of these objects\n *   to register all of them at once.\n * Returns the wish object.\n */\ngenie({\n  id: string | optional,\n  data: object | optional,\n  context: string || [string] || {\n    all: [string],\n    any: [string],\n    none: [string]\n  } | optional,\n  action: function | required,\n  magicWords: string || [string] | required\n});\n\n/*\n * Removes the wish from the registered wishes and the enteredMagicWords\n * Returns the deregisteredWish\n */\ngenie.deregisterWish(id [string || wishObject | required]);\n\n/*\n * Removes all wishes which have any of the given context(s).\n */\ngenie.deregisterWishesWithContext(context [string || array | required]);\n\n/*\n * calls options() with default options and returns the old options\n */\ngenie.reset();\n\n/*\n * Returns an array of wishes which match in order:\n *  1. Most recently made wishes with the given magicWord\n *  2. Following the order of their initial registration\n */\ngenie.getMatchingWishes(magicWord [string | required]);\n\n/*\n * Replace genie's matching algorithm with your own.\n * Gives you three parameters:\n *   - wishes (all genie wishes)\n *   - magicWord (the magicWord to match)\n *   - context (genie's current context)\n *   - enteredMagicWords (genie's current enteredMagicWords object)\n */\ngenie.overrideMatchingAlgorithm(function(wishes, magicWord, context, enteredMagicWords) {\n  // Your matching code.\n});\n\n/*\n * Sets the matching algorithm back to genie's default algorithm.\n */\ngenie.restoreMatchingAlgorithm();\n\n/*\n * Executes the given wish's action.\n * If a magicWord is provided, adds the given wish to the enteredMagicWords\n *   to be given preferential treatment of order in the array returned\n *   by the getMatchingWishes method.\n * Returns the wish object.\n */\ngenie.makeWish(id [string || wishObject | required], magicWord [string | optional]);\n\n/*\n * Returns wishes in the given context. This is what would be returned in the\n *   case genie's context were the given context and you called\n *   genie.getMatchingWishes(); (no args).\n * If no context is provided, all wishes are returned.\n * Returns wishes as an array\n */\ngenie.getWishesInContext(context [string || array | optional]);\n\n\n/*\n * Get wishes which have {type} of {context} in their context.{wishContextType}\n * type can be: 'any', 'all', or 'none' and refers to the context given\n * wishContextType can be a string or array with: 'any', 'all', or 'none'\n *   and refers to the which of the wish's contexts to search. If not provided\n *   genie combines all three. If a wish doesn't have a context object, but an\n *   array or string instead, it treats that as if it were 'any' (as expected).\n *\n * This is a pretty complicated api, so here are a few examples\n * ex. getWishesWithContext('animal', 'none', 'all')\n *   This would find all wishes which have no don't have the context 'animal' in their 'all'\n *   context property\n * ex. getWishesWithContext('fred')\n *   This would find all wishes which have the context 'fred' in any of their contexts\n *   (including none).\n * ex. getWishesWithContext('tom', 'any', ['all', 'none'])\n *   This would find all wishes which have the context 'tom' in their 'all' or 'none' contexts.\n * ex. getWishesWithContext(['tom', 'fred'], 'all')\n *   This would find all wishes which have both the context 'tom' and 'fred' in any of their contexts.\n * ex. getWishesWithContext(['orange', 'apple'], 'none', 'any')\n *   This would find all wishes which do not have 'orange' or 'apple' in their 'any' context.\n */\ngenie.getWishesWithContext(context [string || array | required], type [string | optional | 'any'], wishContextTypes) {\n\n/*\n * Get a specific wish by an id.\n * If the id is an array, returns an array\n *   of wishes with the same order as the\n *   given array.\n * Note: If the id does not correspond to\n *   a registered wish, it will be undefined\n */\ngenie.getWish(id [string || array | required]);\n\n/*\n * Allows you to set the attributes of genie and returns the current genie options.\n *  1. wishes: All wishes (wishObject described above) currently registered\n *  2. noWishMerge: Instead of adding wishes, replace the current list of wishes with the given list.\n *    More about this below...\n *  3. previousId: The number used to auto-generate wish Ids if an id is not\n *    provided when a wish is registered.\n *  4. enteredMagicWords: All magicWords which have been associated with wishes\n *    to give preferential treatment in the order of wishes returned by getMatchingWishes\n *  5. context: The current context of the genie. See below about how context affects wishes\n *  6. enabled: Control whether genie's functions will actually run\n *  7. returnOnDisabled: If enabled is set to false and this is true, will return an empty\n *    object/array/string to prevent the need to do null/undefined checking wherever genie\n *    is used.\n */\ngenie.options({\n  wishes: object | optional,\n  noWishMerge: boolean | optional,\n  previousId: number | optional,\n  enteredMagicWords: object | optional,\n  context: string | optional,\n  enabled: boolean | optional,\n  returnOnDisabled: boolean | optional\n});\n\n/*\n * Merges the given wishes with existing wishes. (See Merging Wishes below)\n */\ngenie.mergeWishes(wishes);\n\n/*\n * Sets and returns the current context to newContext if provided\n * Also sets an internal variable: _previousContext for the revertContext function\n */\ngenie.context(newContext [string || array | optional]);\n\n/*\n * Adds the context(s) to genie's current context\n */\ngenie.addContext(newContext [string || array | optional]);\n\n/*\n * Removes the context(s) to genie's current context\n */\ngenie.removeContext(newContext [string || array | optional]);\n\n/*\n * Sets and returns the current context to the default context: ['universe']\n */\ngenie.restoreContext();\n\n/* Sets and returns the current context to the previous context\n * The previous context is updated when context, addContext,\n *   removeContext, restoreContext, and revertContext are called.\n */\ngenie.revertContext();\n\n/* See more about how this works below in the context section\n *   This will update the current context with the given path\n *   noDeregister is used to prevent this from deregistering\n *     wishes which are not in the path's context.\n */\ngenie.updatePathContext(path, noDeregister);\n\n/*\n * Adds a path context (or array of them) to genie's _pathContext array\n */\ngenie.addPathContext(pathContext);\n\n/*\n * Removes a path context (or array of them) from genie's _pathContext array\n */\ngenie.removePathContext(pathContext);\n\n/*\n * Sets and returns the enabled state\n */\ngenie.enabled(boolean | optional);\n\n/*\n * Sets and returns the returnOnDisabled state\n */\ngenie.returnOnDisabled(boolean | optional);\n```\n\n## Special Wish Actions\n\nThere are some actions that are common use cases, so genie helps with these\n(currently only one special wish action):\n\n### Navigation\n\nYou for the action of the wish you can provide either a string (URL) or an\nobject with a destination property (URL). If the action is an object this gives\nyou a few options:\n\n- openNewTab - If truthy, this will open the URL using '\\_blank'. Otherwise\n  opens in the current window.\n- That's all for now... any [other](https://github.com/kentcdodds/genie/pulls)\n  [ideas?](https://github.com/kentcdodds/genie/issues)\n\n## About Matching Priority\n\nThe wishes returned from `getMatchingWishes` are ordered with the following\npriority\n\n1. King of the Hill for the given `magicWords` (genie optimistically anticipates\n   this as well)\n2. On Deck for the given `magicWords` (also optimistically anticipated)\n3. If the given magic word is equal to any magic words of a wish\n4. If the given magic word is the start to any magic word of a wish (i.e. 'he'\n   in 'hello');\n5. If the given magic word is the start to any word in a magic word (i.e. 'wo'\n   in 'hello world');\n6. If the given magic word is contained in any magic words of a wish\n7. If the given magic word is an acronym of any magic words of a wish\n8. If the given magic word matches the order of characters in any magic words of\n   a wish.\n\nJust trust the genie. He knows best. And if you think otherwise,\n[let me know](https://github.com/kentcdodds/genie/issues) or (even better)\n[contribute](https://github.com/kentcdodds/genie/pulls) :)\n\n## About Optimistic Anticipation\n\nGenie keeps track of which wishes were executed with which magic words so it\nknows which wish is \"King of the Hill\" and \"On Deck.\" But it's not a simple\nstring-to-string comparison. If I have a wish with the magic words of\n`'Do laundry'` and another with `'Laundry stinks`' then make the `'Do laundry`'\nwish with `'laundry`', I would have to type the entire word `'laundry`' before\n`'Do laundry'` came up to the top. So genie will anticipate that what I'm typing\nto be `'laundry'` until I type something that renders this impossible (like if I\ntype `'lan'`, it will anticipate `'laundry`' until I type the `'n'` and keep\n`'Do laundry'` at the top until I do).\n\nThis is possible because the structure of object that genie uses to keep track\nof entered magic words:\n\n```json\n\"enteredMagicWords\": {\n  \"w\": {\n    \"i\": {\n      \"s\": {\n        \"h\": {\n          \"wishes\": [\n            \"g-4\",\n            \"g-3\"\n          ]\n        }\n      },\n      \"wishes\": [\n        \"g-5\"\n      ]\n    }\n  }\n}\n```\n\nIf you're curious, look in the code :-)\n\n## About Context\n\nGenie has a concept of context that allows you to switch between sets of wishes\neasily. It's a toss up between context and the matching algorithm on which is\nmore complex but hopefully I can explain it well enough for you! Each wish is\ngiven the default context which is `universe` unless one is provided when it is\nregistered. Wishes will only behave normally in `getMatchingWishes` and\n`makeWish` when they are in context.\n\nThe easiest way to think of a wish context is that it is structured like so:\n\n```javascript\n{\n  all: ['context1', 'context2'],\n  any: ['context3', 'context4'],\n  none: ['context5', 'context6']\n}\n```\n\nIf you set a wish's context to a string or array of strings, it behaves like so:\n\n```javascript\n// what you set:\nvar wish = genie({\n  context: ['context1', 'context2'],\n})\n\nconsole.log(wish.context) // logs {any: ['context', 'context2']}\n```\n\nThere are a few ways for a wish to **definitely** be in context:\n\n1.  Genie's current context is the default context\n2.  The wish's context is the default context (does not apply if it simply\n    contains the default context)\n3.  The wish's context is equal to the current context\n\nIf none of these are true, then these things must be true for the wish to be in\ncontext:\n\n1.  Genie's context does **not** contain any of the wish's `context.none`\n    contexts if it exists.\n2.  Genie's context contains **at least one** of the wish's `context.any`\n    contexts if it exists.\n3.  Genie's context contains **all** of the wish's `context.all` contexts if it\n    exists.\n\nCheckout [the tests](src/__tests__/index.js) for #context to see more how this\nworks. Here's a simple demonstration:\n\n```javascript\n// Simple stuff\n\n// Before setting context, genie.context is default\nwish0.context // returns the default context\nwish1.context = 'context1'\nwish2.context = ['context1', 'context2']\nwish3.context = 'context3'\n\ngenie.getMatchingWishes() // returns [wish0, wish1, wish2, wish3]\n\ngenie.context('context1')\ngenie.getMatchingWishes() // returns [wish0, wish1, wish2]\n\ngenie.context('context2')\ngenie.getMatchingWishes() // returns [wish0, wish2]\n\ngenie.context('context3')\ngenie.getMatchingWishes() // returns [wish0, wish3]\n\ngenie.context(['context1', 'context2'])\ngenie.getMatchingWishes() // returns [wish0, wish1, wish2]\n\ngenie.context(['context1', 'context3'])\ngenie.getMatchingWishes() // returns [wish0, wish1, wish2, wish3]\n```\n\n```javascript\n// Complex stuff\n\ngenie.context = ['context1', 'context2', 'context3', 'context4']\n\nwish0.context // returns the default context\nwish1.context = {\n  any: ['context2', 'context5'],\n}\nwish2.context = {\n  none: ['context3', 'context5'],\n}\nwish3.context = {\n  all: ['context1', 'context5'],\n}\n\ngenie.getMatchingWishes() // returns [wish0, wish1]\n\ngenie.context(['context5', 'context1'])\ngenie.getMatchingWishes() // returns [wish0, wish1, wish3]\n\ngenie.context(['context2'])\ngenie.getMatchingWishes() // returns [wish0, wish1, wish2]\n\ngenie.restoreContext() // resets genie's context to default\ngenie.getMatchingWishes() // returns [wish0, wish2]\n```\n\n### Path Context\n\nA big use case for context is to have a url path (or route) represent the\ncontext for genie. For example, if you have an email app, you can have the\n`/index` and the `/message/:id` routes which would have different contexts.\nInstead of managing this yourself, genie can help you a little. Genie will not\nwatch the URL for you, so you have to do that yourself. This is by design. At\nany time, you can call `genie.updatePathContext(window.location.pathname)` and\ngenie will update the context based on an internal variable called\n`_pathContexts`. You have control over what's in this array using the\n`genie.addPathContext(pathContext)` and the\n`genie.removePathContext(pathContext)` methods. A `pathContext` object looks\nlike this:\n\n```javascript\n{\n  paths: string || array of strings | optional (either this or regexes),\n  regexes: regex || array of regexes | optional (either this or paths),\n  contexts: string || array of strings | required\n}\n```\n\nThe `contexts` variable is special and is associated with the regexes variable.\nThe easiest way to describe this is via an example:\n\nIf I have a pathContext object like this:\n\n```javascript\n{\n  regexes: [\n    /\\/pizza\\/(-\\d+|\\d+)/gi,\n    /\\/pizza\\/(pepperoni)/gi\n  ],\n  contexts: 'a-page-{{1}}'\n}\n```\n\nThen, when I call `genie.updatePathContext('/pizza/1234')` it will match this\npathContext and genie will automatically change `a-page-{{1}}` to `a-page-1234`.\n\nThe `1` in `a-page-{{1}}` represents the group that is matched on the path in\nthe regex. It will replace the digit in `{{\\d}}` with the group that's matched\n(Note: in true JavaScript form, group 0 represents the entire match string,\nhence, 1 is the first group in parentheses).\n\n## Enabling \u0026 Disabling\n\nTo give you a little more control, you can enable and disable genie globally.\nAll genie functions go through a check to make sure genie is enabled. If it is\nenabled, everything works as expected. If it is disabled, then genie will return\nan empty object/array/string depending on what the function you're calling is\nexpecting. This behavior is to prevent the need to do null/undefined checking\neverywhere you use `genie` and can be disabled as well via the returnOnDisabled\nfunction.\n\n## Merging Wishes\n\nTo persist the user's experience, you may want to store the result of\n`genie.options()` in `localStorage` or even a database associated with the user.\nThen after you have registered all the wishes for the user you load the options\nby calling `genie.options({wishes: usersOptions})`. The problem with this is\nthat `usersOptions` wont have the actions for wishes, so this would overwrite\nthe wishes with a bunch that don't have actions.\n\nTo prevent this, by default when you call `genie.options` genie will merge the\nwishes. So any new wishes provided will either overwrite wishes with the same\nID, but preserve the action of the old version if the new version doesn't have\nan action already. It will also preserve wishes which existed before and don't\nhave matching ids.\n\nTo completely overwrite the existing wishes, simply pass in `noWishMerge` along\nwith the wishes.\n\nNote: Genie provides direct access to the `mergeWishes` function as well.\n\n## Inspiration\n\nI built this after I was trying to add keyboard shortcuts to an application at\nwork and ran out of letters that made sense. I was heavily inspired by\n[Alfred](http://www.alfredapp.com/).\n\n## Other Solutions\n\nSimilar solutions we know of:\n\n- https://github.com/mixmaxhq/frecency\n\nIf you are aware of other solutions please [make a pull request][prs] and add it\nhere!\n\n## Issues\n\n_Looking to contribute? Look for the [Good First Issue][good-first-issue]\nlabel._\n\n### 🐛 Bugs\n\nPlease file an issue for bugs, missing documentation, or unexpected behavior.\n\n[**See Bugs**][bugs]\n\n### 💡 Feature Requests\n\nPlease file an issue to suggest new features. Vote on feature requests by adding\na 👍. This helps maintainers prioritize what to work on.\n\n[**See Feature Requests**][requests]\n\n## Contributors ✨\n\nThanks goes to these people ([emoji key][emojis]):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://kentcdodds.com\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/1500684?v=3\" width=\"100px;\" alt=\"Kent C. Dodds\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eKent C. Dodds\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/kentcdodds/genie/commits?author=kentcdodds\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/kentcdodds/genie/commits?author=kentcdodds\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"#infra-kentcdodds\" title=\"Infrastructure (Hosting, Build-Tools, etc)\"\u003e🚇\u003c/a\u003e \u003ca href=\"https://github.com/kentcdodds/genie/commits?author=kentcdodds\" title=\"Tests\"\u003e⚠️\u003c/a\u003e \u003ca href=\"#talk-kentcdodds\" title=\"Talks\"\u003e📢\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://twitter.com/swyx\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/6764957?v=4\" width=\"100px;\" alt=\"swyx\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eswyx\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/kentcdodds/genie/commits?author=sw-yx\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://stackshare.io/jdorfman/decisions\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/398230?v=4\" width=\"100px;\" alt=\"Justin Dorfman\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJustin Dorfman\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#fundingFinding-jdorfman\" title=\"Funding Finding\"\u003e🔍\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://michaeldeboey.be\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/6643991?v=4\" width=\"100px;\" alt=\"Michaël De Boey\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eMichaël De Boey\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/kentcdodds/genie/commits?author=MichaelDeBoey\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors][all-contributors] specification.\nContributions of any kind welcome!\n\n## LICENSE\n\nMIT\n\n\u003c!-- prettier-ignore-start --\u003e\n[npm]: https://www.npmjs.com\n[node]: https://nodejs.org\n[build-badge]: https://img.shields.io/travis/kentcdodds/genie.svg?style=flat-square\n[build]: https://travis-ci.org/kentcdodds/genie\n[coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/genie.svg?style=flat-square\n[coverage]: https://codecov.io/github/kentcdodds/genie\n[version-badge]: https://img.shields.io/npm/v/genie.svg?style=flat-square\n[package]: https://www.npmjs.com/package/genie\n[downloads-badge]: https://img.shields.io/npm/dm/genie.svg?style=flat-square\n[npm-trends]: https://www.npmtrends.com/genie\n[license-badge]: https://img.shields.io/npm/l/genie.svg?style=flat-square\n[license]: https://github.com/kentcdodds/genie/blob/master/other/LICENSE\n[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square\n[prs]: http://makeapullrequest.com\n[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square\n[coc]: https://github.com/kentcdodds/genie/blob/master/other/CODE_OF_CONDUCT.md\n[emojis]: https://github.com/kentcdodds/all-contributors#emoji-key\n[all-contributors]: https://github.com/kentcdodds/all-contributors\n\u003c!-- prettier-ignore-end --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkentcdodds%2Fgenie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkentcdodds%2Fgenie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkentcdodds%2Fgenie/lists"}