Composer UI interacts with the Python WSGI app at https://github.com/dailydot/composer to manage article content.
Rather than allowing markup to structure content, Composer uses the concept of Nodes
. A Node
represents a piece of
content, such as a paragraph, an image, a heading, a list of text items, or more sophisticated third-party content sources
called embeds.
At the top level, Composer uses a dot.cms.editor.Editor
instance to manage dot.cms.editor.articles.Article
instances.
An Editor
instance will manage a single Article
instance at a time, but that Article instance can be replaced, removed,
or recreated using the same Editor
instance.
Article instances describe the specific article, including fields for properties like title, summary, author, creation
or modification dates, etc. The content of the article is described as a list (array) of Nodes
, in this package represented
by dot.cms.editor.articles.ArticleContentNode
instances.
A client application will receive an article (or articles) with the properties described above; it is the responsibility
of that client application to display the article using that information. For example, a web browser might display a list
of articles using a thumbnail image, the tile, and a summary. When drilling down to detail state for a single article,
a web browser might display a paragraph using a <p>
tag, but also might decorate that tag with CSS class names, inline styles,
data attributes, etc. After each <p>
element, the client might choose to render another decorator element, such as an
icon or horizontal rule. Contrast this with an XML feed - a paragraph might remove any formatting instructions within the
paragraph and print out <item>
elements. A web client might display related content within an inline-frame, but that same content
might be represented by a link or button in a mobile application.
Include all JavaScript files in the dot
directory of this repo.
There is an example implementation in the /app/
directory of this repo. This should work out of the box, assuming the Composer
backend is running on port 5000 (otherwise, just update the url
variable in /app/assets/scripts/application.js
).
The API Key and Client ID used for the Google Document selection UI should be replaced with a property owned by the Daily Dot; the current configuration is from a personal account.
Create an Editor instance:
var editor = new dot.cms.editor.Editor();
Register Node classes the editor should be able to handle:
editor.registerNodeType(dot.cms.editor.articles.nodes.ParagraphNode);
...
Initialize it with URL to Composer backend, and a map of DOM element needed to display the UI:
editor.initialize('http://path.to/REST/API/', {
controls : document.getElementById('cms-editor-add-nodes'),
form : document.getElementById('cms-editor-form'),
preview : document.getElementById('cms-editor-preview'),
toggle : document.getElementById('cms-editor-toggle-button')
});
The editor can create new Article
instance, or show (and edit) existing Article
s. How those articles are provided
to the editor is up to the author (e.g., a select box, a list of links, a GET parameter, etc); an article can be loaded by
its UUID with the fetchArticleById
method. Changes are saved using the saveArticle
method.
To create a new article:
editor.initializeNewArticle();
To load an existing article into the editor, by UUID:
editor.fetchArticleById(id);
To reload the currently displayed article:
editor.fetchArticle();
To save the currently displayed article to the server:
editor.saveArticle();
To set the current article values from an object (probably the parse of a JSON string from the server):
editor.parseArticle(description);
To set the current article values from a JSON string:
var description = JSON.parse(jsonString);
editor.parseArticle(description);
There is not an editor API to get a list of existing articles, but it can be accomplished easily with native methods:
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(e){
var json = JSON.parse(e.target.responseText);
// you may want to do some validation here,
// e.g., test for json.error, or ensure json.data exists, etc
var articles = json.data; // this is an array of article instances
});
xhr.open('GET', url);
xhr.send();
The dot.cms.editor.importer.MarkupToNodeConverter
class contains functionality to convert arbitrary HTML blobs into
node lists, generally either from a loaded document, or from a paste event. This happens in (roughly) two passes: the first normalized the HTML to a predictable structure, and removes
markup that is not relevant or not supported; the second pass consumes that normalized structure and mutates a list
of Nodes
.
A string of HTML passed to a MarkupToNodeConverter
's convert
method will be normalized and each child passed to
each processor function registered with the converter:
var converter = new dot.cms.editor.importer.MarkupToNodeConverter();
converter.registerProcessor(yourProcessorFunction);
...
The processor function is passed information about the DOM node, and access to the converter instance and the converter's
existing ArticleContentNode
list. The processor can react in any way the author wants, but generally the element is
inspected for potential Nodes
, and the Node
list is mutated. Once all processors have been executed, the current
article's Node
list can be updated with dot.cms.editor.articles.Article.setNodes
method.
-
Get latest
composer-frontend
repo from https://github.com/dailydot/composer-frontend. -
Create a class that inherits from
dot.cms.editor.articles.ArticleNode
(see jsdocs in class definition). -
Add rule in
editor.css
for toolbar icon:.<add-node-selector> { background-image: url(../images/name-of-icon.png); }
-
Add rule in
editor.css
for drag hint:.<add-node-selector>.gu-transit:after { content: "Add <Human Readable Label of Node> Here"; }
-
Add rule in
editor.css
for tag:.cms-editor-node-wrapper-<slug>-node:before { content: '<Human Readable Label of Node>'; }
-
Register the node with the editor instance:
-
editorInstance.registerNodeType(dot.cms.editor.articles.nodes.YourNewNodeType);
-
Create and register a processor function for importation (see jsdoc for dot.cms.editor.importer.MarkupToNodeConverter for signature):
markupToNodeConverterInstance.registerProcessor(yourProcessorFunction);
-
In
tests
, create a file with Jasmine unit tests describing your new node. -
Get latest
composer
repo from https://github.com/dailydot/composer. -
In
nodes.py
, define your class:class YourNewNodeType(Node): __typename__ = 'your-new-node-type' def __init__(self, **kwargs): self.some_arbitrary_property = fields.HTMLField() super(YourNewNodeType, self).__init__(**kwargs)
-
In
node_tests.py
, create unit tests:class YourNewNodeTypeTests(TestCase): def test_typename(self): self.assertEqual(nodes.YourNewNodeType.__typename__, 'your-new-node-type') def test_create(self): n = nodes.YourNewNodeType(some_arbitrary_property='It was a dark and stormy night...') self.assertEqual(n.some_arbitrary_property.get(), 'It was a dark and stormy night...')
This is really clear and helpful documentation. One thing I'd add is a shoutout to the reference implementation in
app/
(which you may want to move to a more descriptive subdirectory likeexample
orreference
once you're done).