Command line interface for idyll lang.
See https://idyll-lang.github.io/ for full documentation.
Join our chatroom on Gitter: https://gitter.im/idyll-lang/Lobby
Idyll is a tool that makes it easier to author interactive narratives for the web. The goal of the project is to provide a friendly markup language — and an associated toolchain — that can be used to create dynamic, text-driven web pages.
Idyll lowers the barrier to entry for individuals to create documents that use common narrative techniques such as embedding interactive charts and graphs, responding to scroll events, and explorable explanations. Additionally, its readable format facilitates collaboration between writers, editors, designers, and programmers on complex projects.
Check out some articles create with Idyll:
- The Etymology of Trig Functions - https://mathisonian.github.io/trig/etymology/
- Seattle PD’s Dashcam Problem - https://mathisonian.github.io/dashcam/
- United Complaints of America - https://mathisonian.github.io/consumer-complaints/
- A Scrolling Introduction to Idyll - https://idyll-lang.github.io/idyll/scroll/
# Idyll
This is *Idyll* markup. By default, everything is text,
using some common markdown syntax.
But you can include custom JavaScript components inline:
[DynamicComponent property:"value" /]
The project is inspired by markdown, and
supports a subset of commonly used markdown
syntax, for example using a #
pound sign
to denote headers, using three backticks to
display code, and using stars to display inline bold and italics.
It is built on top of React, taking advantage of the one-way data binding pattern to make it easy to create custom JavaScript components that act as first class elements in the markup. Idyll makes it easy to create data-driven articles
[data name:"cooldataset" src:"cooldata.json" /]
Check out this chart:
[Chart data:cooldataset type:"line" /]
and handles variable binding for you.
[var name:"myVar" value:10 /]
[Range value:myVar min:0 max:100 /]
[DisplayVar var:myVar /]
The easiest way to get started with Idyll is to use the project generator. It will help get you set up using custom components, datasets, and stylesheets. To use the generator:
$ npm install -g yo generator-idyll
$ yo idyll
The generator will produce a structure that looks like this:
$ tree -I node_modules
.
├── _index.html
├── components
│ └── custom-component.js
├── data
│ └── example-data.json
├── images
│ └── idyll.png
├── index.idl
├── package.json
└── styles.css
3 directories, 7 files
The files do the following:
index.idl
- The main Idyll file, write your text in here.styles.css
- By putting CSS in here you can override the default styles.components/
- The folder for custom components. Any component defined in this folder can be invoked in the .idl file.data/
- If you want to include a dataset in your project, put it in here.https://idyll-lang.github.io/images/
- A folder for static images._index.html
- A barebones HTML file that will be used if you publish your project to the web.package.json
- This file contains all the metadata for your project.
To get started, from a terminal in that directory run npm start
and Idyll will compile your
file and open it in your web browser. Every time you save the index.idl
file, the system will automatically recompile
everything and update the page in the browser.
If you instead just want to test things out quickly, the simplest way is to install it from npm, create a new file and start writing:
$ npm install -g idyll
$ idyll my-file.idl
The idyll
command will automatically compile everything and load the
interactive page in your web browser.
To use a custom stylesheet, use the --css
flag.
$ idyll my-file.idl --css styles.css
The idyll
command line tool accepts the following options
--css
the path to your CSS file. You can use this to override Idyll's default styles, e.g.$ idyll index.idl --css my-custom-styles.css
.--components
the path to your custom components. By default this points tocomponents/
.--datasets
the path to the folder containing your datasets. By default this points todata/
.--layout
the name of the layout to use. By default this isblog
. More on layouts below.--theme
the name of the theme to use. By default this isidyll
. More on themes below.--build
the build flag tells Idyll to output the compiled JavaScript instead of running a server and opening your page in a web browser,$ idyll index.idl --build > output.js
.
If you are using Idyll via the project generator, open package.json
to change these options.
Idyll exposes two options to help you style your project, layout
and theme
. layout
deals with CSS styles related to how your content is
layed out on the page: width, columns, etc. The theme
option allows you to choose diffent stylesheets to change the style of the content itself (text color, font, and so on).
Idyll currently ships with two different page layouts that can be used to modify the structure of how to content is displayed on the page, allowing you to quickly test out different narrative styles for you project.
This is the default layout. The blog
layout is fairly traditional article layout with room in the margin to
put notes and other callouts.
The scroll layout leaves more space for a fixed element, and adds margins between text sections, making it easy to trigger events when a certain section enters the viewport.
See https://github.com/idyll-lang/idyll/blob/master/examples/scroll/index.idl for example usage of the scroll layout.
There currently is only one default theme, expect more soon.
Besides text, components are the other basic building block of an Idyll document. Components are denoted with brackets, and can be invoked in one of two ways: they can be self closing,
[Range min:0 max:10 value:value /]
or have a closing and opening tag with content in between.
[Button]
Click Me
[/Button]
Properties can be passed into components in the following ways:
Numbers or string literals may be used.
[Component propName:10 /]
[Component propName:"propValue" /]
A variable or dataset can be passed in directly, this will automatically create a binding between that variable and property, more on this in the next section.
[Component propName:myVar /]
Use backticks to pass an evaluated expression:
[Component propName:`2 * 2 * 2` /]
[Component propName:`{ an: "object" }` /]
If the property expects a function, the expression will automatically be converted to a callback. This is convenient for updating variables on events, for example.
[Component onClick:`myVar++` /]
Idyll uses naming conventions to determine if the expression should be converted to a callback.
Properties that are named onXxxxx
(e.g. onClick
) or handleYyyyy
(e.g. handleMouseMove
) are
assumed to expect callbacks.
Components are resolved according to following algorithm:
- If there is a custom component with this name, use it.
- If there is a built-in component with this name, use it.
- If there is a valid HTML tag with this name, use it.
- If none of the above, just use a div, but give it the class of the component name
So, for example, assume we have one custom component, named Custom
.
// Renders our custom component
[Custom /]
// Renders the built-in range component
[Range /]
// Renders `<img />` because it is a valid HTML tag
[Img /]
// Renders `<div class="SomethingElse"></div>`
[SomethingElse /]
Idyll ships with a handful of components that handle common tasks. They are broken into three categories:
-
Layout - these components help manage page layout, for example putting text in the
Aside
component will render it in the article margin instead of inline with the rest of your text. -
Presentation - these components render something to the screen, for example the
Chart
component takes data as input and can display several types of charts. -
Helpers - these components don't affect the page content, but help with common tasks. The
Analytics
component makes it easy to add Google Analytics to your page.
All built-in compononents expose a property onEnteredView
that can be used to trigger events when a reader scrolls the page to
reveal specific content.
Content inside of an aside component will be displayed in the margin of your document. For example, the consumer complaints article uses the aside component to display a small chart and caption:
[aside]
[Chart type:"time" data:complaintsByDate /]
[caption]Complaints sent to the CFPB each month[/caption]
[/aside]
Content inside of a fixed
component will be locked in place, even when the rest of the document scrolls. The scroll example uses the fixed
component to keep the dynamic chart in place:
[fixed]
[Chart type:"scatter" data:dynamicData /]
[/fixed]
The inline
component adds the display: inline-block
style property, so that items inside of inline
component will
be displayed next to eachother. For example, this code,
[section]
[inline][img src:"..." /][/inline]
[inline][img src:"..." /][/inline]
[inline][img src:"..." /][/inline]
[/section]
Will display three images side by side.
This will display a button. To control what happens when the button is clicked, add an onClick
property:
[button onClick`myVar += 1`]Click Me![/button]
This will display a chart. It expects the following properties:
data
- A JSON object containing the data for this chart. It uses the victory library to handle rendering, so see those docs for more information on what types of data can be passed in.type
- The type of the chart to display, can beline
,scatter
,bar
,pie
, ortime
. The time type is a line chart that expects thex
values in the data to be in the temporal domain.
[var name:`dataToBeCharted` value:`[
{x: 0, y: 0.5},
{x: 3.5, y: 0.5},
{x: 4, y: 0},
{x: 4.5, y: 1},
{x: 5, y: 0.5},
{x: 8, y: 0.5}
]` /]
[Chart type:"line" data:dataToBeCharted /]
This will render the value of a variable to the screen. It is mostly useful for debugging:
[var name:"myVar" value:10 /]
[Range value:myVar min:0 max:100 /]
[DisplayVar var:myVar /]
This component makes it easy to add a title, subtitle, and byline to your article:
[Header
title:"The Title of my Article"
subtitle:"The subtitle of my article"
author:"Matthew Conlen"
authorLink:"https://github.com/mathisonian/"
/]
This component just acts as syntactic sugar for displaying links inline in your text.
[link text:"the text" url:"https://some.url" /]
is equivalent to [a href:"https://some.url"]the text[/a].
This component displays a range slider. The properties are:
value
: The value to display; if this is a variable, the variable will automatically be updated when the slider is moved.man
: The maximum value.min
: The minimum value.step
: The granularity of the slider
[var name:"myVar" value:10 /]
[Range value:myVar min:0 max:100 /]
[DisplayVar var:myVar /]
This component is used to dynamically display different content. It can be used to make slideshows, but is generally useful for dynamically displaying different content of any type.
[var name:"slide" value:1 /]
[slideshow currentSlide:slide]
[slide]This is the content for slide 1[/slide]
[slide]This is the content for slide 2[/slide]
[slide]This is the content for slide 3[/slide]
[/slideshow]
[Button onClick:`slide = 1`]Slide 1[/Button]
[Button onClick:`slide = 2`]Slide 2[/Button]
[Button onClick:`slide = 3`]Slide 3[/Button]
This component will display an SVG file inline using https://github.com/matthewwithanm/react-inlinesvg. This makes it easy to style the SVG with css, as opposed to displaying the svg inside of an image tag.
Usage:
[SVG src="path/to/filg.svg" /]
Display tabular data. Uses https://github.com/glittershark/reactable under the hood to render the table.
[Table data:`[{columnName1: value, columnName2: value}, {columnName1: value, {columnName2: value}}]` /]
Render a Vega Lite spec, using https://github.com/kristw/react-vega-lite.
[data name:"myData" src:"my-dataset.json" /]
[VegaLite data:myData spec:`{
mark: "bar",
encoding: {
x: {field: "a", type: "ordinal"},
y: {field: "b", type: "quantitative"}
}
}`]
This component makes it easy to insert a Google Analytics code on your page.
[Analytics google:"UA-XXXXXXXXX" /]
The meta component adds context to the page template when building your app for publication. The following variables are available and will be inserted
as <meta>
properties into the head of your HTML page if you define them:
title
- the page titledescription
- a short description of your projecturl
- the canonical URL from this projecttwitterHandle
- the author's twitter handle, it will create a link in the twitter cardshareImageUrl
- the URL of an image to be shared on social media (twitter cards, etc.). This must be a fully qualified URL, e.g. https://idyll-lang.github.io/https://idyll-lang.github.io/images/logo.png.shareImageWidth
- the width of the share image in pixelsshareImageHeight
- the height of the share image in pixels
Idyll is designed for people to use their own custom components as well. Under the hood an Idyll component just needs to be anything that will function as a React component. If you create a custom component in JavaScript, point Idyll to the folder where you created it and everything will just work, no need to worry about compiling, bundling, or module loading.
For example, this custom component
const React = require('react');
const IdyllComponent = require('idyll-component');
class Custom extends IdyllComponent {
render() {
return (
<div {...this.props}>
This is a custom component
</div>
);
}
}
module.exports = Custom;
could be invoked inside of an Idyll file with the following code:
[Custom /]
The IdyllComponent
class adds an
updateProps
method that is used to keep
variables in sync with the rest of the document, and also
adds a property onEnteredView
that can be used to
trigger events in scroll-based narratives.
For example, a component can change the value of a property that it receives, and Idyll will propegate the change to any bound variables on the page.
const React = require('react');
const IdyllComponent = require('idyll-component');
class Incrementer extends IdyllComponent {
increment() {
this.updateProps({
value: this.props.value + 1
})
}
render() {
return (
<div onClick={this.increment.bind(this)}>
Click me.
</div>
);
}
}
module.exports = Incrementer;
The Incrementer
component could then be used as follows:
[var name:"clickCount" value:0 /]
[Incrementer value:clickCount /]
[DisplayVar var:clickCount /]
Notice that even thought the Incrementer
component doesn't know
anything about the variable clickCount
, it will still correctly
update.
Of course, this trivial example could be accomplished using built-in components:
[var name:"clickCount" value:0 /]
[Button onClick:`clickCount+=1` ]Click Me.[/Button]
[DisplayVar var:clickCount /]
Components lookup is based on filenames. If your component name
is in CamelCase
, it will automatically be converted to kebab-case
,
so for example if you want to create a component named CustomComponent
,
it should be stored in a file called custom-component.js
.
Custom component are meant for times when more complex and custom
code is needed. By default Idyll will look for your custom components
inside of a folder called components/
. If you wish to change the custom
component path, specify it with the --components
option, e.g.
idyll index.idl --css styles.css --components custom/component/path/
.
In addition to rendered components, Idyll supports datasets and variables. These are declared in the same way:
[var name:"myVar" value:5 /]
[data name:"myDataset" source:"data.json" /]
The above code declares a variable myVar
and initializes it
with the value 5. It also imports a dataset from a JSON file.
Once a variable or dataset is initialized it can be used as an input to a component.
For example, the following code declares a variable myVar
, and uses
it as input to two components.
[var name:"myVar" value:5 /]
[Range min:0 max:10 value:myVar /]
[DisplayVar var:myVar /]
Idyll handles all of the updating and rerendering of components for you, so when someone interacts with the range slider, the display will be updated automatically!
Idyll exposes the ref
property to allow you to refer to specific components in
property expressions.
[Component ref:"thisComponent" propName:`refs.thisComponent` /]
The ref property allows you to update the state of one component based on properties of another. Idyll provides some utilities automatically, for example keeping track of the position of a component on the page, and how far through a component's content the reader has scrolled.
Each ref object has the following properties:
{
domNode: node,
scrollProgress: {
x: number,
y: number
},
size: {
x: number,
y: number
},
position: {
left: number,
top: number,
right: number,
bottom: number
}
}
For example:
[section ref:'section' style:`{ opacity: 1 - refs.section.scrollProgress.y }`]
Lorem ipsum...
...
lots of text here
...
[/section]
The above code will create a section of text that fades out as the user scrolls to the bottom of it.
scrollProgress.y
in this case is a value from 0 to 1 that Idyll automatically computes,
providing the percentage that a user has scrolled through a certain component.
Once you are happy with your project, the idyll
command-line tool can also be used to
build a stand alone JavaScript bundle. If you used the Idyll project generator, npm tasks
to build and deploy the project to github pages are available. Once you've initialized your
project with a repo on github, run the command
$ npm run deploy
this will compile the assets and push it to github pages. Note that the meta component is useful for inserting metadata into the compiled output.
If you are using the project generator, that's all you need to know about deploying. Continue reading to learn more about the nuts and bolts of the process.
If you want to use the idyll
command line tool directly and build the JavaScript bundle, pass in
the --build
flag.
$ idyll my-file.idl --build > bundle.js
This can be combined with other
useful tools to quickly generate a webpage. For example,
the indexhtmlify
package will create a barebones HTML page,
properly inserting the
$ npm install -g indexhtmlify
$ idyll my-file.idl --build | indexhtmlify --style styles.css > idyll.html
The command that the generator uses is
$ cp -r {images,styles.css} build/; idyll index.idl --build | uglifyjs > build/index.js && gh-pages -d ./build
which copies default static asset folders to the build directory, compiles and minifies the JavaScript, and
then uses the gh-pages
package to push the resulting folder to your gh-pages
branch on Github.