Skip to content

Commit

Permalink
Initial atoms and Mobiledoc 0.3 design
Browse files Browse the repository at this point in the history
  • Loading branch information
rlivsey authored and mixonic committed Feb 2, 2016
1 parent d1907a0 commit 0a51e71
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 76 deletions.
58 changes: 58 additions & 0 deletions ATOMS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Mobiledoc Atoms

Atoms are effectively read-only inline cards.

## Atom format

An atom is a javascript object with 3 *required* properties:

* `name` [string] - The name of this atom in the mobiledoc
* `type` [string] - The output of this atom. Valid values are 'dom', 'html', and 'text'
* `render` [function] - Invoked by the renderer to render this atom

## Atom rendering

The `render` function on an atom is called by an instance of a renderer and passed an object with the following four properties:

* `env` [object] - A set of environment-specific properties
* `options` [object] - Rendering options that were passed to the renderer (as `cardOptions`) when it was instantiated
* `payload` [object] - The data payload for this atom from the mobiledoc
* `value` [string] - The textual representation to for this atom

The return value of the `render` function will be inserted by the renderer into the rendered mobiledoc.
The return value can be null if an atom does not have any output. If there is a return value it
must be of the correct type (a DOM Node for the dom renderer, a string of html or text for the html or text renderers, respectively).

#### `env`

`env` always has the following properties:

* `name` [string] - the name of the card
* `onTeardown` [function] - The atom can pass a callback function: `onTeardown(callbackFn)`. The callback will be called when the rendered content is torn down.

## Atom Examples

Example dom atom that renders a mention:

```js
export default {
name: 'mention',
type: 'dom',
render({ env, options, value, payload}) {
return document.createTextNode(`@${value}`);
}
};
```

Example dom atom that registers a teardown callback:
```js
let card = {
name: 'atom-with-teardown-callback',
type: 'dom',
render({env, options, value, payload}) {
env.onTeardown(() => {
console.log('tearing down atom named: ' + env.name);
});
}
};
```
27 changes: 0 additions & 27 deletions CARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,6 @@ When being rendered by an editor (i.e., `env.isInEditor` is true), the env will
* `remove` [function] - Removes this card from the document
* `postModel` [object] - The instance of this card's section in the editor's internal abstract tree. This can be used along with the mobiledoc-kit `postEditor` API to transform the card in other ways (for example, moving the card to a different section of the document)

## Renderers

All of the officially-supported mobiledoc renderers have the same signature and methods.
To instantiate a renderer, call its constructor with an object of options that has any of the following optional properties:

* `cards` [array] - The cards that your mobiledoc includes and that the renderer will encounter
* `cardOptions` [object] - Options to be passed to the card `render` (or `edit`) function
* `unknownCardHandler` [function] - This function is called (with the same arguments as `render`) whenever the renderer encounters a card that doesn't
match one of the `cards` it has been provided

An instance of a renderer has one method, `render`. This method accepts a mobiledoc and returns an object with two properties:
* `result` [mixed] - The rendered result. Its type depends on the renderer, and can be a DOM Node (dom renderer) or a string (html or text renderers)
* `teardown` [function] - Call this function to tear down the rendered mobiledoc. The dom renderer will remove the rendered dom from the screen. All renderers will call
the card teardown callbacks that were registered using `env.onTeardown(callbackFunction)`

Example usage of a renderer:
```js
let renderer = new DOMRenderer({cards: [card1, card2], cardOptions: {foo: 'bar'});
let rendered = renderer.render(mobiledoc);

document.body.appendChild(renderered.result);

// later...

rendered.teardown(); // removes the rendered items, calls teardown hooks
```
## Card examples

Example dom card that renders an image:
Expand Down
146 changes: 103 additions & 43 deletions MOBILEDOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,92 +22,152 @@ prescription for output display.
Often Mobiledoc will be used with one or more of:

* [Mobiledoc Kit](https://github.com/bustlelabs/mobiledoc-kit) to author an editor
* [Ember Mobiledoc Editor](https://github.com/bustlelabs/ember-mobiledoc-editor) is one such editor
* [Ember Mobiledoc Editor](https://github.com/bustlelabs/ember-mobiledoc-editor) is one such editor
* [Mobiledoc DOM Renderer](https://github.com/bustlelabs/mobiledoc-dom-renderer)
* [Mobiledoc HTML Renderer](https://github.com/bustlelabs/mobiledoc-html-renderer)
* [Mobiledoc Text Renderer](https://github.com/bustlelabs/mobiledoc-text-renderer)

## Specification

Mobiledoc consists of a wrapping object and nested array of section data. Using
arrays makes Mobiledocs each to render quickly.
Mobiledoc consists of a wrapping object, type definitions for markup, atoms and cards,
and an array of section data. Using arrays makes Mobiledocs each to render quickly.

The wrapper signature:

```
{
version: "0.1", ──── Versioning information
sections: [
[ ──── List of markup types.
markup,
markup
],
[ ──── List of sections.
section,
section,
section
]
version: "0.3", ──── Versioning information
markups: [ ──── List of markup types
markup,
markup
],
atoms: [ ──── List of atom types
atom,
atom
],
cards: [ ──── List of card types
card,
card
],
sections: [ ──── List of sections.
section,
section,
section
]
}
```

**Markup signature**
**Markup definition signature**

Markups have a tagName, and optionally an array of `attributeName, attributeValue]` pairs

```
{
version: "0.1",
sections: [
version: "0.3",
markups: [
[tagName, optionalAttributes], ──── Markup
['em'], ──── Example simple markup
['a', ['href', 'http://google.com']], ──── Example markup with attributes
],[
// ...
]]
]
}
```

**Text Section**
**Atom definition signature**

Atoms have a name, text value and arbitrary payload.

```
{
version: "0.1",
sections: [[
version: "0.3",
atoms: [
[atomName, atomText, atomPayload], ──── Atom
['mention', '@bob', { id: 42 }] ──── Example 'mention' atom
]
}
```

**Card definition signature**

Cards have a name and arbitrary payload.

```
{
version: "0.3",
cards: [
[cardName, cardPayload], ──── Card
['image', { ──── Example 'image' card
src: 'http://google.com/logo.png'
}]
]
}
```

**Markup Section**

Markup sections, in addition to plain text, can include markups and atoms.

```
{
version: "0.3",
markups: [
["b"], ──── Markup at index 0
["i"] ──── Markup at index 1
],[
[typeIdentifier, tagName, markers], ──── typeIdentifier for text sections
[1, "h2", [ is always 1.
[[], 0, "Simple h2 example"],
],
atoms: [
["mention", "@bob", { id: 42 }] ──── mention Atom at index 0
["mention", "@tom", { id: 12 }] ──── mention Atom at index 1
]
sections: [
[sectionTypeIdentifier, tagName, markers], ──── sectionTypeIdentifier for markup sections
[1, "h2", [ is always 1.
[0, [], 0, "Simple h2 example"],
]],
[1, "p", [
[textTypeIdentifier, openMarkupsIndexes, numberOfClosedMarkups, value],
[0, [], 0, "Example with no markup"], ──── textTypeIdentifier for markup is always 0
[0, [0], 1, "Example wrapped in b tag"],
[0, [1], 0, "Example opening i tag"],
[0, [], 1, "Example closing i tag"],
[0, [1, 0], 1, "Example opening i tag and b tag, closing b tag"],
[0, [], 1, "Example closing b tag"],
]],
[1, "p", [
[openMarkupsIndexes, numberOfClosedMarkups, value],
[[], 0, "Example with no markup"],
[[0], 1, "Example wrapped in b tag"],
[[1], 0, "Example opening i tag"],
[[], 1, "Example closing i tag"],
[[1, 0], 1, "Example opening i tag and b tag, closing b tag"],
[[], 1, "Example closing b tag"]
]]
]]
[textTypeIdentifier, atomIndex, openMarkupsIndexes, numberOfClosedMarkups],
[1, 0, [], 0], ──── mention atom at index 0 (@bob), textTypeIdentifier for atom is always 1
[1, 1, [0], 1] ──── mention atom at index 1 (@tom) wrapped in b tag
]],
]
}
```

The first item in the `sections` array is a list of markups. Markups have
a tagName, and optionally an array of `attributeName, attributeValue]` pairs.
The index in `openMarkupsIndex` specifies which markups should be opened at
the start of the `value` text. As these tags are opened, then create a stack
of opened markups. The `numberOfClosedMarkups` says how many markups should
be closed at the end of a `value`.

In addition to markups, markup sections may contain [ATOMS](ATOMS.md).
Atoms have a `textTypeIdentifier` of 1 and contain a `atomTypeIndex`, text content
and an `atomPayload` object which is arbitrary and passed through to the atom's
implementation.

Atoms also have `openMarkupsIndex` and `numberOfClosedMarkups` so that markup can flow
across them.

If an atom is present in Mobiledoc, but no atom implementation is registered, then the text
value of the atom will be rendered as plain text as a fallback.

**Card Section**

```
{
version: "0.1",
sections: [[],[
[typeIdentifier, tagName, markers], ──── typeIdentifier for card sections
[10, "card-name", cardPayload] is always 10.
]]
version: "0.3",
cards: [
["card-name", { cardPayload }]
],
sections: [
[typeIdentifier, cardIndex], ──── typeIdentifier for card sections
[10, 0] is always 10.
]
}
```

Expand Down
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ supporting rich content via cards.
platform. Mobiledoc is portable and fast.
* The editor makes limited use of Content Editable, the siren-song of doomed
web editor technologies.
* Mobiledoc is designed for *rich* content. We call these sections of an
article "cards", and implementing a new one doesn't require an understanding
of Mobiledoc editor internals. Adding a new card takes an afternoon, not several
days. To learn more about cards and mobiledoc renderers, see the **[Cards docs](https://github.com/bustlelabs/mobiledoc-kit/blob/master/CARDS.md)**.
* Mobiledoc is designed for *rich* content. We call rich sections of an
article "cards" and rich inline elements "atoms" and implementing a new one doesn't require an understanding
of Mobiledoc editor internals. Adding a new atom or card takes an afternoon, not several
days. To learn more, see the docs for
**[Atoms](https://github.com/bustlelabs/mobiledoc-kit/blob/master/ATOMS.md)**,
**[Cards](https://github.com/bustlelabs/mobiledoc-kit/blob/master/CARDS.md)**
and
**[Mobiledoc Renderers](https://github.com/bustlelabs/mobiledoc-kit/blob/master/RENDERERS.md)**

To learn more about the ideas behind Mobiledoc and the editor (note that the
editor used to be named Content-Kit), see these blog posts:
Expand Down Expand Up @@ -55,7 +59,7 @@ editor.render(element);
* `spellcheck` - [boolean] whether to enable spellcheck. Defaults to true.
* `autofocus` - [boolean] When true, focuses on the editor when it is rendered.
* `cards` - [array] The list of cards that the editor may render
* `cardOptions` - [object] Options passed to
* `cardOptions` - [object] Options passed to
* `unknownCardHandler` - [function] This will be invoked by the editor-renderer whenever it encounters an unknown card

### Editor API
Expand Down Expand Up @@ -132,10 +136,21 @@ It is important that you make changes to posts, sections, and markers through
the `run` and `postEditor` API. This API allows the Mobiledoc editor to conserve
and better understand changes being made to the post.

```js
editor.run(postEditor => {
const mention = postEditor.builder.createAtom("mention", "John Doe", { id: 42 });
// insert at current cursor position:
// or should the user have to grab the current position from the editor first?
postEditor.insert(mention);
// or specify a different position:
postEditor.insert(mention, position);
});
```

For more details on the API of `postEditor`, see the [API documentation](https://github.com/bustlelabs/mobiledoc-kit/blob/master/src/js/editor/post.js).

For more details on the API for the builder, required to create new sections
and markers, see the [builder API](https://github.com/bustlelabs/mobiledoc-kit/blob/master/src/js/models/post-node-builder.js).
atoms, and markers, see the [builder API](https://github.com/bustlelabs/mobiledoc-kit/blob/master/src/js/models/post-node-builder.js).

### Configuring hot keys

Expand Down
29 changes: 29 additions & 0 deletions RENDERERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Renderers

All of the officially-supported mobiledoc renderers have the same signature and methods.
To instantiate a renderer, call its constructor with an object of options that has any of the following optional properties:

* `atoms` [array] - The atoms that your mobiledoc includes and that the renderer will encounter
* `cards` [array] - The cards that your mobiledoc includes and that the renderer will encounter
* `cardOptions` [object] - Options to be passed to the card `render` (or `edit`) function and atoms `render` function
* `unknownAtomHandler` [function] - This function is called (with the same arguments as `render`) whenever the renderer encounters an atom that doesn't
match one of the `atoms` it has been provided
* `unknownCardHandler` [function] - This function is called (with the same arguments as `render`) whenever the renderer encounters a card that doesn't
match one of the `cards` it has been provided

An instance of a renderer has one method, `render`. This method accepts a mobiledoc and returns an object with two properties:
* `result` [mixed] - The rendered result. Its type depends on the renderer, and can be a DOM Node (dom renderer) or a string (html or text renderers)
* `teardown` [function] - Call this function to tear down the rendered mobiledoc. The dom renderer will remove the rendered dom from the screen. All renderers will call
the card teardown callbacks that were registered using `env.onTeardown(callbackFunction)`

Example usage of a renderer:
```js
let renderer = new DOMRenderer({atoms: [atom1, atom2], cards: [card1, card2], cardOptions: {foo: 'bar'});
let rendered = renderer.render(mobiledoc);

document.body.appendChild(renderered.result);

// later...

rendered.teardown(); // removes the rendered items, calls teardown hooks
```
6 changes: 6 additions & 0 deletions src/js/models/post-node-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ export default class PostNodeBuilder {
return marker;
}

createAtom(name, text, payload={}) {
const atom = new Atom(name, text, payload);
atom.builder = this;
return atom;
}

/**
* @param {Object} attributes {key:value}
*/
Expand Down

0 comments on commit 0a51e71

Please sign in to comment.