• Stars
    star
    383
  • Rank 111,995 (Top 3 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 3 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Record typing on one or more editors and replay it at will, to simulate live coding

rety

“Live” coding without the stress

What is this?

Rety is a library that allows you to record the edits you make on one or more pieces of text (usually code) and replay them later to recreate the same typing flow.

This is particularly useful for orchestrating live demos that run without your presence.

It 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.

Here’s an example of using it together with the Inspire.js Live demo plugin to do live demos during a talk:

<iframe 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></iframe>

Background & Motivation

I love live coding as a teaching tool, and over the years it has become part of my trademark speaking style.

When combined with some kind of interactive preview, it 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. Live coding is to programming what a blackboard is to math or physics.

However, it does create a unique challenge: My live coded slides don't make sense without me. This may be acceptable for a conference talk, which is usually recorded, but not in other contexts, such 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.

I 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, so I thought instead: what if I could record my live coding, and make it replayable? However, doing so manually seemed like cruel and unusual punishment. And thus, rety was born (pronounced like the "rety" in "retype").

After using it extensively for my course at MIT, I started using it during actual conference talks as well, as it was strictly superior to actual live coding: It offered the same progressive development which is the primary benefit of live coding, but none of the fumbling, delays, or mistakes that often come with it. You can watch the first conference talk I did with it, at CSS Day 2022 here (first live demo at 7:15).

Rety is designed to work well with the code editors of Prism Live and CodeFlask but it should work with any <input>, <textarea> or even compatible custom elements.

Basic Usage

To record edits on a textarea (myTextarea):

import Recorder from "https://rety.verou.me/src/recorder.js";

let recorder = new Recorder(myTextarea);
recorder.start();

recorder.addEventListener("actionschange", evt => {
	// recorder.actions has been updated
	// evt.detail contains the new (or in some cases changed) action
});

To replay an array of actions (actionsArray) on a textarea (editor) with default settings:

import Replayer from "https://rety.verou.me/src/replayer.js";

let replayer = new Replayer(editor);
replayer.runAll(actionsArray);

Instead of importing directly from the CDN, you can also use npm:

npm install rety

API

Rety consists of two classes, residing in correspondingly named modules: Recorder and Replayer.

Recorder class

The Recorder class allows you to record actions on one or more <input>, <textarea>, or any recorder-compatible control.

let recorder = new Recorder(source);
recorder.start();

To record actions from a single editor, source can be a reference to that editor. To record actions from multiple editors, pass in an object literal with identifiers as keys and references to the elements as values.

E.g.

let recorder = new Recorder({
	css: document.querySelector("textarea#css"),
	html: document.querySelector("textarea#html")
});

The identifiers can be anything you want, e.g. to record actions in a multi-file editor environment, the ids could be the filenames.

Call recorder.start() to start recording and recorder.pause() to temporarily pause recording.

You will find any recorded actions in recorder.actions.

recorder is a subclass of EventTarget, meaning it emits events. You can listen to the actionschange event to respond to any change in the recorder.actions array.

Most changes to recorder.actions will be new actions being added at the end of the array. However, there are few cases where instead of adding a new action, the previous action is modified instead. This happens when the caret moves around or the selection is modified without any other action between changes. To preserve all caret changes as separate actions, you can use the preserveCaretChanges option:

let recorder = new Recorder(source, {preserveCaretChanges: true});

You can also provide options when calling start():

recorder.start({preserveCaretChanges: true});

Constructor: new Recorder(editor [, options])

Options:

Option Default Description
preserveCaretChanges false If true, will not coalesce consecutive caret position changes
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.
pauses undefined Set to "ignore" to not record pauses entirely.
pauseCap undefined Set to a number of milliseconds to cap pauses to that duration.
keys undefined Keystrokes to record, see How do I record custom keystrokes that don’t produce output?

To record custom keystrokes (that don’t result in output), you’d use the keys parameter with strings like "Ctrl + Shift + E". You can specify one or more keystrokes as an array. By default the keyup event is monitored. You can specify a different event by using an object instead of a string, e.g. {key: "Ctrl + Shift + E", event: "keydown"}.

Methods

Member Description
recorder.start() Start listening to edits
recorder.pause() Temporarily stop listening to edits

Properties and accessors

Member Description
recorder.actions The array of actions recorded so far.

Replayer class

The Replayer class allows you to run a single action or a sequence of actions on an <input>, <textarea>, or any replayer-compatible control.

Constructor: new Replayer(dest [, options])

dest is the same type as the first argument to the Recorder constructor. To replay actions on a single editor element, dest would be a reference to that element. To replay actions that span multiple editors, dest would be an object literal that maps ids to editor elements.

Options:

Option Default Description
delay 140 Delay between consecutive actions when runAll() is used
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
animated_selection true Should selections be animated or happen at once?

Methods

Member Description
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.
async replayer.queueAll(actions) Just like runAll() but instead of replacing the queue, it will add the actions to the existing queue.
async replayer.next() Runs the next action in the queue
async replayer.run([action]) Run a single action (except pauses, since this is pretty low-level and does not handle timing).
replayer.pause() Finish the action currently executing (if any), then pause.
async replayer.resume() Resumes playing the current queue.

Properties and Accessors

Member Description
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.
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.
recorder.played Array with actions that have already been played from the current queue. These actions have been removed from the queue.

FAQ

How do I record a demo from an arbitrary page, e.g. a live coded slide?

Drag this bookmarklet to your bookmarks toolbar: Rety. Then, when you’re ready to record, press it. It will insert a button at the top right corner that you press to stop recording. When 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. Unfortunately, it does not yet allow customizing recording options.

What is the browser support?

Generally: all modern browsers. No IE11 or pre-Chromium Edge. More details:

  • Recorder makes heavy use of evt.inputType so it supports browsers that support that
  • Replayer makes heavy use of document.execCommand(), which limits Firefox support to Firefox 89+.
  • Both are written with well-supported modern ES features, such as private members. Since these generally have better support than evt.inputType, I did not bother transpiling.

What about unit tests?

I’m still trying to decide what's the best testing framework for mocking interactions with a textarea. If you have suggestions, please weigh in!

Where is the minified version?

This is currently a tiny codebase, so minifying is more trouble than it’s worth. No, it makes zero difference if you save one KB. If in the future the code grows enough that minifying adds value, there will be a minified version.

If you really can’t live with a non-minified asset, you can always use the generated version by jsdelivr.

I want to record actions from a custom element, not a built-in <input> or <textarea>

Recorder will work fine with any control that meets the following requirements:

  • Implements selectionStart and selectionEnd properties
  • Implements a value property
  • Emits input events with suitable inputType properties
  • Emits select events when the selection changes

I want to run actions on a custom element, not a built-in <input> or <textarea>

Replayer will work fine with any control that meets the following requirements:

  • Implements writable selectionStart and selectionEnd properties
  • Works well with document.execCommand() (the actions used are insertText, delete, forwardDelete, undo, redo)

How do I record custom keystrokes that don’t produce output?

When constructing Recorder objects, you can pass in a keys parameter, which is an array of custom keystrokes to record. These 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).

Recorder will then record key actions that match this keystroke. Replayer does not need to be taught about custom keystrokes, it replicates any key action it finds.

More Repositories

1

awesomplete

Ultra lightweight, usable, beautiful autocomplete with zero dependencies.
JavaScript
6,964
star
2

prefixfree

Break free from CSS prefix hell!
JavaScript
3,841
star
3

animatable

One property, two values, endless possiblities
HTML
2,580
star
4

bliss

Blissful JavaScript
JavaScript
2,390
star
5

inspire.js

Lean, hackable, extensible slide deck framework. Previously known as CSSS.
JavaScript
1,717
star
6

color.js

Color conversion & manipulation library by the editors of the CSS Color specifications
JavaScript
1,581
star
7

css3patterns

The popular CSS3 patterns gallery, now on Github :)
HTML
1,415
star
8

stretchy

Form element autosizing, the way it should be
JavaScript
1,275
star
9

dabblet

An interactive CSS playground
JavaScript
817
star
10

dpi

dpi love - Easily find the DPI/PPI of any screen
JavaScript
748
star
11

multirange

A tiny polyfill for HTML5 multi-handle sliders
CSS
605
star
12

conic-gradient

Polyfill for conic-gradient() and repeating-conic-gradient()
HTML
486
star
13

parsel

A tiny, permissive CSS selector parser
TypeScript
361
star
14

md-block

A custom element for rendering stylable (light DOM) Markdown
JavaScript
283
star
15

regexplained

JavaScript
273
star
16

chainvas

Make APIs suck less
HTML
262
star
17

css3test

How does your browser score for its CSS3 support?
JavaScript
214
star
18

css.land

Hands on CSS demos
HTML
213
star
19

nudeui

Lea's kitchen sink of form components. WIP. Try at your own risk or come back later.
JavaScript
204
star
20

HTML5-Progress-polyfill

Polyfill for the HTML5 <progress> element
JavaScript
178
star
21

rgba.php

Script for automatic generation of one pixel alpha-transparent images for non-RGBA browsers to easily emulate RGBA colors in backgrounds
PHP
176
star
22

cubic-bezier

Playground for CSS bezier-based timing functions
JavaScript
169
star
23

markapp

Building apps by authoring HTML
CSS
134
star
24

duoload

Simple, client-side comparison of website loading behavior
CSS
130
star
25

play.csssecrets.io

CSS Secrets Book live demos
CSS
119
star
26

talks

All my talks (move still in progress)
HTML
92
star
27

incrementable

Increment length values in textfields
JavaScript
83
star
28

corner-shape

Play with corner-shape before it’s implemented!
JavaScript
75
star
29

StronglyTyped

A library for strongly typed properties & global variables in JavaScript
JavaScript
70
star
30

css-colors

Share & convert CSS colors
JavaScript
62
star
31

bubbly

[Unfinished] CSS speech bubbles made easy!
JavaScript
50
star
32

html-syntax-guidelines

48
star
33

whathecolor

CSS
47
star
34

css-almanac

Repo for planning & voting on which stats to study
SCSS
34
star
35

forkgasm

Lea & Chris’ culinary adventures
HTML
33
star
36

talks-list

Automatic talks list, generated from JSON. Unmaintained, use Mavo instead.
JavaScript
29
star
37

chroma-zone

Slides for my talk “The Chroma Zone: Engineering Color on the Web”
CSS
21
star
38

htest

Declarative, boilerplate-free unit testing
JavaScript
19
star
39

lea.verou.me

Towards a serverless lea.verou.me! WIP, either help out or move along.
JavaScript
17
star
40

mavoice

Prioritize features for open source projects
CSS
15
star
41

issue-closing

View issue closing stats for any repo!
HTML
14
star
42

missing-slice

The Missing Slice talk slides
HTML
12
star
43

hci.mit.edu

WIP. Preview:
HTML
12
star
44

tweeplus

Longer tweets, no strings attached.
JavaScript
11
star
45

brep

Write batch find & replace scripts that transform files with a simple human-readable syntax
JavaScript
10
star
46

leaverou.github.io

Just a data repo, I don't intend to put a website here for now
10
star
47

mygraine

A migraine tracker, built with Mavo. Work in progress, come back later.
HTML
9
star
48

bytesizematters

JavaScript
8
star
49

refresh-it

Refresh page resources without a page reload. An alternative to the heavyweight processes that take over your local server.
JavaScript
8
star
50

uist2017

Website for ACM UIST 2017
HTML
6
star
51

homesearch

A sample Mavo app for people looking for housing to store info about and compare the homes they are trying to decide between
HTML
6
star
52

feedback

Easily save & share lists of tweets and own your data. Made with Mavo.
HTML
5
star
53

eleventy-plugin-citations

JavaScript
5
star
54

my-lifesheets

HTML
4
star
55

expenses

App to generate summary of expenses, made with Mavo
CSS
4
star
56

rework-utils

Utilities to explore ASTs generated by the Rework CSS parser. Originally written for the Web Almanac.
JavaScript
3
star
57

mv-data

3
star
58

testlib

Just a test, move along
JavaScript
2
star
59

blissful-hooks

Deep extensibility for all!
JavaScript
2
star
60

labelizer

Manage repo labels (WIP)
JavaScript
1
star
61

contacts

1
star
62

zoe-eats

Baby food log, made with Mavo
1
star
63

zoelearns.com

JavaScript
1
star
64

website

1
star
65

pathed

Get/set/subset deep objects via readable, extensible paths. WIP.
JavaScript
1
star