feat(docs): Compile article docs with grunt docs, better styling

This commit is contained in:
Ben Gotow 2015-05-08 19:17:09 -07:00
parent ff15099f46
commit f999de9b41
33 changed files with 556 additions and 292 deletions

View file

@ -49,6 +49,9 @@ thirdPartyClasses = {
}
module.exports = (grunt) ->
{cp, mkdir, rm} = require('./task-helpers')(grunt)
getClassesToInclude = ->
modulesPath = path.resolve(__dirname, '..', '..', 'internal_packages')
classes = {}
@ -127,6 +130,8 @@ module.exports = (grunt) ->
if filename[0] is '_' and path.extname(filename) is '.html'
Handlebars.registerPartial(filename[0..-6], grunt.file.read(abspath))
# Class-level documentation
templatePath = path.join(templatesPath, 'class.html')
template = Handlebars.compile(grunt.file.read(templatePath))
@ -174,3 +179,26 @@ module.exports = (grunt) ->
result = template(contents)
resultPath = path.join(docsOutputDir, "#{classname}.html")
grunt.file.write(resultPath, result)
# Conceptual documentation
articleTemplatePath = path.join(templatesPath, 'article.html')
articleTemplate = Handlebars.compile(grunt.file.read(articleTemplatePath))
articlesPath = path.resolve(__dirname, '..', '..', 'docs')
fs.traverseTreeSync articlesPath, (file) ->
if path.extname(file) is '.md'
content = grunt.file.read(file)
for task in [marked, expandTypeReferences, expandFuncReferences]
content = task(content)
result = articleTemplate({content})
resultPath = path.join(docsOutputDir, path.basename(file)[0..-3]+'html')
grunt.file.write(resultPath, result)
true
# Copy styles and images
imagesPath = path.resolve(__dirname, '..', '..', 'docs', 'images')
cssPath = path.resolve(__dirname, '..', '..', 'docs', 'css')
cp imagesPath, path.join(docsOutputDir, "images")
cp cssPath, path.join(docsOutputDir, "css")

View file

@ -0,0 +1,2 @@
</body>
</html>

View file

@ -1,19 +1,28 @@
<h4 id={{name}}>{{name}} <a href="#{{name}}">Link</a></h4>
<h4 id={{name}} class="function-name">
{{name}} <a href="#{{name}}" class="link"></a>
</h4>
<p>{{{description}}}</p>
{{#if arguments.length}}
Parameters
<table>
<strong>Parameters</strong>
<table class="arguments">
{{#each arguments}}
<tr><td>{{name}}</td><td>{{type}}</td><td>{{{description}}}</td><td>{{isOptional}}</td></tr>
<tr>
<td style="width:15%;">
<em>{{name}}</em>
</td>
<td>
{{{description}}} {{#if isOptional}} (Optional) {{/if}}
</td>
</tr>
{{/each}}
</table>
{{/if}}
{{#if returnValues.length}}
Returns
<table>
<strong>Returns</strong>
<table class="arguments">
{{#each returnValues}}
<tr><td>{{type}}</td><td>{{{description}}}</td></tr>
<tr><td style="width:15%;">{{type}}</td><td>{{{description}}}</td></tr>
{{/each}}
</table>
{{/if}}

View file

@ -0,0 +1,19 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<link rel="stylesheet" type="text/css" href="css/main.css"/>
<link rel="stylesheet" type="text/css" href="css/tomorrow.css">
</head>
<body>
<!--
<div style="border-bottom:1px solid #ccc;">
<img src="images/edgehill.png" style="width:50px; height:50px; float:right;"/>
<h2>Nylas Mail Developer Preview</h2>
<span>Last Revised: May 8, 2015</span>
</div>
<div style="border-bottom:1px solid #ccc; padding:10px; margin-bottom:20px;">
<a href="index.html">Home</a>
<a href="api.html">API Reference</a>
</div>
-->

View file

@ -1,9 +1,16 @@
<h4 id={{name}}>{{name}} <a href="#{{name}}">Link</a></h4>
<h4 id={{name}}>{{name}} <a href="#{{name}}" class="link"></a></h4>
<p>{{{description}}}</p>
{{#if arguments.length}}
<table>
<table class="arguments">
{{#each arguments}}
<tr><td>{{name}}</td><td>{{type}}</td><td>{{{description}}}</p></td><td>{{isOptional}}</td></tr>
<tr>
<td style="width:15%;">
<em>{{name}}</em>
</td>
<td>
{{{description}}} {{#if isOptional}} (Optional) {{/if}}
</td>
</tr>
{{/each}}
</table>
{{/if}}

View file

@ -0,0 +1,7 @@
{{>_header}}
<div class="article">
{{{content}}}
</div>
{{>_footer}}

View file

@ -1,167 +1,9 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<style>
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote {
margin: 0;
padding: 0;
}
body {
font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
font-size: 13px;
line-height: 18px;
color: #737373;
background-color: white;
margin: 10px 13px 10px 13px;
}
table {
margin: 10px 0 15px 0;
border-collapse: collapse;
}
td,th {
vertical-align: top;
border: 1px solid #ddd;
padding: 3px 10px;
}
th {
padding: 5px 10px;
}
{{>_header}}
a {
color: #0069d6;
}
a:hover {
color: #0050a3;
text-decoration: none;
}
a img {
border: none;
}
p,td {
margin-bottom: 9px;
font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
font-size: 13px;
line-height: 18px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #404040;
line-height: 36px;
}
h1 {
margin-bottom: 18px;
font-size: 30px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 13px;
}
hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family:georgia,serif;
font-style: italic;
}
blockquote:before {
content:"\201C";
font-size:40px;
margin-left:-10px;
font-family:georgia,serif;
color:#eee;
}
blockquote p {
font-size: 14px;
font-weight: 300;
line-height: 18px;
margin-bottom: 0;
font-style: italic;
}
code, pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
code {
background-color: #fee9cc;
color: rgba(0, 0, 0, 0.75);
padding: 1px 3px;
font-size: 12px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
pre {
display: block;
padding: 14px;
margin: 0 0 18px;
line-height: 16px;
font-size: 11px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
}
pre code {
background-color: #fff;
color:#737373;
font-size: 11px;
padding: 0;
}
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
* {
-webkit-print-color-adjust: exact;
}
@media screen and (min-width: 914px) {
body {
width: 854px;
margin:10px auto;
}
}
@media print {
body,code,pre code,h1,h2,h3,h4,h5,h6 {
color: black;
}
table, pre {
page-break-inside: avoid;
}
}
</style>
<title>asdasda</title>
</head>
<body>
<h2>{{name}}{{#if superClass}} extends {{superClass}}{{/if}}</h2>
<span>{{filename}}</span>
<h2>{{name}}</h2>
{{#if superClass}}
<span>extends {{superClass}}</span>
{{/if}}
<p>{{{description}}}</p>
@ -200,5 +42,4 @@ sup {
{{/if}}
</body>
</html>
{{>_footer}}

View file

@ -1,4 +1,4 @@
### Flux Architecture
### Application Architecture: Flux
Nylas Mail also uses [Reflux](https://github.com/spoike/refluxjs), a slim implementation of Facebook's [Flux Application Architecture](https://facebook.github.io/flux/) to coordinate the movement of data through the application. Flux is extremely well suited for applications that support third-party extension, because it emphasizes loose coupling and well defined interfaces between components. It enforces:
@ -35,4 +35,3 @@ For a complete list of available actions, see `Actions.coffee`. Actions in Nylas
- Main Window Actions: These actions can be fired in any window and are automatically sent to the main window via IPC. They are not sent to other windows of the app.
- Window Actions: These actions only broadcast within the window they're fired in.

View file

@ -2,7 +2,7 @@
Nylas Mail is built on top of a custom database layer modeled after ActiveRecord. For many parts of the application, the database is the source of truth. Data is retrieved from the API, written to the database, and changes to the database trigger Stores and components to refresh their contents. The illustration below shows this flow of data:
<img src="./images/database-flow.png" style="max-width:750px;">
<img src="./images/database-flow.png">
The Database connection is managed by the `DatabaseStore`, a singleton object that exists in every window. All Database requests are asynchronous. Queries are forwarded to the application's `Browser` process via IPC and run in SQLite.
@ -10,7 +10,7 @@ The Database connection is managed by the `DatabaseStore`, a singleton object th
In Nylas Mail, Models are thin wrappers around data with a particular schema. Each Model class declares a set of attributes that define the object's data. For example:
```
```coffee
class Example extends Model
@attributes:
@ -43,7 +43,7 @@ When models are inflated from JSON using `fromJSON` or converted to JSON using `
The DatabaseStore automatically maintains cache tables for storing Model objects. By default, models are stored in the cache as JSON blobs and basic attributes are not queryable. When the `queryable` option is specified on an attribute, it is given a separate column and index in the SQLite table for the model, and you can construct queries using the attribute:
```
```coffee
Thread.attributes.namespaceId.equals("123")
// where namespace_id = '123'
@ -58,7 +58,7 @@ Thread.attributes.lastMessageTimestamp.descending()
You can make queries for models stored in SQLite using a Promise-based ActiveRecord-style syntax. There is no way to make raw SQL queries against the local data store.
```
```coffee
DatabaseStore.find(Thread, '123').then (thread) ->
# thread is a thread object
@ -100,7 +100,7 @@ Joined Data attributes allow you to store certain attributes of an object in a s
When building a query on a model with a JoinedData attribute, you need to call `include` to explicitly load the joined data attribute. The query builder will automatically perform a `LEFT OUTER JOIN` with the secondary table to retrieve the attribute:
```
```coffee
DatabaseStore.find(Message, '123').then (message) ->
// message.body is undefined
@ -120,13 +120,13 @@ When Collection attributes are marked as `queryable`, the DatabaseStore automati
Collection attributes have an additional clause builder, `contains`:
```
```coffee
DatabaseStore.findAll(Thread).where([Thread.attributes.tags.contains('inbox')])
```
This is equivalent to writing the following SQL:
```
```sql
SELECT `Thread`.`data` FROM `Thread` INNER JOIN `Thread-Tag` AS `M1` ON `M1`.`id` = `Thread`.`id` WHERE `M1`.`value` = 'inbox' ORDER BY `Thread`.`last_message_timestamp` DESC
```
@ -136,13 +136,13 @@ For many parts of the application, the Database is the source of truth. Funnelin
Within Reflux Stores, you can listen to the DatabaseStore using the `listenTo` helper method:
```
```coffee
@listenTo(DatabaseStore, @_onDataChanged)
```
Within generic code, you can listen to the DatabaseStore using this syntax:
```
```coffee
@unlisten = DatabaseStore.listen(@_onDataChanged, @)
```
@ -163,6 +163,3 @@ Nylas Mail exposes a minimal Database API that exposes high-level methods for sa
- Package code should not be tightly coupled to SQLite
- Queries should be composed in a way that makes invalid queries impossible
- All changes to the local database must be observable

View file

@ -16,7 +16,7 @@ To create a Draft Store Extension, subclass {DraftStoreExtension} and override t
This extension displays a warning before sending a draft that contains the names of competitor's products and if the user proceeds to send the draft containing the words, it appends a disclaimer.
```
```coffee
{DraftStoreExtension} = require 'inbox-exports'
class ProductsExtension extends DraftStoreExtension
@ -37,4 +37,3 @@ class ProductsExtension extends DraftStoreExtension
or trademarks used in context."
session.changes.add(body: bodyWithWarning)
```

View file

@ -1,49 +1,60 @@
##Nylas Package API
<img src="images/edgehill.png" class="center-logo"/>
<h2 style="text-align:center;">Nylas Package API</h2>
<p style="text-align:center; width:75%; margin:auto; margin-top:18px;">
The Nylas Package API allows you to create powerful extensions to Nylas Mail. The client is built on top of Atom Shell and runs on Mac OS X, Windows, and Linux. It exposes rich APIs for working with the mail, contacts, and calendar and a robust local cache layer. Your packages can leverage NodeJS and other web technologies to create innovative new experiences.
</p>
<table>
<table class="no-border">
<tr><td style="width:50%;">
###Installing Nylas Mail
<h4>Installing Nylas Mail</h4>
<p>
Nylas Mail is available for Mac, Windows, and Linux. Download the latest build for your platform below:
- [Mac OS X](https://edgehill.nylas.com/download?platform=darwin)
- [Linux](https://edgehill.nylas.com/download?platform=linux)
- [Windows](https://edgehill.nylas.com/download?platform=win32)
</p>
<ul>
<li><a href="https://edgehill.nylas.com/download?platform=darwin">Mac OS X</a></li>
<li><a href="https://edgehill.nylas.com/download?platform=linux">Linux</a></li>
<li><a href="https://edgehill.nylas.com/download?platform=win32">Windows</a></li>
</ul>
</td><td style="width:50%;">
###Package Architecture
<h4>Package Architecture</h4>
<p>
Packages lie at the heart of Nylas Mail. Each part of the core experience is a separate package that uses the Nilas Package API to add functionality to the client. Learn more about packages and create your first package.
- [Package Overview](./PackageOverview.md)
</p>
<ul>
<li><a href="./PackageOverview.html">Package Overview</a></li>
</ul>
</td></tr>
<tr><td style="width:50%; vertical-align:top;">
### Dive Deeper
<h4>Dive Deeper</h4>
- [Application Architecture](./Architecture.md)
- [React & Component Injection](./React.md)
- [Core Interface Concepts](./InterfaceConcepts.md)
- [Accessing the Database](./Database.md)
- [Draft Store Extensions](./DraftStoreExtensions.md)
<ul>
<li><a href="./Architecture.html">Application Architecture</a></li>
<li><a href="./React.html">React & Component Injection</a></li>
<li><a href="./InterfaceConcepts.html">Core Interface Concepts</a></li>
<li><a href="./Database.html">Accessing the Database</a></li>
<li><a href="./DraftStoreExtensions.html">Draft Store Extensions</a></li>
</ul>
</td><td style="width:50%; vertical-align:top;">
### Debugging Packages
<h4>Debugging Packages</h4>
<p>
Nylas Mail is built on top of Electron, which runs the latest version of Chromium. Learn how to access debug tools in Electron and use our Developer Tools Extensions:
- [Debugging in Nylas](./Debugging.md)
</p>
<ul>
<li><a href="./Debugging.html">Debugging in Nylas</a></li>
</ul>
</td></tr>
<tr colspan="2"><td>
##### Questions?
Need help? Check out the [FAQ](./FAQ.md) or post a question in the [Nylas Mail Facebook Group](facebook.com/groups/nylas.mail)
<h4>Questions?</h4>
<p>
Need help? Check out the [FAQ](./FAQ.html) or post a question in the [Nylas Mail Facebook Group](facebook.com/groups/nylas.mail)
</p>
</td></tr>

View file

@ -4,7 +4,7 @@
The Nylas Mail user interface is conceptually organized into Sheets. Each Sheet represents a window of content. For example, the `Threads` sheet lies at the heart of the application. When the user chooses the "Files" tab, a separate `Files` sheet is displayed in place of `Threads`. When the user clicks a thread in single-pane mode, a `Thread` sheet is pushed on to the workspace and appears after a brief transition.
<img src="./images/sheets.png" style="max-width:400px;">
<img src="./images/sheets.png">
The `WorkspaceStore` maintains the state of the application's workspace and the stack of sheets currently being displayed. Your packages can declare "root" sheets which are listed in the app's main sidebar, or push custom sheets on top of sheets to display data.
@ -12,9 +12,9 @@ The Nilas Workspace supports two display modes: `split` and `list`. Each Sheet d
For each mode, Sheets register a set of column names.
<img src="./images/columns.png" style="max-width:800px;">
<img src="./images/columns.png">
```
```coffee
@defineSheet 'Threads', {root: true},
split: ['RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar']
list: ['RootSidebar', 'ThreadList']
@ -25,7 +25,7 @@ Column names are important. Once you've registered a sheet, your package (and ot
Sheets also have a `Header` and `Footer` region that spans all of their content columns. You can register components to appear in these regions to display notifications, add bars beneath the toolbar, etc.
```
```coffee
ComponentRegistry.register AccountSidebar,
location: WorkspaceStore.Location.RootSidebar
@ -42,11 +42,11 @@ Each column is laid out as a CSS Flexbox, making them extremely flexible. For mo
Toolbars in Nylas Mail are also powered by the Component Registry. Though toolbars appear to be a single unit at the top of a sheet, they are divided into columns with the same widths as the columns in the sheet beneath them.
<img src="./images/toolbar.png" style="max-width:800px;">
<img src="./images/toolbar.png">
Each Toolbar column is laid out using Flexbox. You can control where toolbar elements appear within the column using the CSS `order` attribute. To make it easy to position toolbar items on the left, right, or center of a column, we've added two "spacer" elements with `order:50` and `order:-50` that evenly use up available space. Other CSS attributes allow you to control whether your items shrink or expand as the column's size changes.
<img src="./images/toolbar-column.png" style="max-width:800px;">
<img src="./images/toolbar-column.png">
To add items to a toolbar, you inject them via the Component Registry. There are several ways of describing the location of a toolbar component which are useful in different scenarios:

View file

@ -40,7 +40,7 @@ Our package also contains source files, a spec file with complete tests for the
`package.json` lists `lib/main` as the root file of our package. As our package expands, we can add other source files. Since Nylas Mail runs NodeJS, you can `require` other source files, Node packages, etc. Inside `main.cjsx`, there are two important functions being exported:
```
```coffee
module.exports =
##

View file

@ -15,27 +15,27 @@ To use a standard component, require it from `ui-components` and use it in your
Here's a quick look at standard components you can require from `ui-components`:
- **Menu**: Allows you to display a list of items consistent with the rest of the Nylas Mail user experience.
- **{Menu}**: Allows you to display a list of items consistent with the rest of the Nylas Mail user experience.
- **Spinner**: Displays an indeterminate progress indicator centered within it's container.
- **{Spinner}**: Displays an indeterminate progress indicator centered within it's container.
- **Popover**: Component for creating menus and popovers that appear in response to a click and stay open until the user clicks outside them.
- **{Popover}**: Component for creating menus and popovers that appear in response to a click and stay open until the user clicks outside them.
- **Flexbox**: Component for creating a Flexbox layout.
- **{Flexbox}**: Component for creating a Flexbox layout.
- **RetinaImg**: Replacement for standard `<img>` tags which automatically resolves the best version of the image for the user's display and can apply many image transforms.
- **{RetinaImg}**: Replacement for standard `<img>` tags which automatically resolves the best version of the image for the user's display and can apply many image transforms.
- **ListTabular**: Component for creating a list of items backed by a paginating ModelView.
- **{ListTabular}**: Component for creating a list of items backed by a paginating ModelView.
- **MultiselectList**: Component for creating a list that supports multi-selection. (Internally wraps ListTabular)
- **{MultiselectList}**: Component for creating a list that supports multi-selection. (Internally wraps ListTabular)
- **MultiselectActionBar**: Component for creating a contextual toolbar that is activated when the user makes a selection on a ModelView.
- **{MultiselectActionBar}**: Component for creating a contextual toolbar that is activated when the user makes a selection on a ModelView.
- **ResizableRegion**: Component that renders it's children inside a resizable region with a draggable handle.
- **{ResizableRegion}**: Component that renders it's children inside a resizable region with a draggable handle.
- **TokenizingTextField**: Wraps a standard `<input>` and takes function props for tokenizing input values and displaying autocompletion suggestions.
- **{TokenizingTextField}**: Wraps a standard `<input>` and takes function props for tokenizing input values and displaying autocompletion suggestions.
- **EventedIFrame**: Replacement for the standard `<iframe>` tag which handles events directed at the iFrame to ensure a consistent user experience.
- **{EventedIFrame}**: Replacement for the standard `<iframe>` tag which handles events directed at the iFrame to ensure a consistent user experience.
### React Component Injection
@ -47,7 +47,7 @@ Once you've created components, the next step is to register them with the {Comp
It's easy to see where registered components are displayed in Nylas Mail. Enable the Developer bar at the bottom of the app by opening the Inspector panel, and then click "**Component Regions**":
<img src="./images/injected-components.png" style="max-width:800px;">
<img src="./images/injected-components.png">
Each region outlined in red is filled dynamically by looking up a React component or set of components from the Component Registry. You can see the role or location you'd need to register for, and the `props` that your component will receive in those locations.
@ -55,21 +55,21 @@ Here are a few examples of how to use it to extend Nylas Mail:
1. Add a component to the Thread List column:
```
```coffee
ComponentRegistry.register ThreadList,
location: WorkspaceStore.Location.ThreadList
```
2. Add a component to the action bar at the bottom of the Composer:
```
```coffee
ComponentRegistry.register TemplatePicker,
role: 'Composer:ActionButton'
```
3. Replace the `Participants` component that ships with Nylas Mail to display thread participants on your own:
```
```coffee
ComponentRegistry.register ParticipantsWithStatusDots,
role: 'Participants'
```
@ -85,7 +85,7 @@ There are also several convenience components that make it easy to dynamically i
- {InjectedComponent}: Renders the first component for the `matching` criteria you provide, and passes it the props in `externalProps`. See the API reference for more information.
```
```coffee
<InjectedComponent
matching={role:"Attachment"}
exposedProps={file: file, messageLocalId: @props.localId}/>
@ -93,7 +93,7 @@ There are also several convenience components that make it easy to dynamically i
- {InjectedComponentSet}: Renders all of the components `matching` criteria you provide inside a {Flexbox}, and passes it the props in `externalProps`. See the API reference for more information.
```
```coffee
<InjectedComponentSet
className="message-actions"
matching={role:"MessageAction"}
@ -104,6 +104,6 @@ There are also several convenience components that make it easy to dynamically i
Nylas Mail considers all injected components "unsafe". When you render them using {InjectedComponent} or {InjectedComponentSet}, they will be wrapped in a component that prevents exceptions in their React render and lifecycle methods from impacting your component. Instead of your component triggering a React Invariant exception in the application, an exception notice will be rendered in place of the unsafe component.
<img src="./images/unsafe-component-exception.png" style="max-width:800px;">
<img src="./images/unsafe-component-exception.png">
In the future, Nylas Mail may automatically disable packages when their React components throw exceptions.

223
docs/css/main.css Normal file
View file

@ -0,0 +1,223 @@
@font-face {
font-family: 'FaktPro';
font-style: normal;
font-weight: 400;
src: local('FaktPro-Blond'), url('fonts/FaktPro-Blond.ttf'), local('Helvetica Neue'), local('Helvetica');
}
@font-face {
font-family: 'FaktPro';
font-style: normal;
font-weight: 500;
src: local('FaktPro-Medium'), url('fonts/FaktPro-Medium.ttf'), local('Helvetica Neue'), local('Helvetica');
}
@font-face {
font-family: 'FaktPro';
font-style: normal;
font-weight: 600;
src: local('FaktPro-SemiBold'), url('fonts/FaktPro-SemiBold.ttf'), local('Helvetica Neue'), local('Helvetica');
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote {
margin: 0;
padding: 0;
}
strong {
color: #404040;
}
body {
font-family: 'FaktPro';
font-weight: normal;
font-size: 17px;
line-height: 1.5em;
color: #737373;
background-color: white;
margin: 10px 13px 10px 13px;
}
table {
margin: 10px 0 15px 0;
border-collapse: collapse;
}
td,th {
vertical-align: top;
border: 1px solid #ddd;
padding: 10px;
line-height: 1.5em;
}
th {
padding: 5px 10px;
}
a {
color: #0069d6;
text-decoration: none;
}
a:hover {
color: #0050a3;
text-decoration: none;
}
a img {
border: none;
}
p {
margin-bottom: 12px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #404040;
line-height: 36px;
}
h1 {
margin-bottom: 18px;
font-size: 30px;
}
h2 {
font-size: 30px;
}
h3 {
font-size: 24px;
margin-top:30px;
}
h4 {
font-size: 18px;
margin-top:20px;
}
h5 {
font-size: 16px;
}
h6 {
font-size: 14px;
}
hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family:georgia,serif;
font-style: italic;
}
blockquote:before {
content:"\201C";
font-size:40px;
margin-left:-10px;
font-family:georgia,serif;
color:#eee;
}
blockquote p {
font-size: 16px;
font-weight: normal;
margin-bottom: 0;
font-style: italic;
}
img {
max-width:100%;
}
code, pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
code {
color: rgba(0, 0, 0, 0.75);
background-color: #fee9cc;
padding: 1px 3px;
font-size: 14px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
pre {
margin: 0 0 18px;
line-height: 18px;
font-size: 14px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
}
pre code {
font-size: 13px;
background-color: white;
padding: 0;
display: block;
padding: 14px;
}
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
* {
-webkit-print-color-adjust: exact;
}
.link {
width:18px;
height:18px;
display:inline-block;
vertical-align:sub;
opacity:0.4;
background:url(../images/link.png) top left;
background-size:cover;
}
@media screen and (min-width: 914px) {
body {
width: 854px;
margin:10px auto;
}
}
@media print {
body,code,pre code,h1,h2,h3,h4,h5,h6 {
color: black;
}
table, pre {
page-break-inside: avoid;
}
}
.article img {
margin-top:18px;
margin-bottom:18px;
border:1px solid #ccc;
}
.article .center-logo {
width:270px;
height:270px;
margin:auto;
margin-top:20px;
display:block;
border:0;
}
.function-name {
border-bottom:1px solid #ccc;
padding-top:10px;
}
table.arguments {
width:100%;
}
table.arguments p {
margin-bottom:0;
}
table.no-border td {
border:0;
}

93
docs/css/tomorrow.css Normal file
View file

@ -0,0 +1,93 @@
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Tomorrow Comment */
.hljs-comment {
color: #8e908c;
}
/* Tomorrow Red */
.hljs-variable,
.hljs-attribute,
.hljs-tag,
.hljs-regexp,
.ruby .hljs-constant,
.xml .hljs-tag .hljs-title,
.xml .hljs-pi,
.xml .hljs-doctype,
.html .hljs-doctype,
.css .hljs-id,
.css .hljs-class,
.css .hljs-pseudo {
color: #c82829;
}
/* Tomorrow Orange */
.hljs-number,
.hljs-preprocessor,
.hljs-pragma,
.hljs-built_in,
.hljs-literal,
.hljs-params,
.hljs-constant {
color: #f5871f;
}
/* Tomorrow Yellow */
.ruby .hljs-class .hljs-title,
.css .hljs-rule .hljs-attribute {
color: #eab700;
}
/* Tomorrow Green */
.hljs-string,
.hljs-value,
.hljs-inheritance,
.hljs-header,
.hljs-name,
.ruby .hljs-symbol,
.xml .hljs-cdata {
color: #718c00;
}
/* Tomorrow Aqua */
.hljs-title,
.css .hljs-hexcolor {
color: #3e999f;
}
/* Tomorrow Blue */
.hljs-function,
.python .hljs-decorator,
.python .hljs-title,
.ruby .hljs-function .hljs-title,
.ruby .hljs-title .hljs-keyword,
.perl .hljs-sub,
.javascript .hljs-title,
.coffeescript .hljs-title {
color: #4271ae;
}
/* Tomorrow Purple */
.hljs-keyword,
.javascript .hljs-function {
color: #8959a8;
}
.hljs {
display: block;
overflow-x: auto;
background: white;
color: #4d4d4c;
padding: 0.5em;
-webkit-text-size-adjust: none;
}
.coffeescript .javascript,
.javascript .xml,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}

BIN
docs/images/edgehill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

BIN
docs/images/link.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

View file

@ -13,7 +13,7 @@ Public: InjectedComponent makes it easy to include a set of dynamically register
components inside of your React render method. Rather than explicitly render
an array of buttons, for example, you can use InjectedComponentSet:
```
```coffee
<InjectedComponentSet className="message-actions"
matching={role: 'ThreadActionButton'}
exposedProps={thread:@props.thread, message:@props.message}>

View file

@ -12,7 +12,7 @@ Public: InjectedComponent makes it easy to include dynamically registered
components inside of your React render method. Rather than explicitly render
a component, such as a `<Composer>`, you can use InjectedComponent:
```
```coffee
<InjectedComponent matching={role:"Composer"} exposedProps={draftId:123} />
```

View file

@ -15,7 +15,7 @@ When the provided `dataStore` has a selection, it appears over the other items i
Generally, you wrap {MultiselectActionBar} in your own simple component to provide a dataStore
and other settings:
```
```coffee
class MultiselectActionBar extends React.Component
@displayName: 'MultiselectActionBar'
@ -29,7 +29,7 @@ class MultiselectActionBar extends React.Component
The MultiselectActionBar uses the `ComponentRegistry` to find items to display for the given
collection name. To add an item to the bar created in the example above, register it like this:
```
```coffee
ComponentRegistry.register ThreadBulkArchiveButton,
role: 'thread:BulkAction'
```

View file

@ -2,9 +2,6 @@ React = require 'react/addons'
_ = require 'underscore-plus'
{CompositeDisposable} = require 'event-kit'
###
###
class QuotedTextToggleButton extends React.Component
@displayName: "QuotedTextToggleButton"

View file

@ -59,7 +59,7 @@ This is adapted from Facebook's CSSTransitionGroup which is in the React
addons and under the Apache 2.0 License.
Example:
```
```coffee
<TimeoutTransitionGroup
leaveTimeout={125}
enterTimeout={125}

View file

@ -13,7 +13,7 @@ your `flex` and `order` values to be one level too deep. For these scenarios,
UnsafeComponent looks for `containerStyles` on your React component and attaches
them to the wrapper div:
```
```coffee
class MyComponent extends React.Component
@displayName: 'MyComponent'
@containerStyles:

View file

@ -15,13 +15,13 @@ collection.
Collection attributes have an additional clause builder, `contains`:
```
```coffee
DatabaseStore.findAll(Thread).where([Thread.attributes.tags.contains('inbox')])
```
This is equivalent to writing the following SQL:
```
```sql
SELECT `Thread`.`data` FROM `Thread`
INNER JOIN `Thread-Tag` AS `M1` ON `M1`.`id` = `Thread`.`id`
WHERE `M1`.`value` = 'inbox'

View file

@ -17,7 +17,7 @@ to call `include` to explicitly load the joined data attribute.
The query builder will automatically perform a `LEFT OUTER JOIN` with
the secondary table to retrieve the attribute:
```
```coffee
DatabaseStore.find(Message, '123').then (message) ->
# message.body is undefined

View file

@ -2,10 +2,35 @@ _ = require 'underscore-plus'
{tableNameForJoin} = require '../models/utils'
###
Public: The Matcher class encapsulates a particular comparison clause on an attribute.
Matchers can evaluate whether or not an object matches them, and in the future
they will also compose WHERE clauses. Each matcher has a reference to a model
Public: The Matcher class encapsulates a particular comparison clause on an {Attribute}.
Matchers can evaluate whether or not an object matches them, and also compose
SQL clauses for the DatabaseStore. Each matcher has a reference to a model
attribute, a comparator and a value.
```coffee
# Retrieving Matchers
isUnread = Thread.attributes.unread.equal(true)
isInInbox = Thread.attributes.tags.contains('inbox')
# Using Matchers in Database Queries
DatabaseStore.findAll(Thread).where(isUnread)...
# Using Matchers to test Models
threadA = new Thread(unread: true)
threadB = new Thread(unread: false)
isUnread.evaluate(threadA)
# => true
isUnread.evaluate(threadB)
# => false
```
###
class Matcher
constructor: (@attr, @comparator, @val) ->

View file

@ -1,7 +1,14 @@
###
Public: Represents a particular sort direction on a particular column. You should not
instantiate SortOrders manually. Instead, call `Attribute.ascending()` or
`Attribute.descending()` to obtain a sort order.
instantiate SortOrders manually. Instead, call {Attribute::ascending} or
{Attribute::descending} to obtain a sort order instance:
```coffee
DatabaseStore.findBy(Message)
.where({threadId: threadId, draft: false})
.order(Message.attributes.date.descending()).then (messages) ->
```
###
class SortOrder
constructor: (@attr, @direction = 'DESC') ->

View file

@ -13,7 +13,7 @@ and {DatabaseStore::unpersistModel}.
**Simple Example:** Fetch a thread
```
```coffee
query = DatabaseStore.find(Thread, '123a2sc1ef4131')
query.then (thread) ->
# thread or null
@ -21,7 +21,7 @@ query.then (thread) ->
**Advanced Example:** Fetch 50 threads in the inbox, in descending order
```
```coffee
query = DatabaseStore.findAll(Thread)
query.where([Thread.attributes.tagIds.contains('inbox')])
.order([Thread.attributes.lastMessageTimestamp.descending()])
@ -125,7 +125,7 @@ class ModelQuery
# Public:
#
# - `offset` {Number] The start offset of the query.
# - `offset` {Number} The start offset of the query.
#
# This method is chainable.
#
@ -160,7 +160,7 @@ class ModelQuery
# Public: Starts query execution and returns a Promise.
#
# Returns A {Promise} that resolves with the Models returned by the
# Returns a {Promise} that resolves with the Models returned by the
# query, or rejects with an error from the Database layer.
#
then: (next) ->

View file

@ -19,7 +19,7 @@ The ContactStore monitors the {DatabaseStore} for changes to {Contact} models
and triggers when contacts have changed, allowing your stores and components
to refresh data based on the ContactStore.
```
```coffee
@unsubscribe = ContactStore.listen(@_onContactsChanged, @)
_onContactsChanged: ->

View file

@ -111,7 +111,7 @@ rest of the application in sync.
To listen for changes to the local cache, subscribe to the DatabaseStore and
inspect the changes that are sent to your listener method.
```
```coffeescript
@unsubscribe = DatabaseStore.listen(@_onDataChanged, @)
...
@ -382,7 +382,7 @@ class DatabaseStore
# - `id` The {String} id of the {Model} you're trying to retrieve
#
# Example:
# ```
# ```coffee
# DatabaseStore.find(Thread, 'id-123').then (thread) ->
# # thread is a Thread object, or null if no match was found.
# ```

View file

@ -7,7 +7,7 @@ To register your extension with the DraftStore, call {DraftStore::registerExtens
When your package is being unloaded, you *must* call the corresponding
{DraftStore::unregisterExtension} to unhook your extension.
```
```coffee
activate: ->
DraftStore.registerExtension(MyExtension)
@ -51,7 +51,7 @@ class DraftStoreExtension
Example:
```
```coffee
# Remove any <code> tags found in the draft body
finalizeSessionBeforeSending: (session) ->
body = session.draft().body
@ -122,7 +122,7 @@ class DraftStoreExtension
`<code>` tag placed in the body and change it's CSS class to reflect that it is no
longer empty.
```
```coffee
onInput: (editableNode, event) ->
selection = document.getSelection()

View file

@ -76,7 +76,7 @@ class DraftStore
#
# Example:
#
# ```
# ```coffee
# session = DraftStore.sessionForLocalId(localId)
# session.prepare().then ->
# # session.draft() is now ready