Move guides from Edgehill repo to Redwood and only generate docs from classes
|
@ -52,9 +52,6 @@ module.exports = (grunt) ->
|
|||
|
||||
{cp, mkdir, rm} = require('./task-helpers')(grunt)
|
||||
|
||||
relativePathForArticle = (filename) ->
|
||||
filename[0..-4]+'.html'
|
||||
|
||||
relativePathForClass = (classname) ->
|
||||
classname+'.html'
|
||||
|
||||
|
@ -170,51 +167,8 @@ module.exports = (grunt) ->
|
|||
|
||||
# Parse Article Markdown
|
||||
|
||||
articles = []
|
||||
articlesPath = path.resolve(__dirname, '..', '..', 'docs')
|
||||
fs.traverseTreeSync articlesPath, (file) ->
|
||||
if path.extname(file) is '.md'
|
||||
{html, meta} = marked(grunt.file.read(file))
|
||||
|
||||
filename = path.basename(file)
|
||||
meta ||= {title: filename}
|
||||
for key, val of meta
|
||||
meta[key.toLowerCase()] = val
|
||||
|
||||
articles.push({
|
||||
html: html
|
||||
meta: meta
|
||||
name: meta.title
|
||||
filename: filename
|
||||
link: relativePathForArticle(filename)
|
||||
})
|
||||
|
||||
# Sort articles by the `Order` flag when present. Lower order, higher in list.
|
||||
articles.sort (a, b) ->
|
||||
(a.meta?.order ? 1000)/1 - (b.meta?.order ? 1000)/1
|
||||
|
||||
# Build Sidebar metadata we can hand off to each of the templates to
|
||||
# generate the sidebar
|
||||
sidebar = {sections: []}
|
||||
sidebar.sections.push
|
||||
name: 'Getting Started'
|
||||
items: articles.filter ({meta}) -> meta.section is 'Getting Started'
|
||||
|
||||
sidebar.sections.push
|
||||
name: 'Guides'
|
||||
items: articles.filter ({meta}) -> meta.section is 'Guides'
|
||||
|
||||
sidebar.sections.push
|
||||
name: 'Sample Code'
|
||||
items: [{
|
||||
name: 'Composer Translation'
|
||||
link: 'https://github.com/nylas/edgehill-plugins/tree/master/translate'
|
||||
external: true
|
||||
},{
|
||||
name: 'Github Sidebar'
|
||||
link: 'https://github.com/nylas/edgehill-plugins/tree/master/sidebar-github-profile'
|
||||
external: true
|
||||
}]
|
||||
|
||||
referenceSections = {}
|
||||
for klass in classes
|
||||
|
@ -233,13 +187,18 @@ module.exports = (grunt) ->
|
|||
for key, val of referenceSections
|
||||
sorted.push(val)
|
||||
|
||||
sidebar.sections.push
|
||||
name: 'API Reference'
|
||||
items: sorted.map ({name, classes}) ->
|
||||
api_sidebar_meta = sorted.map ({name, classes}) ->
|
||||
name: name
|
||||
items: classes.map ({name}) -> {name: name, link: relativePathForClass(name) }
|
||||
items: classes.map ({name}) -> {name: name, link: relativePathForClass(name), slug: name.toLowerCase() }
|
||||
|
||||
console.log("Here's the sidebar info:")
|
||||
console.log(api_sidebar_meta.toString())
|
||||
|
||||
docsOutputDir = grunt.config.get('docsOutputDir')
|
||||
sidebarJson = JSON.stringify(api_sidebar_meta, null, 2)
|
||||
sidebarPath = path.join(docsOutputDir, '_sidebar.json')
|
||||
grunt.file.write(sidebarPath, sidebarJson)
|
||||
|
||||
console.log(sidebar)
|
||||
|
||||
|
||||
# Prepare to render by loading handlebars partials
|
||||
|
@ -255,10 +214,6 @@ module.exports = (grunt) ->
|
|||
for classname, val of apiJSON.classes
|
||||
knownClassnames[classname.toLowerCase()] = val
|
||||
|
||||
knownArticles = {}
|
||||
for article in articles
|
||||
knownArticles[article.filename.toLowerCase()] = article
|
||||
|
||||
expandTypeReferences = (val) ->
|
||||
refRegex = /{([\w.]*)}/g
|
||||
while (match = refRegex.exec(val)) isnt null
|
||||
|
@ -271,9 +226,6 @@ module.exports = (grunt) ->
|
|||
url = thirdPartyClasses[term]
|
||||
else if knownClassnames[term]
|
||||
url = relativePathForClass(term)
|
||||
else if knownArticles[term]
|
||||
label = knownArticles[term].meta.title
|
||||
url = relativePathForArticle(knownArticles[term].filename)
|
||||
else
|
||||
console.warn("Cannot find class named #{term}")
|
||||
|
||||
|
@ -307,25 +259,5 @@ module.exports = (grunt) ->
|
|||
processFields(documentation, ['description'], [marked.noMeta, expandTypeReferences, expandFuncReferences])
|
||||
processFields(documentation, ['type'], [expandTypeReferences])
|
||||
|
||||
result = classTemplate({name, documentation, section, sidebar})
|
||||
result = classTemplate({name, documentation, section})
|
||||
grunt.file.write(outputPathFor(relativePathForClass(name)), result)
|
||||
|
||||
# Render Article Pages
|
||||
|
||||
articleTemplatePath = path.join(templatesPath, 'article.html')
|
||||
articleTemplate = Handlebars.compile(grunt.file.read(articleTemplatePath))
|
||||
|
||||
for {name, meta, html, filename} in articles
|
||||
# Process the article content to expand references to types, functions
|
||||
for task in [expandTypeReferences, expandFuncReferences]
|
||||
html = task(html)
|
||||
|
||||
result = articleTemplate({name, meta, html, sidebar})
|
||||
grunt.file.write(outputPathFor(relativePathForArticle(filename)), result)
|
||||
|
||||
# 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")
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<div class="container">
|
||||
<img src="images/nylas.png" class="logo" />
|
||||
<div class="small">Nylas Mail Developer Preview<br><em>© 2014-2015 Nylas, Inc.</em></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Nylas Mail — {{name}}</title>
|
||||
<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 id="header">
|
||||
<div class="container">
|
||||
<img src="images/nylas.png" class="logo" />
|
||||
<div class="title">Nylas Mail<div class="small">Developer Preview</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
|
@ -1,3 +0,0 @@
|
|||
{{#each sidebar.sections}}
|
||||
{{>_sidebarSection}}
|
||||
{{/each}}
|
|
@ -1,10 +0,0 @@
|
|||
<div class="heading">{{name}}</div>
|
||||
<ul>
|
||||
{{#each items}}
|
||||
{{#if items}}
|
||||
{{> _sidebarSection}}
|
||||
{{else}}
|
||||
<li><a href="{{link}}" {{#if external}}target="_blank"{{/if}}>{{name}}</a></li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</ul>
|
|
@ -1,15 +0,0 @@
|
|||
{{>_header}}
|
||||
|
||||
{{#if meta.TitleHidden}}
|
||||
{{else}}
|
||||
<div class="page-title">{{meta.title}}</div>
|
||||
{{/if}}
|
||||
|
||||
<div id="sidebar">
|
||||
{{>_sidebar}}
|
||||
</div>
|
||||
<div id="main" class="article">
|
||||
{{{html}}}
|
||||
</div>
|
||||
|
||||
{{>_footer}}
|
|
@ -1,5 +1,3 @@
|
|||
{{>_header}}
|
||||
|
||||
<div class="page-title">
|
||||
{{name}}
|
||||
{{#if documentation.superClass}}
|
||||
|
@ -7,51 +5,44 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div id="sidebar">
|
||||
{{>_sidebar}}
|
||||
|
||||
<h2>Summary</h2>
|
||||
|
||||
<div class="markdown-from-sourecode">
|
||||
<p>{{{documentation.description}}}</p>
|
||||
</div>
|
||||
|
||||
<div id="main">
|
||||
<h2>Summary</h2>
|
||||
|
||||
<div class="markdown-from-sourecode">
|
||||
<p>{{{documentation.description}}}</p>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{{#each documentation.sections}}
|
||||
<li><a href="#{{name}}">{{name}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<ul>
|
||||
{{#each documentation.sections}}
|
||||
<li><a href="#{{name}}">{{name}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
|
||||
{{#if documentation.classProperties.length}}
|
||||
<h3>Class Properties</h3>
|
||||
{{#if documentation.classProperties.length}}
|
||||
<h3>Class Properties</h3>
|
||||
|
||||
{{#each documentation.classProperties}}
|
||||
{{>_property}}
|
||||
{{/each}}
|
||||
{{#each documentation.classProperties}}
|
||||
{{>_property}}
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if documentation.classMethods.length}}
|
||||
<h3>Class Methods</h3>
|
||||
{{#if documentation.classMethods.length}}
|
||||
<h3>Class Methods</h3>
|
||||
|
||||
{{#each documentation.classMethods}}
|
||||
{{>_function}}
|
||||
{{/each}}
|
||||
{{#each documentation.classMethods}}
|
||||
{{>_function}}
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if documentation.instanceMethods.length}}
|
||||
<h3>Instance Methods</h3>
|
||||
{{#if documentation.instanceMethods.length}}
|
||||
<h3>Instance Methods</h3>
|
||||
|
||||
{{#each documentation.instanceMethods}}
|
||||
{{>_function}}
|
||||
{{/each}}
|
||||
{{#each documentation.instanceMethods}}
|
||||
{{>_function}}
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{>_footer}}
|
||||
{{/if}}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
Title: Application Architecture
|
||||
Section: Guides
|
||||
Order: 1
|
||||
---
|
||||
|
||||
Nylas Mail 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:
|
||||
|
||||
- **Uni-directional data flow**
|
||||
- **Loose coupling between components**
|
||||
|
||||
For more information about the Flux pattern, check out [this diagram](https://facebook.github.io/flux/docs/overview.html#structure-and-data-flow). For a bit more insight into why we chose Reflux over other Flux implementations, there's a great [blog post](http://spoike.ghost.io/deconstructing-reactjss-flux/) by the author of Reflux.
|
||||
|
||||
There are several core stores in the application:
|
||||
|
||||
- **{NamespaceStore}**: When the user signs in to Nylas Mail, their auth token provides one or more namespaces. The NamespaceStore manages the available Namespaces, exposes the current Namespace, and allows you to observe changes to the current namespace.
|
||||
|
||||
- **{TaskQueue}**: Manages {Task}s, operations queued for processing on the backend. Task objects represent individual API actions and are persisted to disk, ensuring that they are performed eventually. Each Task may depend on other tasks, and Tasks are executed in order.
|
||||
|
||||
- **{DatabaseStore}**: The {DatabaseStore} marshalls data in and out of the local cache, and exposes an ActiveRecord-style query interface. You can observe the DatabaseStore to monitor the state of data in Nylas Mail.
|
||||
|
||||
- **{DraftStore}**: Manages Drafts, which are {Message} objects the user is authoring. Drafts present a unique case in Nylas Mail because they may be updated frequently by disconnected parts of the application. You should use the {DraftStore} to create, edit, and send drafts.
|
||||
|
||||
- **{FocusedContentStore}**: Manages focus within the main applciation window. The {FocusedContentStore} allows you to query and monitor changes to the selected thread, tag, file, etc.
|
||||
|
||||
Most packages declare additional stores that subscribe to these Stores, as well as user Actions, and vend data to the package's React components.
|
||||
|
||||
|
||||
###Actions
|
||||
|
||||
In Flux applications, views fire {Actions}, which anyone in the application can subscribe to. Typically, `Stores` listen to actions to perform business logic and trigger updates to their corresponding views. For example, when you click "Compose" in the top left corner of Nylas Mail, the React component for the button fires {Actions::composeNewBlankDraft}. The {DraftStore} listens to this action and opens a new composer window.
|
||||
|
||||
This approach means that your packages can fire existing {Actions}, like {Actions::composeNewBlankDraft}, or observe actions to add functionality. (For example, we have an Analytics package that also listens for {Actions::composeNewBlankDraft} and counts how many times it's been fired.) You can also define your own actions for use within your package.
|
||||
|
||||
For a complete list of available actions, see {Actions}.
|
169
docs/Database.md
|
@ -1,169 +0,0 @@
|
|||
---
|
||||
Title: Accessing the Database
|
||||
Section: Guides
|
||||
Order: 4
|
||||
---
|
||||
|
||||
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">
|
||||
|
||||
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.
|
||||
|
||||
#####Declaring Models
|
||||
|
||||
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:
|
||||
'id': Attributes.String
|
||||
queryable: true
|
||||
modelKey: 'id'
|
||||
|
||||
'object': Attributes.String
|
||||
modelKey: 'object'
|
||||
|
||||
'namespaceId': Attributes.String
|
||||
queryable: true
|
||||
modelKey: 'namespaceId'
|
||||
jsonKey: 'namespace_id'
|
||||
|
||||
'body': Attributes.JoinedData
|
||||
modelTable: 'MessageBody'
|
||||
modelKey: 'body'
|
||||
|
||||
'files': Attributes.Collection
|
||||
modelKey: 'files'
|
||||
itemClass: File
|
||||
|
||||
'unread': Attributes.Boolean
|
||||
queryable: true
|
||||
modelKey: 'unread'
|
||||
```
|
||||
|
||||
When models are inflated from JSON using `fromJSON` or converted to JSON using `toJSON`, only the attributes declared on the model are copied. The `modelKey` and `jsonKey` options allow you to specify where a particular key should be found. Attributes are also coerced to the proper types: String attributes will always be strings, Boolean attributes will always be `true` or `false`, etc. `null` is a valid value for all types.
|
||||
|
||||
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'
|
||||
|
||||
Thread.attributes.lastMessageTimestamp.greaterThan(123)
|
||||
// where last_message_timestamp > 123
|
||||
|
||||
Thread.attributes.lastMessageTimestamp.descending()
|
||||
// order by last_message_timestamp DESC
|
||||
```
|
||||
|
||||
#####Retrieving Models
|
||||
|
||||
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
|
||||
|
||||
DatabaseStore.findBy(Thread, {subject: 'Hello World'}).then (thread) ->
|
||||
# find a single thread by subject
|
||||
|
||||
DatabaseStore.findAll(Thread).where([Thread.attributes.tags.contains('inbox')]).then (threads) ->
|
||||
# find threads with the inbox tag
|
||||
|
||||
DatabaseStore.count(Thread).where([Thread.attributes.lastMessageTimestamp.greaterThan(120315123)]).then (results) ->
|
||||
# count threads where last message received since 120315123.
|
||||
|
||||
```
|
||||
|
||||
#####Retrieving Pages of Models
|
||||
|
||||
If you need to paginate through a view of data, you should use a `DatabaseView`. Database views can be configured with a sort order and a set of where clauses. After the view is configured, it maintains a cache of models in memory in a highly efficient manner and makes it easy to implement pagination. `DatabaseView` also performs deep inspection of it's cache when models are changed and can avoid costly SQL queries.
|
||||
|
||||
|
||||
#####Saving and Updating Models
|
||||
|
||||
The {DatabaseStore} exposes two methods for creating and updating models: `persistModel` and `persistModels`. When you call `persistModel`, queries are automatically executed to update the object in the cache and the {DatabaseStore} triggers, broadcasting an update to the rest of the application so that views dependent on these kind of models can refresh.
|
||||
|
||||
When possible, you should accumulate the objects you want to save and call `persistModels`. The {DatabaseStore} will generate batch insert statements, and a single notification will be broadcast throughout the application. Since saving objects can result in objects being re-fetched by many stores and components, you should be mindful of database insertions.
|
||||
|
||||
#####Saving Drafts
|
||||
|
||||
Drafts in Nylas Mail presented us with a unique challenge. The same draft may be edited rapidly by unrelated parts of the application, causing race scenarios. (For example, when the user is typing and attachments finish uploading at the same time.) This problem could be solved by object locking, but we chose to marshall draft changes through a central DraftStore that debounces database queries and adds other helpful features. See the `DraftStore` documentation for more information.
|
||||
|
||||
#####Removing Models
|
||||
|
||||
The {DatabaseStore} exposes a single method, `unpersistModel`, that allows you to purge an object from the cache. You cannot remove a model by ID alone - you must load it first.
|
||||
|
||||
####Advanced Model Attributes
|
||||
|
||||
#####Attribute.JoinedData
|
||||
|
||||
Joined Data attributes allow you to store certain attributes of an object in a separate table in the database. We use this attribute type for Message bodies. Storing message bodies, which can be very large, in a separate table allows us to make queries on message metadata extremely fast, and inflate Message objects without their bodies to build the thread list.
|
||||
|
||||
When building a {ModelQuery} on a model with a {JoinedDataAttribute}, 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
|
||||
|
||||
DatabaseStore.find(Message, '123').include(Message.attributes.body).then (message) ->
|
||||
// message.body is defined
|
||||
```
|
||||
|
||||
When you call `persistModel`, JoinedData attributes are automatically written to the secondary table.
|
||||
|
||||
JoinedData attributes cannot be `queryable`.
|
||||
|
||||
#####Attribute.Collection
|
||||
|
||||
Collection attributes provide basic support for one-to-many relationships. For example, {Thread}s in Nylas Mail have a collection of {Tag}s.
|
||||
|
||||
When Collection attributes are marked as `queryable`, the {DatabaseStore} automatically creates a join table and maintains it as you create, save, and delete models. When you call `persistModel`, entries are added to the join table associating the ID of the model with the IDs of models in the 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' ORDER BY `Thread`.`last_message_timestamp` DESC
|
||||
```
|
||||
|
||||
#### Listening for Changes
|
||||
|
||||
For many parts of the application, the Database is the source of truth. Funneling changes through the database ensures that they are available to the entire application. Basing your packages on the Database, and listening to it for changes, ensures that your views never fall out of sync.
|
||||
|
||||
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, @)
|
||||
```
|
||||
|
||||
When a model is persisted or unpersisted from the database, your listener method will fire. It's very important to inspect the change payload before making queries to refresh your data. The change payload is a simple object with the following keys:
|
||||
|
||||
```
|
||||
{
|
||||
"objectClass": // string: the name of the class that was changed. ie: "Thread"
|
||||
"objects": // array: the objects that were persisted or removed
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
##### But why can't I...?
|
||||
|
||||
Nylas Mail exposes a minimal Database API that exposes high-level methods for saving and retrieving objects. The API was designed with several goals in mind, which will help us create a healthy ecosystem of third-party packages:
|
||||
|
||||
- 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
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
Title: Debugging Nylas Mail
|
||||
Section: Guides
|
||||
Order: 6
|
||||
---
|
||||
|
||||
Nylas Mail is built on top of Electron, which runs the latest version of Chromium (at the time of writing, Chromium 41). You can access the standard Chrome Developer Tools using the Command-Option-I keyboard shortcut. When you open the developer tools, you'll also notice a bar appear at the bottom of the window. This bar allows you to inspect API requests sent from the app, streaming updates received from the Nylas API, and tasks that are queued for processing with the `TaskQueue`.
|
|
@ -1,42 +0,0 @@
|
|||
---
|
||||
Title: Extending the Composer
|
||||
Section: Guides
|
||||
Order: 5
|
||||
---
|
||||
The composer lies at the heart of Nylas Mail, and many improvements to the mail experience require deep integration with the composer. To enable these sort of plugins, the DraftStore exposes an extension API.
|
||||
|
||||
This API allows your package to:
|
||||
|
||||
- Display warning messages before a draft is sent. (ie: "Are you sure you want to send this without attaching a proposal?")
|
||||
|
||||
- Intercept keyboard and mouse events to the composer's text editor.
|
||||
|
||||
- Transform the draft and make additional changes before it is sent.
|
||||
|
||||
To create a Draft Store Extension, subclass {DraftStoreExtension} and override the methods your extension needs. See {DraftStoreExtension} for a complete list of the methods your extension can implement. In the sample packages repository, [templates]() is an example of a package which uses a DraftStoreExtension to enhance the composer experience.
|
||||
|
||||
####Example:
|
||||
|
||||
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 'nylas-exports'
|
||||
|
||||
class ProductsExtension extends DraftStoreExtension
|
||||
|
||||
@warningsForSending: (draft) ->
|
||||
words = ['iphone', 'ipad', 'apple', 'iwatch', 'macbook']
|
||||
body = draft.body.toLowercase()
|
||||
for word in words
|
||||
if body.indexOf(word) > 0
|
||||
return ["with the word '#{word}'?"]
|
||||
return []
|
||||
|
||||
@finalizeSessionBeforeSending: (session) ->
|
||||
draft = session.draft()
|
||||
if @warningsForSending(draft)
|
||||
bodyWithWarning = draft.body += "<br>This email \
|
||||
contains competitor's product names \
|
||||
or trademarks used in context."
|
||||
session.changes.add(body: bodyWithWarning)
|
||||
```
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
#####Do I have to use React?
|
||||
|
||||
You need to use React if you want to register UI components with the {ComponentRegistry}. However, you can create a React component and attach DOM to it's DOM node manually. Packages can also add a DOM node directly to `document.body`, though this is discouraged.
|
|
@ -1,67 +0,0 @@
|
|||
---
|
||||
Title: Welcome
|
||||
TitleHidden: true
|
||||
Section: Getting Started
|
||||
---
|
||||
|
||||
<img src="images/nylas.png" class="center-logo"/>
|
||||
<h2 style="text-align:center;">Nylas Package API</h2>
|
||||
<p style="text-align:center; margin:auto; margin-bottom:60px;">
|
||||
The Nylas Package API allows you to create powerful extensions to Nylas Mail. The client is built on top of Electron 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 class="no-border">
|
||||
<tr><td style="width:50%;">
|
||||
|
||||
<h4>Installing Nylas Mail</h4>
|
||||
<p>
|
||||
Nylas Mail is available for Mac, Windows, and Linux. Download the latest build for your platform below:
|
||||
</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%;">
|
||||
|
||||
<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 Nylas Package API to add functionality to the client. Learn more about packages and create your first package.
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="./PackageOverview.html">Package Overview</a></li>
|
||||
</ul>
|
||||
|
||||
</td></tr>
|
||||
<tr><td style="width:50%; vertical-align:top;">
|
||||
|
||||
<h4>Dive Deeper</h4>
|
||||
|
||||
<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;">
|
||||
|
||||
<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:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="./Debugging.html">Debugging in Nylas</a></li>
|
||||
</ul>
|
||||
|
||||
</td></tr>
|
||||
<tr colspan="2"><td>
|
||||
<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>
|
||||
</table>
|
|
@ -1,65 +0,0 @@
|
|||
---
|
||||
Title: Interface Concepts
|
||||
Section: Guides
|
||||
Order: 2
|
||||
---
|
||||
|
||||
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">
|
||||
|
||||
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.
|
||||
|
||||
The Nylas Workspace supports two display modes: `split` and `list`. Each Sheet describes it's appearance in each of the view modes it supports. For example, the `Threads` sheet describes a three column `split` view and a single column `list` view. Other sheets, like `Files` register for only one mode, and the user's mode preference is ignored.
|
||||
|
||||
For each mode, Sheets register a set of column names.
|
||||
|
||||
<img src="./images/columns.png">
|
||||
|
||||
```coffee
|
||||
@defineSheet 'Threads', {root: true},
|
||||
split: ['RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar']
|
||||
list: ['RootSidebar', 'ThreadList']
|
||||
```
|
||||
|
||||
Column names are important. Once you've registered a sheet, your package (and other packages) register React components that appear in each column.
|
||||
|
||||
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
|
||||
|
||||
|
||||
ComponentRegistry.register NotificationsStickyBar,
|
||||
location: WorkspaceStore.Sheet.Threads.Header
|
||||
|
||||
```
|
||||
|
||||
Each column is laid out as a CSS Flexbox, making them extremely flexible. For more about layout using Flexbox, see Working with Flexbox.
|
||||
|
||||
|
||||
###Toolbars
|
||||
|
||||
Toolbars in Nylas Mail are also powered by the {ComponentRegistry}. 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">
|
||||
|
||||
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">
|
||||
|
||||
To add items to a toolbar, you inject them via the {ComponentRegistry}. There are several ways of describing the location of a toolbar component which are useful in different scenarios:
|
||||
|
||||
- `<Location>.Toolbar`: This component will always appear in the toolbar above the column named `<Location>`.
|
||||
|
||||
(Example: Compose button which appears above the Left Sidebar column, regardless of what else is there.)
|
||||
|
||||
- `<ComponentName>.Toolbar`: This component will appear in the toolbar above `<ComponentName>`.
|
||||
|
||||
(Example: Archive button that should always be coupled with the MessageList component, placed anywhere a MessageList component is placed.)
|
||||
|
||||
- `Global.Toolbar.Left`: This component will always be added to the leftmost column of the toolbar.
|
||||
|
||||
(Example: Window Controls)
|
|
@ -1,102 +0,0 @@
|
|||
---
|
||||
Title: Building a Package
|
||||
Section: Getting Started
|
||||
---
|
||||
|
||||
Packages lie at the heart of Nylas Mail. Each part of the core experience is a separate package that uses the Nylas Package API to add functionality to the client. Want to make a read-only mail client? Remove the core `Composer` package and you'll see reply buttons and composer functionality disappear.
|
||||
|
||||
Let's explore the files in a simple package that adds a Translate option to the Composer. When you tap the Translate button, we'll display a popup menu with a list of languages. When you pick a language, we'll make a web request and convert your reply into the desired language.
|
||||
|
||||
#####Package Structure
|
||||
|
||||
Each package is defined by a `package.json` file that includes it's name, version and dependencies. Our `translate` package uses [React](https://facebook.github.io/react/) and the Node [request](https://github.com/request/request) library.
|
||||
|
||||
```
|
||||
{
|
||||
"name": "translate",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "An example package for Nylas Mail",
|
||||
"license": "Proprietary",
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^0.12.2",
|
||||
"request": "^2.53"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Our package also contains source files, a spec file with complete tests for the behavior the package adds, and a stylesheet for CSS.
|
||||
|
||||
```
|
||||
- package.json
|
||||
- lib/
|
||||
- main.cjsx
|
||||
- spec/
|
||||
- main-spec.coffee
|
||||
- stylesheets/
|
||||
- translate.less
|
||||
```
|
||||
|
||||
`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 =
|
||||
|
||||
##
|
||||
# Activate is called when the package is loaded. If your package previously
|
||||
# saved state using `serialize` it is provided.
|
||||
#
|
||||
activate: (@state) ->
|
||||
ComponentRegistry.register TranslateButton,
|
||||
role: 'Composer:ActionButton'
|
||||
|
||||
##
|
||||
# Serialize is called when your package is about to be unmounted.
|
||||
# You can return a state object that will be passed back to your package
|
||||
# when it is re-activated.
|
||||
#
|
||||
serialize: ->
|
||||
{}
|
||||
|
||||
##
|
||||
# This optional method is called when the window is shutting down,
|
||||
# or when your package is being updated or disabled. If your package is
|
||||
# watching any files, holding external resources, providing commands or
|
||||
# subscribing to events, release them here.
|
||||
deactivate: ->
|
||||
ComponentRegistry.unregister(TranslateButton)
|
||||
```
|
||||
|
||||
|
||||
> Nylas Mail uses CJSX, a Coffeescript version of JSX, which makes it easy to express Virtual DOM in React `render` methods! You may want to add the [Babel](https://github.com/babel/babel-sublime) plugin to Sublime Text, or the [CJSX Language](https://atom.io/packages/language-cjsx) for syntax highlighting.
|
||||
|
||||
|
||||
#####Package Stylesheets
|
||||
|
||||
Style sheets for your package should be placed in the _styles_ directory.
|
||||
Any style sheets in this directory will be loaded and attached to the DOM when
|
||||
your package is activated. Style sheets can be written as CSS or [Less], but
|
||||
Less is recommended.
|
||||
|
||||
Ideally, you won't need much in the way of styling. We've provided a standard
|
||||
set of components which define both the colors and UI elements for any package
|
||||
that fits into Nylas Mail seamlessly.
|
||||
|
||||
If you _do_ need special styling, try to keep only structural styles in the
|
||||
package stylesheets. If you _must_ specify colors and sizing, these should be
|
||||
taken from the active theme's [ui-variables.less][ui-variables]. For more
|
||||
information, see the [theme variables docs][theme-variables]. If you follow this
|
||||
guideline, your package will look good out of the box with any theme!
|
||||
|
||||
An optional `stylesheets` array in your _package.json_ can list the style sheets
|
||||
by name to specify a loading order; otherwise, all style sheets are loaded.
|
||||
|
||||
###Installing a Package
|
||||
|
||||
Nylas Mail ships with many packages already bundled with the application. When the application launches, it looks for additional packages in `~/.nylas/packages`. Each package you create belongs in it's own directory inside this folder.
|
||||
|
||||
In the future, it will be possible to install packages directly from within the client.
|
113
docs/React.md
|
@ -1,113 +0,0 @@
|
|||
---
|
||||
Title: Interface Components
|
||||
Section: Guides
|
||||
Order: 3
|
||||
---
|
||||
|
||||
Nylas Mail uses [React](https://facebook.github.io/react/) to create a fast, responsive UI. Packages that want to extend the Nylas Mail interface should use React. Using React's [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) is optional, but both [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) and [CJSX](https://github.com/jsdf/coffee-react) (Coffeescript) are available.
|
||||
|
||||
For a quick introduction to React, take a look at Facebook's [Getting Started with React](https://facebook.github.io/react/docs/getting-started.html).
|
||||
|
||||
#### React Components
|
||||
|
||||
Nylas Mail provides a set of core React components you can use in your packages. Many of the standard components listen for key events, include considerations for different platforms, and have extensive CSS. Wrapping standard components makes it easy to build rich interfaces that are consistent with the rest of the Nylas Mail platform.
|
||||
|
||||
To use a standard component, require it from `nylas-component-kit` and use it in your component's `render` method.
|
||||
|
||||
> Keep in mind that React's Component model is based on composition rather than inheritance. On other platforms, you might subclass {Popover} to create your own custom Popover. In React, you should wrap the standard Popover component in your own component, which provides the Popover with `props` and children to customize it's behavior.
|
||||
|
||||
|
||||
Here's a quick look at standard components you can require from `nylas-component-kit`:
|
||||
|
||||
- **{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.
|
||||
|
||||
- **{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.
|
||||
|
||||
- **{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.
|
||||
|
||||
- **{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.
|
||||
|
||||
- **{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.
|
||||
|
||||
- **{EventedIFrame}**: Replacement for the standard `<iframe>` tag which handles events directed at the iFrame to ensure a consistent user experience.
|
||||
|
||||
### React Component Injection
|
||||
|
||||
#####Registering Components
|
||||
|
||||
Once you've created components, the next step is to register them with the {ComponentRegistry}. The Component Registry enables the React component injection that makes Nylas Mail so extensible. You can request that your components appear in a specific `Location` defined by the {WorkspaceStore}, or register your component for a `Role` that another package has declared.
|
||||
|
||||
> The Component Registry allows you to insert your custom component without hacking up the DOM. Register for a `Location` or `Role` and your Component will be rendered into that part of the interface.
|
||||
|
||||
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">
|
||||
|
||||
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.
|
||||
|
||||
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'
|
||||
```
|
||||
|
||||
*Tip: Remember to unregister components in the `deactivate` method of your package.*
|
||||
|
||||
|
||||
### Using Registered Components
|
||||
|
||||
It's easy to build packages that use the Component Registry to display components vended by other parts of the application. You can query the Component Registry and display the components it returns. The Component Registry is a Reflux-compatible Store, so you can listen to it and update your state as the registry changes.
|
||||
|
||||
There are also several convenience components that make it easy to dynamically inject components into your Virtual DOM. These are the preferred way of using injected components.
|
||||
|
||||
- {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}/>
|
||||
```
|
||||
|
||||
- {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"}
|
||||
exposedProps={thread:@props.thread, message: @props.message}>
|
||||
```
|
||||
|
||||
### Unsafe Components
|
||||
|
||||
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">
|
||||
|
||||
In the future, Nylas Mail may automatically disable packages when their React components throw exceptions.
|
|
@ -1,355 +0,0 @@
|
|||
|
||||
@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: 16px;
|
||||
line-height: 1.5em;
|
||||
color: #737373;
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
}
|
||||
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 {
|
||||
font-size: 40px;
|
||||
margin-top:30px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 30px;
|
||||
margin-top: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;
|
||||
}
|
||||
h1:first-child,
|
||||
h2:first-child,
|
||||
h3:first-child,
|
||||
h4:first-child,
|
||||
h5:first-child,
|
||||
h6:first-child {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 13px 13px 21px 15px;
|
||||
margin-bottom: 18px;
|
||||
font-family:georgia,serif;
|
||||
font-style: italic;
|
||||
border-left: 5px solid #C7DDDB;
|
||||
background-color: #EAF7F6;
|
||||
}
|
||||
blockquote p {
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
blockquote code {
|
||||
background-color:white;
|
||||
}
|
||||
img {
|
||||
max-width:100%;
|
||||
}
|
||||
code, pre {
|
||||
font-family: Monaco, Andale Mono, Courier New, monospace;
|
||||
}
|
||||
code {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
background-color: #EAF7F6;
|
||||
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: 1020px) {
|
||||
.container {
|
||||
width: 960px;
|
||||
margin:auto;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
body,code,pre code,h1,h2,h3,h4,h5,h6 {
|
||||
color: black;
|
||||
}
|
||||
table, pre {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
#header {
|
||||
padding-top:40px;
|
||||
padding-bottom:20px;
|
||||
background-color:#eee;
|
||||
margin-bottom:40px;
|
||||
}
|
||||
#footer {
|
||||
margin-top:50px;
|
||||
padding-top:30px;
|
||||
padding-bottom:30px;
|
||||
background-color:#eee;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
#header .logo,
|
||||
#footer .logo {
|
||||
float:left;
|
||||
width:50px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
#header .title,
|
||||
#footer .title {
|
||||
line-height: 22px;
|
||||
font-size:20px;
|
||||
font-weight:600;
|
||||
padding-top:6px;
|
||||
}
|
||||
#header .small,
|
||||
#footer .small {
|
||||
font-weight:300;
|
||||
font-size:0.9em;
|
||||
}
|
||||
|
||||
#main {
|
||||
margin-left:290px;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
float:left;
|
||||
position:static;
|
||||
width:250px;
|
||||
}
|
||||
|
||||
#sidebar .heading {
|
||||
color:#404040;
|
||||
line-height:28px;
|
||||
font-size:16px;
|
||||
font-weight:bold;
|
||||
}
|
||||
#sidebar ul .heading {
|
||||
line-height:22px;
|
||||
font-size:14px;
|
||||
padding-top:6px;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
margin-top:0;
|
||||
padding-left:15px;
|
||||
}
|
||||
#sidebar ul li {
|
||||
list-style-type:none;
|
||||
}
|
||||
#sidebar ul li a {
|
||||
color:#404040;
|
||||
font-size:14px;
|
||||
line-height:1.8em;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-weight:200;
|
||||
font-size:40px;
|
||||
color:#404040;
|
||||
padding-bottom:40px;
|
||||
padding-top:30px;
|
||||
}
|
||||
.page-title .extends {
|
||||
font-style: italic;
|
||||
font-size:0.7em;
|
||||
color:#ccc;
|
||||
}
|
||||
|
||||
/* It's not possible to make H3+ in Markdown parsed out of source code, because
|
||||
Coffeescript uses the ### as the comment block character. Allow the use of #
|
||||
and ##, but convert these h1 and h2 elements to smaller headings. */
|
||||
.markdown-from-sourecode h1,
|
||||
.markdown-from-sourecode h2 {
|
||||
font-size: 24px;
|
||||
margin-top:30px;
|
||||
}
|
||||
|
||||
.article img {
|
||||
margin-top:18px;
|
||||
margin-bottom:18px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.function-name .arg {
|
||||
color: #999;
|
||||
}
|
||||
.function-name .arg:after {
|
||||
content: ", ";
|
||||
color: #999;
|
||||
}
|
||||
.function-name .arg:last-child:after {
|
||||
content: "";
|
||||
}
|
||||
.function-description h1,
|
||||
.function-description h2,
|
||||
.function-description h3,
|
||||
.function-description h4,
|
||||
.function-description h5 {
|
||||
font-size: 16px;
|
||||
line-height:1.5em;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
table th {
|
||||
background-color: #EAF7F6;
|
||||
text-align:left;
|
||||
}
|
||||
table td {
|
||||
position:relative;
|
||||
}
|
||||
table.arguments {
|
||||
width:100%;
|
||||
}
|
||||
table.arguments p {
|
||||
margin-bottom:0;
|
||||
}
|
||||
table.arguments .optional {
|
||||
background-color: #EAF7F6;
|
||||
position:absolute;
|
||||
top:0px;
|
||||
right:0px;
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height:16px;
|
||||
}
|
||||
|
||||
table.no-border td {
|
||||
border:0;
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/* 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;
|
||||
}
|
Before Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 168 KiB |
Before Width: | Height: | Size: 608 B |
Before Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 545 KiB |