{"id":13468291,"url":"https://github.com/LeaVerou/rety","last_synced_at":"2025-03-26T05:30:59.636Z","repository":{"id":45389612,"uuid":"439111442","full_name":"LeaVerou/rety","owner":"LeaVerou","description":"Record typing on one or more editors and replay it at will, to simulate live coding","archived":false,"fork":false,"pushed_at":"2024-04-04T22:31:22.000Z","size":92,"stargazers_count":398,"open_issues_count":7,"forks_count":8,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-18T05:36:20.488Z","etag":null,"topics":["live-coding"],"latest_commit_sha":null,"homepage":"https://rety.verou.me","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/LeaVerou.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-16T19:56:37.000Z","updated_at":"2025-03-04T09:15:15.000Z","dependencies_parsed_at":"2024-10-06T12:58:09.074Z","dependency_job_id":"e482bc8f-1571-4752-a6cc-f8807018cd69","html_url":"https://github.com/LeaVerou/rety","commit_stats":{"total_commits":94,"total_committers":1,"mean_commits":94.0,"dds":0.0,"last_synced_commit":"c9ec0f70f6c0029009ed9cd5f700759949ad82a3"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Frety","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Frety/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Frety/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Frety/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LeaVerou","download_url":"https://codeload.github.com/LeaVerou/rety/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245597201,"owners_count":20641859,"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":["live-coding"],"created_at":"2024-07-31T15:01:08.300Z","updated_at":"2025-03-26T05:30:59.303Z","avatar_url":"https://github.com/LeaVerou.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cheader\u003e\n\n\u003cimg src=\"logo.svg\" style=\"width: 200px\"\u003e\n\n# rety\n## “Live” coding without the stress\n\n\u003c/header\u003e\n\n\u003csection\u003e\n\n## What is this?\n\nRety is a library that allows you to record the edits you make on one or more pieces of text (usually code)\nand replay them later to recreate the same typing flow.\n\nThis is particularly useful for orchestrating live demos that run without your presence.\n\nIt does not come with any particular UI, the UI is up to you. The UI you see in some of the demos in these docs is not part of Rety.\n\nHere’s an example of using it together with the [Inspire.js](https://inspirejs.org) Live demo plugin to do live demos during a talk:\n\n\u003ciframe width=\"100%\" style=\"aspect-ratio: 560 / 315\" src=\"https://www.youtube.com/embed/ZuZizqDF4q8?start=436\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen\u003e\u003c/iframe\u003e\n\n\u003c/section\u003e\n\n\u003csection\u003e\n\n## Background \u0026 Motivation\n\nI love live coding as a teaching tool, and over the years\n[it](https://twitter.com/aarongarciah/status/844212506365235200)\n[has become](https://twitter.com/gumnos/status/1118527972342935552)\n[part](https://twitter.com/ChrisFerdinandi/status/1027343408187277312)\n[of](https://twitter.com/LinnOyenFarley/status/1011650208831287296)\n[my](https://twitter.com/feross/status/928018779115724800)\n[trademark](https://twitter.com/HenriHelvetica/status/1011630698984361985)\n[speaking](https://twitter.com/johnallsopp/status/926417129070456832)\n[style](https://bradfrost.com/blog/post/on-speaking/#:~:text=Don%E2%80%99t%20live%20code%20%E2%80%93%20This%20applies%20to%20everyone%20except%20Lea%20Verou%2C%20who%20is%20an%20absolute%20beast).\n\nWhen combined with some kind of interactive preview,\nit allows the speaker to demonstrate not only the final state of a coding snippet, but how you get there, and what the intermediate results are.\nLive coding is to programming what a blackboard is to math or physics.\n\nHowever, it does create a unique challenge: My live coded slides don't make sense without me.\nThis may be acceptable for a conference talk, which is usually recorded, but not in other contexts,\nsuch as teaching a university course, where all instructors need to be able to teach all lectures, and students need to be able to quickly refer to examples shown.\n\nI didn't want to remove live coding from my slides, as I truly believe it is the perfect implementation of the *\"show, don’t tell\"* teaching adage,\nso I thought instead: what if I could *record* my live coding, and make it replayable?\nHowever, doing so manually seemed like cruel and unusual punishment.\nAnd thus, rety was born (pronounced like the \"rety\" in \"retype\").\n\nAfter using it extensively for [my course at MIT](https://designftw.mit.edu),\nI started using it during actual conference talks as well, as it was strictly superior to actual live coding:\nIt offered the same progressive development which is the primary benefit of live coding,\nbut none of the fumbling, delays, or mistakes that often come with it.\nYou can [watch the first conference talk I did with it, at CSS Day 2022 here](https://www.youtube.com/watch?v=ZuZizqDF4q8)\n(first live demo at 7:15).\n\nRety is designed to work well with the code editors of [Prism Live](https://live.prismjs.com/) and [CodeFlask](https://kazzkiq.github.io/CodeFlask/)\nbut it should work with any `\u003cinput\u003e`, `\u003ctextarea\u003e` or even [compatible custom elements](#recorder-compatible-controls).\n\n\u003c/section\u003e\n\n\u003csection\u003e\n\n## Basic Usage\n\nTo record edits on a textarea (`myTextarea`):\n\n```js\nimport Recorder from \"https://rety.verou.me/src/recorder.js\";\n\nlet recorder = new Recorder(myTextarea);\nrecorder.start();\n\nrecorder.addEventListener(\"actionschange\", evt =\u003e {\n\t// recorder.actions has been updated\n\t// evt.detail contains the new (or in some cases changed) action\n});\n```\n\nTo replay an array of actions (`actionsArray`) on a textarea (`editor`) with default settings:\n\n```js\nimport Replayer from \"https://rety.verou.me/src/replayer.js\";\n\nlet replayer = new Replayer(editor);\nreplayer.runAll(actionsArray);\n```\n\nInstead of importing directly from the CDN, you can also use npm:\n\n```\nnpm install rety\n```\n\n\u003c/section\u003e\n\n\u003csection\u003e\n\n## API\n\nRety consists of two classes, residing in correspondingly named modules: `Recorder` and `Replayer`.\n\n### `Recorder` class\n\nThe `Recorder` class allows you to record actions on one or more `\u003cinput\u003e`, `\u003ctextarea\u003e`, or any [recorder-compatible control](#compatible-controls).\n\n```js\nlet recorder = new Recorder(source);\nrecorder.start();\n```\n\nTo record actions from a single editor, `source` can be a reference to that editor.\nTo record actions from multiple editors, pass in an object literal with identifiers as keys and references to the elements as values.\n\nE.g.\n\n```js\nlet recorder = new Recorder({\n\tcss: document.querySelector(\"textarea#css\"),\n\thtml: document.querySelector(\"textarea#html\")\n});\n```\n\nThe identifiers can be anything you want, e.g. to record actions in a multi-file editor environment, the ids could be the filenames.\n\nCall `recorder.start()` to start recording and `recorder.pause()` to temporarily pause recording.\n\nYou will find any recorded actions in `recorder.actions`.\n\n`recorder` is a subclass of `EventTarget`, meaning it emits events.\nYou can listen to the `actionschange` event to respond to any change in the `recorder.actions` array.\n\nMost changes to `recorder.actions` will be new actions being added at the end of the array.\nHowever, there are few cases where instead of adding a new action, the previous action is modified instead.\nThis happens when the caret moves around or the selection is modified without any other action between changes.\nTo preserve all caret changes as separate actions, you can use the `preserveCaretChanges` option:\n\n```js\nlet recorder = new Recorder(source, {preserveCaretChanges: true});\n```\n\nYou can also provide options when calling `start()`:\n\n```js\nrecorder.start({preserveCaretChanges: true});\n```\n\n#### Constructor: `new Recorder(editor [, options])`\n\nOptions:\n\n| Option | Default | Description |\n|---|---|---|\n| `preserveCaretChanges` | `false` | If true, will not coalesce consecutive caret position changes |\n| `pauseThreshold` | `2000` | The delay (in ms) between consecutive actions that will cause a `pause` action to be inserted. Use `0` or `false` to disable pause actions entirely. |\n| `pauses` | `undefined` | Set to `\"ignore\"` to not record pauses entirely.\n| `pauseCap` | `undefined` | Set to a number of milliseconds to cap pauses to that duration.\n| `keys` | `undefined` | Keystrokes to record, see [How do I record custom keystrokes that don’t produce output?](#record-custom-keystrokes)\n\nTo record custom keystrokes (that don’t result in output), you’d use the `keys` parameter with strings like `\"Ctrl + Shift + E\"`.\nYou can specify one or more keystrokes as an array.\nBy default the `keyup` event is monitored. You can specify a different event by using an object instead of a string,\ne.g. `{key: \"Ctrl + Shift + E\", event: \"keydown\"}`.\n\n#### Methods\n\n| Member | Description |\n|---|---|\n| `recorder.start()` | Start listening to edits |\n| `recorder.pause()` | Temporarily stop listening to edits |\n\n#### Properties and accessors\n\n| Member | Description |\n|---|---|\n| `recorder.actions` | The array of actions recorded so far.\n\n### `Replayer` class\n\nThe `Replayer` class allows you to run a single action or a sequence of actions on an `\u003cinput\u003e`, `\u003ctextarea\u003e`, or any [replayer-compatible control](#compatible-controls).\n\n#### Constructor: `new Replayer(dest [, options])`\n\n`dest` is the same type as the first argument to the `Recorder` constructor.\nTo replay actions on a single editor element, `dest` would be a reference to that element.\nTo replay actions that span multiple editors, `dest` would be an object literal that maps ids to editor elements.\n\nOptions:\n\n| Option | Default | Description |\n|---|---|---|\n| `delay` | `140` | Delay between consecutive actions when `runAll()` is used |\n| `pauses` | `\"delay\"`  | What to do with pause actions? `\"delay\"` will just pause by that amount of time, `\"pause\"` will pause playback, `\"ignore\"` will discard them. You can also provide a function that decides which of these keywords to return based on the action specifics |\n| `animated_selection` | `true` | Should selections be animated or happen at once? |\n\n#### Methods\n\n| Member | Description |\n|---|---|\n| `async replayer.runAll(actions)` | Run a sequence of actions. Returns a promise that resolves when all actions have ran or the replayer has been paused. If another sequence of actions is currently being played, it will stop it first, then replace the rest of its queue.\n| `async replayer.queueAll(actions)` | Just like `runAll()` but instead of replacing the queue, it will add the actions to the existing queue.\n| `async replayer.next()` | Runs the next action in the queue\n| `async replayer.run([action])` | Run a single action (except pauses, since this is pretty low-level and does not handle timing).\n| `replayer.pause()` | Finish the action currently executing (if any), then pause.\n| `async replayer.resume()` | Resumes playing the current queue.\n\n#### Properties and Accessors\n\n| Member | Description |\n|---|---|\n| `recorder.queue` | Contains the actions that have been queued up for playing, but have not been played yet. Can also be set, and the array it is set to will be (shallowly) cloned. |\n| `recorder.paused` | `true` if the Replayer is paused or stopped, `false` if playing, `undefined` if the Replayer has not yet been used in any way. |\n| `recorder.played` | Array with actions that have already been played from the current queue. These actions have been removed from the queue. |\n\n\u003c/section\u003e\n\n\u003csection\u003e\n\n## FAQ\n\n\u003csection id=\"record-page\"\u003e\n\n### How do I record a demo from an arbitrary page, e.g. [a live coded slide](https://projects.verou.me/talks/css-variables/#button)?\n\nDrag this bookmarklet to your bookmarks toolbar: \u003ca href=\"javascript:(function(){import('https://rety.verou.me/plugins/bookmarklet.js').then(m =\u003e m.default())})()\"\u003e⏺ Rety\u003c/a\u003e.\nThen, when you’re ready to record, press it. It will insert a button at the top right corner that you press to stop recording.\nWhen you stop recording, it will log the actions it recorded in the console, for your copying convenience. It will log them in two ways: both as an object, as well as a minified JSON serialization.\nUnfortunately, it does not yet allow customizing recording options.\n\n\u003c/section\u003e\n\n\u003csection id=\"browser-support\"\u003e\n\n### What is the browser support?\n\nGenerally: all modern browsers. No IE11 or pre-Chromium Edge. More details:\n\n* `Recorder` makes heavy use of [`evt.inputType`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType) so it supports browsers that support that\n* `Replayer` makes heavy use of [`document.execCommand()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand), which limits Firefox support to Firefox 89+.\n* Both are written with well-supported modern ES features, such as [private members](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields). Since these generally have better support than `evt.inputType`, I did not bother transpiling.\n\n\u003c/section\u003e\n\n\u003csection id=\"tests\"\u003e\n\n### What about unit tests?\n\nI’m still trying to decide what's the best testing framework for mocking interactions with a textarea.\nIf you have suggestions, [please weigh in](https://github.com/LeaVerou/rety/issues/1)!\n\n\u003c/section\u003e\n\n\u003csection id=\"minified\"\u003e\n\n### Where is the minified version?\n\nThis is currently a tiny codebase, so minifying is more trouble than it’s worth.\nNo, it makes zero difference if you save one KB.\nIf in the future the code grows enough that minifying adds value, there will be a minified version.\n\nIf you *really* can’t live with a non-minified asset, you can always use the [generated version by jsdelivr]().\n\n\u003c/section\u003e\n\n\u003csection id=\"recorder-compatible-controls\"\u003e\n\n### I want to record actions from a custom element, not a built-in `\u003cinput\u003e` or `\u003ctextarea\u003e`\n\n`Recorder` will work fine with any control that meets the following requirements:\n- Implements `selectionStart` and `selectionEnd` properties\n- Implements a `value` property\n- Emits `input` events with suitable [`inputType` properties](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType)\n- Emits [`select`](https://developer.mozilla.org/en-US/docs/Web/API/Element/select_event) events when the selection changes\n\n\u003c/section\u003e\n\n\u003csection id=\"replayer-compatible-controls\"\u003e\n\n### I want to run actions on a custom element, not a built-in `\u003cinput\u003e` or `\u003ctextarea\u003e`\n\n`Replayer` will work fine with any control that meets the following requirements:\n- Implements writable `selectionStart` and `selectionEnd` properties\n- Works well with `document.execCommand()` (the actions used are `insertText`, `delete`, `forwardDelete`, `undo`, `redo`)\n\n\u003c/section\u003e\n\n\u003csection id=\"record-custom-keystrokes\"\u003e\n\n### How do I record custom keystrokes that don’t produce output?\n\nWhen constructing `Recorder` objects, you can pass in a `keys` parameter, which is an array of custom keystrokes to record.\nThese keystrokes can be specified as strings, like `Ctrl + Enter` or objects (like `{key: \"Ctrl + Enter\", event: \"keydown\"}`), if you also want to specify an event (other than `keyup` which is the default).\n\n`Recorder` will then record [`key` actions](#key-actions) that match this keystroke.\n`Replayer` does not need to be taught about custom keystrokes, it replicates any `key` action it finds.\n\n\u003c/section\u003e\n\n\u003c/section\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLeaVerou%2Frety","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FLeaVerou%2Frety","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLeaVerou%2Frety/lists"}