update(examples): Add github sidebar and translate examples

This commit is contained in:
Ben Gotow 2015-09-21 11:36:54 -07:00
parent abf91a16e6
commit a0c225219a
37 changed files with 1802 additions and 0 deletions

1
examples/N1-Composer-Translate/.gitignore vendored Executable file
View file

@ -0,0 +1 @@
node_modules

View file

@ -0,0 +1,12 @@
## Translate
A sample package for Nylas Mail that is translates draft text to other languages using the Yandex Translation API.
#### To build documentation (the manual way):
```
cjsx-transform lib/main.cjsx > docs/main.coffee
docco docs/main.coffee
rm docs/main.coffee
```

View file

@ -0,0 +1,518 @@
/*--------------------- Typography ----------------------------*/
@font-face {
font-family: 'aller-light';
src: url('public/fonts/aller-light.eot');
src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'),
url('public/fonts/aller-light.woff') format('woff'),
url('public/fonts/aller-light.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'aller-bold';
src: url('public/fonts/aller-bold.eot');
src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'),
url('public/fonts/aller-bold.woff') format('woff'),
url('public/fonts/aller-bold.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'roboto-black';
src: url('public/fonts/roboto-black.eot');
src: url('public/fonts/roboto-black.eot?#iefix') format('embedded-opentype'),
url('public/fonts/roboto-black.woff') format('woff'),
url('public/fonts/roboto-black.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
/*--------------------- Layout ----------------------------*/
html { height: 100%; }
body {
font-family: "aller-light";
font-size: 14px;
line-height: 18px;
color: #30404f;
margin: 0; padding: 0;
height:100%;
}
#container { min-height: 100%; }
a {
color: #000;
}
b, strong {
font-weight: normal;
font-family: "aller-bold";
}
p {
margin: 15px 0 0px;
}
.annotation ul, .annotation ol {
margin: 25px 0;
}
.annotation ul li, .annotation ol li {
font-size: 14px;
line-height: 18px;
margin: 10px 0;
}
h1, h2, h3, h4, h5, h6 {
color: #112233;
line-height: 1em;
font-weight: normal;
font-family: "roboto-black";
text-transform: uppercase;
margin: 30px 0 15px 0;
}
h1 {
margin-top: 40px;
}
h2 {
font-size: 1.26em;
}
hr {
border: 0;
background: 1px #ddd;
height: 1px;
margin: 20px 0;
}
pre, tt, code {
font-size: 12px; line-height: 16px;
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
margin: 0; padding: 0;
}
.annotation pre {
display: block;
margin: 0;
padding: 7px 10px;
background: #fcfcfc;
-moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
-webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
overflow-x: auto;
}
.annotation pre code {
border: 0;
padding: 0;
background: transparent;
}
blockquote {
border-left: 5px solid #ccc;
margin: 0;
padding: 1px 0 1px 1em;
}
.sections blockquote p {
font-family: Menlo, Consolas, Monaco, monospace;
font-size: 12px; line-height: 16px;
color: #999;
margin: 10px 0 0;
white-space: pre-wrap;
}
ul.sections {
list-style: none;
padding:0 0 5px 0;;
margin:0;
}
/*
Force border-box so that % widths fit the parent
container without overlap because of margin/padding.
More Info : http://www.quirksmode.org/css/box.html
*/
ul.sections > li > div {
-moz-box-sizing: border-box; /* firefox */
-ms-box-sizing: border-box; /* ie */
-webkit-box-sizing: border-box; /* webkit */
-khtml-box-sizing: border-box; /* konqueror */
box-sizing: border-box; /* css3 */
}
/*---------------------- Jump Page -----------------------------*/
#jump_to, #jump_page {
margin: 0;
background: white;
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
font: 16px Arial;
cursor: pointer;
text-align: right;
list-style: none;
}
#jump_to a {
text-decoration: none;
}
#jump_to a.large {
display: none;
}
#jump_to a.small {
font-size: 22px;
font-weight: bold;
color: #676767;
}
#jump_to, #jump_wrapper {
position: fixed;
right: 0; top: 0;
padding: 10px 15px;
margin:0;
}
#jump_wrapper {
display: none;
padding:0;
}
#jump_to:hover #jump_wrapper {
display: block;
}
#jump_page_wrapper{
position: fixed;
right: 0;
top: 0;
bottom: 0;
}
#jump_page {
padding: 5px 0 3px;
margin: 0 0 25px 25px;
max-height: 100%;
overflow: auto;
}
#jump_page .source {
display: block;
padding: 15px;
text-decoration: none;
border-top: 1px solid #eee;
}
#jump_page .source:hover {
background: #f5f5ff;
}
#jump_page .source:first-child {
}
/*---------------------- Low resolutions (> 320px) ---------------------*/
@media only screen and (min-width: 320px) {
.pilwrap { display: none; }
ul.sections > li > div {
display: block;
padding:5px 10px 0 10px;
}
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
padding-left: 30px;
}
ul.sections > li > div.content {
overflow-x:auto;
-webkit-box-shadow: inset 0 0 5px #e5e5ee;
box-shadow: inset 0 0 5px #e5e5ee;
border: 1px solid #dedede;
margin:5px 10px 5px 10px;
padding-bottom: 5px;
}
ul.sections > li > div.annotation pre {
margin: 7px 0 7px;
padding-left: 15px;
}
ul.sections > li > div.annotation p tt, .annotation code {
background: #f8f8ff;
border: 1px solid #dedede;
font-size: 12px;
padding: 0 0.2em;
}
}
/*---------------------- (> 481px) ---------------------*/
@media only screen and (min-width: 481px) {
#container {
position: relative;
}
body {
background-color: #F5F5FF;
font-size: 15px;
line-height: 21px;
}
pre, tt, code {
line-height: 18px;
}
p, ul, ol {
margin: 0 0 15px;
}
#jump_to {
padding: 5px 10px;
}
#jump_wrapper {
padding: 0;
}
#jump_to, #jump_page {
font: 10px Arial;
text-transform: uppercase;
}
#jump_page .source {
padding: 5px 10px;
}
#jump_to a.large {
display: inline-block;
}
#jump_to a.small {
display: none;
}
#background {
position: absolute;
top: 0; bottom: 0;
width: 350px;
background: #fff;
border-right: 1px solid #e5e5ee;
z-index: -1;
}
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
padding-left: 40px;
}
ul.sections > li {
white-space: nowrap;
}
ul.sections > li > div {
display: inline-block;
}
ul.sections > li > div.annotation {
max-width: 350px;
min-width: 350px;
min-height: 5px;
padding: 13px;
overflow-x: hidden;
white-space: normal;
vertical-align: top;
text-align: left;
}
ul.sections > li > div.annotation pre {
margin: 15px 0 15px;
padding-left: 15px;
}
ul.sections > li > div.content {
padding: 13px;
vertical-align: top;
border: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.pilwrap {
position: relative;
display: inline;
}
.pilcrow {
font: 12px Arial;
text-decoration: none;
color: #454545;
position: absolute;
top: 3px; left: -20px;
padding: 1px 2px;
opacity: 0;
-webkit-transition: opacity 0.2s linear;
}
.for-h1 .pilcrow {
top: 47px;
}
.for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow {
top: 35px;
}
ul.sections > li > div.annotation:hover .pilcrow {
opacity: 1;
}
}
/*---------------------- (> 1025px) ---------------------*/
@media only screen and (min-width: 1025px) {
body {
font-size: 16px;
line-height: 24px;
}
#background {
width: 525px;
}
ul.sections > li > div.annotation {
max-width: 525px;
min-width: 525px;
padding: 10px 25px 1px 50px;
}
ul.sections > li > div.content {
padding: 9px 15px 16px 25px;
}
}
/*---------------------- Syntax Highlighting -----------------------------*/
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
pre code {
display: block; padding: 0.5em;
color: #000;
background: #f8f8ff
}
pre .hljs-comment,
pre .hljs-template_comment,
pre .hljs-diff .hljs-header,
pre .hljs-javadoc {
color: #408080;
font-style: italic
}
pre .hljs-keyword,
pre .hljs-assignment,
pre .hljs-literal,
pre .hljs-css .hljs-rule .hljs-keyword,
pre .hljs-winutils,
pre .hljs-javascript .hljs-title,
pre .hljs-lisp .hljs-title,
pre .hljs-subst {
color: #954121;
/*font-weight: bold*/
}
pre .hljs-number,
pre .hljs-hexcolor {
color: #40a070
}
pre .hljs-string,
pre .hljs-tag .hljs-value,
pre .hljs-phpdoc,
pre .hljs-tex .hljs-formula {
color: #219161;
}
pre .hljs-title,
pre .hljs-id {
color: #19469D;
}
pre .hljs-params {
color: #00F;
}
pre .hljs-javascript .hljs-title,
pre .hljs-lisp .hljs-title,
pre .hljs-subst {
font-weight: normal
}
pre .hljs-class .hljs-title,
pre .hljs-haskell .hljs-label,
pre .hljs-tex .hljs-command {
color: #458;
font-weight: bold
}
pre .hljs-tag,
pre .hljs-tag .hljs-title,
pre .hljs-rules .hljs-property,
pre .hljs-django .hljs-tag .hljs-keyword {
color: #000080;
font-weight: normal
}
pre .hljs-attribute,
pre .hljs-variable,
pre .hljs-instancevar,
pre .hljs-lisp .hljs-body {
color: #008080
}
pre .hljs-regexp {
color: #B68
}
pre .hljs-class {
color: #458;
font-weight: bold
}
pre .hljs-symbol,
pre .hljs-ruby .hljs-symbol .hljs-string,
pre .hljs-ruby .hljs-symbol .hljs-keyword,
pre .hljs-ruby .hljs-symbol .hljs-keymethods,
pre .hljs-lisp .hljs-keyword,
pre .hljs-tex .hljs-special,
pre .hljs-input_number {
color: #990073
}
pre .hljs-builtin,
pre .hljs-constructor,
pre .hljs-built_in,
pre .hljs-lisp .hljs-title {
color: #0086b3
}
pre .hljs-preprocessor,
pre .hljs-pi,
pre .hljs-doctype,
pre .hljs-shebang,
pre .hljs-cdata {
color: #999;
font-weight: bold
}
pre .hljs-deletion {
background: #fdd
}
pre .hljs-addition {
background: #dfd
}
pre .hljs-diff .hljs-change {
background: #0086b3
}
pre .hljs-chunk {
color: #aaa
}
pre .hljs-tex .hljs-formula {
opacity: 0.5;
}

View file

@ -0,0 +1,145 @@
# # Translation Plugin
# Last Revised: April 23, 2015 by Ben Gotow
#
# TranslateButton is a simple React component that allows you to select
# a language from a popup menu and translates draft text into that language.
#
request = require 'request'
{Utils,
React,
ComponentRegistry,
DraftStore} = require 'nylas-exports'
{Menu,
RetinaImg,
Popover} = require 'nylas-component-kit'
YandexTranslationURL = 'https://translate.yandex.net/api/v1.5/tr.json/translate'
YandexTranslationKey = 'trnsl.1.1.20150415T044616Z.24814c314120d022.0a339e2bc2d2337461a98d5ec9863fc46e42735e'
YandexLanguages =
'English': 'en'
'Spanish': 'es'
'Russian': 'ru'
'Chinese': 'zh'
'French': 'fr'
'German': 'de'
'Italian': 'it'
'Japanese': 'ja'
'Portuguese': 'pt'
'Korean': 'ko'
class TranslateButton extends React.Component
# Adding a `displayName` makes debugging React easier
@displayName: 'TranslateButton'
# Since our button is being injected into the Composer Footer,
# we receive the local id of the current draft as a `prop` (a read-only
# property). Since our code depends on this prop, we mark it as a requirement.
#
@propTypes:
draftLocalId: React.PropTypes.string.isRequired
# The `render` method returns a React Virtual DOM element. This code looks
# like HTML, but don't be fooled. The CJSX preprocessor converts
#
# `<a href="http://facebook.github.io/react/">Hello!</a>`
#
# into Javascript objects which describe the HTML you want:
#
# `React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello!')`
#
# We're rendering a `Popover` with a `Menu` inside. These components are part
# of Edgehill's standard `nylas-component-kit` library, and make it easy to build
# interfaces that match the rest of Edgehill's UI.
#
render: =>
React.createElement(Popover, {"ref": "popover", \
"className": "translate-language-picker pull-right", \
"buttonComponent": (@_renderButton())},
React.createElement(Menu, {"items": ( Object.keys(YandexLanguages) ), \
"itemKey": ( (item) -> item ), \
"itemContent": ( (item) -> item ), \
"onSelect": (@_onTranslate)
})
)
# Helper method to render the button that will activate the popover. Using the
# `RetinaImg` component makes it easy to display an image from our package.
# `RetinaImg` will automatically chose the best image format for our display.
#
_renderButton: =>
React.createElement("button", {"className": "btn btn-toolbar"}, """
Translate
""", React.createElement(RetinaImg, {"name": "toolbar-chevron.png"})
)
_onTranslate: (lang) =>
@refs.popover.close()
# Obtain the session for the current draft. The draft session provides us
# the draft object and also manages saving changes to the local cache and
# Nilas API as multiple parts of the application touch the draft.
#
session = DraftStore.sessionForLocalId(@props.draftLocalId)
session.prepare().then =>
body = session.draft().body
bodyQuoteStart = Utils.quotedTextIndex(body)
# Identify the text we want to translate. We need to make sure we
# don't translate quoted text.
if bodyQuoteStart > 0
text = body.substr(0, bodyQuoteStart)
else
text = body
query =
key: YandexTranslationKey
lang: YandexLanguages[lang]
text: text
format: 'html'
# Use Node's `request` library to perform the translation using the Yandex API.
request {url: YandexTranslationURL, qs: query}, (error, resp, data) =>
return @_onError(error) unless resp.statusCode is 200
json = JSON.parse(data)
# The new text of the draft is our translated response, plus any quoted text
# that we didn't process.
translated = json.text.join('')
translated += body.substr(bodyQuoteStart) if bodyQuoteStart > 0
# To update the draft, we add the new body to it's session. The session object
# automatically marshalls changes to the database and ensures that others accessing
# the same draft are notified of changes.
session.changes.add(body: translated)
session.changes.commit()
_onError: (error) =>
@refs.popover.close()
dialog = require('remote').require('dialog')
dialog.showErrorBox('Geolocation Failed', error.toString())
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)

View file

@ -0,0 +1,312 @@
<!DOCTYPE html>
<html>
<head>
<title>Translation Plugin</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
<link rel="stylesheet" media="all" href="docco.css" />
</head>
<body>
<div id="container">
<div id="background"></div>
<ul class="sections">
<li id="section-1">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-1">&#182;</a>
</div>
<h1 id="translation-plugin">Translation Plugin</h1>
<p>Last Revised: April 23, 2015 by Ben Gotow</p>
<p>TranslateButton is a simple React component that allows you to select
a language from a popup menu and translates draft text into that language.</p>
</div>
<div class="content"><div class='highlight'><pre>
request = <span class="hljs-built_in">require</span> <span class="hljs-string">'request'</span>
{Utils,
React,
ComponentRegistry,
DraftStore} = <span class="hljs-built_in">require</span> <span class="hljs-string">'nylas-exports'</span>
{Menu,
RetinaImg,
Popover} = <span class="hljs-built_in">require</span> <span class="hljs-string">'nylas-component-kit'</span>
YandexTranslationURL = <span class="hljs-string">'https://translate.yandex.net/api/v1.5/tr.json/translate'</span>
YandexTranslationKey = <span class="hljs-string">'trnsl.1.1.20150415T044616Z.24814c314120d022.0a339e2bc2d2337461a98d5ec9863fc46e42735e'</span>
YandexLanguages =
<span class="hljs-string">'English'</span>: <span class="hljs-string">'en'</span>
<span class="hljs-string">'Spanish'</span>: <span class="hljs-string">'es'</span>
<span class="hljs-string">'Russian'</span>: <span class="hljs-string">'ru'</span>
<span class="hljs-string">'Chinese'</span>: <span class="hljs-string">'zh'</span>
<span class="hljs-string">'French'</span>: <span class="hljs-string">'fr'</span>
<span class="hljs-string">'German'</span>: <span class="hljs-string">'de'</span>
<span class="hljs-string">'Italian'</span>: <span class="hljs-string">'it'</span>
<span class="hljs-string">'Japanese'</span>: <span class="hljs-string">'ja'</span>
<span class="hljs-string">'Portuguese'</span>: <span class="hljs-string">'pt'</span>
<span class="hljs-string">'Korean'</span>: <span class="hljs-string">'ko'</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TranslateButton</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span></span></pre></div></div>
</li>
<li id="section-2">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-2">&#182;</a>
</div>
<p>Adding a <code>displayName</code> makes debugging React easier</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-property">@displayName</span>: <span class="hljs-string">'TranslateButton'</span></pre></div></div>
</li>
<li id="section-3">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-3">&#182;</a>
</div>
<p>Since our button is being injected into the Composer Footer,
we receive the local id of the current draft as a <code>prop</code> (a read-only
property). Since our code depends on this prop, we mark it as a requirement.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-property">@propTypes</span>:
<span class="hljs-attribute">draftLocalId</span>: React.PropTypes.string.isRequired</pre></div></div>
</li>
<li id="section-4">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-4">&#182;</a>
</div>
<p>The <code>render</code> method returns a React Virtual DOM element. This code looks
like HTML, but dont be fooled. The CJSX preprocessor converts</p>
<p><code>&lt;a href=&quot;http://facebook.github.io/react/&quot;&gt;Hello!&lt;/a&gt;</code></p>
<p>into Javascript objects which describe the HTML you want:</p>
<p><code>React.createElement(&#39;a&#39;, {href: &#39;http://facebook.github.io/react/&#39;}, &#39;Hello!&#39;)</code></p>
<p>Were rendering a <code>Popover</code> with a <code>Menu</code> inside. These components are part
of Edgehills standard <code>nylas-component-kit</code> library, and make it easy to build
interfaces that match the rest of Edgehills UI.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-attribute">render</span>: <span class="hljs-function">=&gt;</span>
React.createElement(Popover, {<span class="hljs-string">"ref"</span>: <span class="hljs-string">"popover"</span>, \
<span class="hljs-string">"className"</span>: <span class="hljs-string">"translate-language-picker pull-right"</span>, \
<span class="hljs-string">"buttonComponent"</span>: (<span class="hljs-property">@_renderButton</span>())},
React.createElement(Menu, {<span class="hljs-string">"items"</span>: ( Object.keys(YandexLanguages) ), \
<span class="hljs-string">"itemKey"</span>: <span class="hljs-function"><span class="hljs-params">( (item) -&gt; item )</span>, \
"itemContent": <span class="hljs-params">( (item) -&gt; item )</span>, \
"onSelect": <span class="hljs-params">(<span class="hljs-property">@_onTranslate</span>)</span>
})
)
</span></pre></div></div>
</li>
<li id="section-5">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-5">&#182;</a>
</div>
<p>Helper method to render the button that will activate the popover. Using the
<code>RetinaImg</code> component makes it easy to display an image from our package.
<code>RetinaImg</code> will automatically chose the best image format for our display.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-attribute">_renderButton</span>: <span class="hljs-function">=&gt;</span>
React.createElement(<span class="hljs-string">"button"</span>, {<span class="hljs-string">"className"</span>: <span class="hljs-string">"btn btn-toolbar"</span>}, <span class="hljs-string">"""
Translate
"""</span>, React.createElement(RetinaImg, {<span class="hljs-string">"name"</span>: <span class="hljs-string">"toolbar-chevron.png"</span>})
)
<span class="hljs-attribute">_onTranslate</span>: <span class="hljs-function"><span class="hljs-params">(lang)</span> =&gt;</span>
<span class="hljs-property">@refs</span>.popover.close()</pre></div></div>
</li>
<li id="section-6">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-6">&#182;</a>
</div>
<p>Obtain the session for the current draft. The draft session provides us
the draft object and also manages saving changes to the local cache and
Nilas API as multiple parts of the application touch the draft.</p>
</div>
<div class="content"><div class='highlight'><pre> session = DraftStore.sessionForLocalId(<span class="hljs-property">@props</span>.draftLocalId)
session.prepare().<span class="hljs-keyword">then</span> =&gt;
body = session.draft().body
bodyQuoteStart = Utils.quotedTextIndex(body)</pre></div></div>
</li>
<li id="section-7">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-7">&#182;</a>
</div>
<p>Identify the text we want to translate. We need to make sure we
dont translate quoted text.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> bodyQuoteStart &gt; <span class="hljs-number">0</span>
text = body.substr(<span class="hljs-number">0</span>, bodyQuoteStart)
<span class="hljs-keyword">else</span>
text = body
query =
<span class="hljs-attribute">key</span>: YandexTranslationKey
<span class="hljs-attribute">lang</span>: YandexLanguages[lang]
<span class="hljs-attribute">text</span>: text
<span class="hljs-attribute">format</span>: <span class="hljs-string">'html'</span></pre></div></div>
</li>
<li id="section-8">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-8">&#182;</a>
</div>
<p>Use Nodes <code>request</code> library to perform the translation using the Yandex API.</p>
</div>
<div class="content"><div class='highlight'><pre> request {<span class="hljs-attribute">url</span>: YandexTranslationURL, <span class="hljs-attribute">qs</span>: query}, <span class="hljs-function"><span class="hljs-params">(error, resp, data)</span> =&gt;</span>
<span class="hljs-keyword">return</span> <span class="hljs-property">@_onError</span>(error) <span class="hljs-keyword">unless</span> resp.statusCode <span class="hljs-keyword">is</span> <span class="hljs-number">200</span>
json = JSON.parse(data)</pre></div></div>
</li>
<li id="section-9">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-9">&#182;</a>
</div>
<p>The new text of the draft is our translated response, plus any quoted text
that we didnt process.</p>
</div>
<div class="content"><div class='highlight'><pre> translated = json.text.join(<span class="hljs-string">''</span>)
translated += body.substr(bodyQuoteStart) <span class="hljs-keyword">if</span> bodyQuoteStart &gt; <span class="hljs-number">0</span></pre></div></div>
</li>
<li id="section-10">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-10">&#182;</a>
</div>
<p>To update the draft, we add the new body to its session. The session object
automatically marshalls changes to the database and ensures that others accessing
the same draft are notified of changes.</p>
</div>
<div class="content"><div class='highlight'><pre> session.changes.add(<span class="hljs-attribute">body</span>: translated)
session.changes.commit()
<span class="hljs-attribute">_onError</span>: <span class="hljs-function"><span class="hljs-params">(error)</span> =&gt;</span>
<span class="hljs-property">@refs</span>.popover.close()
dialog = <span class="hljs-built_in">require</span>(<span class="hljs-string">'remote'</span>).<span class="hljs-built_in">require</span>(<span class="hljs-string">'dialog'</span>)
dialog.showErrorBox(<span class="hljs-string">'Geolocation Failed'</span>, error.toString())
<span class="hljs-built_in">module</span>.exports =</pre></div></div>
</li>
<li id="section-11">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-11">&#182;</a>
</div>
<p>Activate is called when the package is loaded. If your package previously
saved state using <code>serialize</code> it is provided.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-attribute">activate</span>: <span class="hljs-function"><span class="hljs-params">(<span class="hljs-property">@state</span>)</span> -&gt;</span>
ComponentRegistry.register TranslateButton,
<span class="hljs-attribute">role</span>: <span class="hljs-string">'Composer:ActionButton'</span></pre></div></div>
</li>
<li id="section-12">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-12">&#182;</a>
</div>
<p>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.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-attribute">serialize</span>: <span class="hljs-function">-&gt;</span></pre></div></div>
</li>
<li id="section-13">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-13">&#182;</a>
</div>
<p>This <strong>optional</strong> 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.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-attribute">deactivate</span>: <span class="hljs-function">-&gt;</span>
ComponentRegistry.unregister(TranslateButton)</pre></div></div>
</li>
</ul>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -0,0 +1,375 @@
/*! normalize.css v2.0.1 | MIT License | git.io/normalize */
/* ==========================================================================
HTML5 display definitions
========================================================================== */
/*
* Corrects `block` display not defined in IE 8/9.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
nav,
section,
summary {
display: block;
}
/*
* Corrects `inline-block` display not defined in IE 8/9.
*/
audio,
canvas,
video {
display: inline-block;
}
/*
* Prevents modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/*
* Addresses styling for `hidden` attribute not present in IE 8/9.
*/
[hidden] {
display: none;
}
/* ==========================================================================
Base
========================================================================== */
/*
* 1. Sets default font family to sans-serif.
* 2. Prevents iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-ms-text-size-adjust: 100%; /* 2 */
}
/*
* Removes default margin.
*/
body {
margin: 0;
}
/* ==========================================================================
Links
========================================================================== */
/*
* Addresses `outline` inconsistency between Chrome and other browsers.
*/
a:focus {
outline: thin dotted;
}
/*
* Improves readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* ==========================================================================
Typography
========================================================================== */
/*
* Addresses `h1` font sizes within `section` and `article` in Firefox 4+,
* Safari 5, and Chrome.
*/
h1 {
font-size: 2em;
}
/*
* Addresses styling not present in IE 8/9, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/*
* Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/*
* Addresses styling not present in Safari 5 and Chrome.
*/
dfn {
font-style: italic;
}
/*
* Addresses styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/*
* Corrects font family set oddly in Safari 5 and Chrome.
*/
code,
kbd,
pre,
samp {
font-family: monospace, serif;
font-size: 1em;
}
/*
* Improves readability of pre-formatted text in all browsers.
*/
pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}
/*
* Sets consistent quote types.
*/
q {
quotes: "\201C" "\201D" "\2018" "\2019";
}
/*
* Addresses inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/*
* Prevents `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* ==========================================================================
Embedded content
========================================================================== */
/*
* Removes border when inside `a` element in IE 8/9.
*/
img {
border: 0;
}
/*
* Corrects overflow displayed oddly in IE 9.
*/
svg:not(:root) {
overflow: hidden;
}
/* ==========================================================================
Figures
========================================================================== */
/*
* Addresses margin not present in IE 8/9 and Safari 5.
*/
figure {
margin: 0;
}
/* ==========================================================================
Forms
========================================================================== */
/*
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/*
* 1. Corrects color not being inherited in IE 8/9.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/*
* 1. Corrects font family not being inherited in all browsers.
* 2. Corrects font size not being inherited in all browsers.
* 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome
*/
button,
input,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 2 */
margin: 0; /* 3 */
}
/*
* Addresses Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
button,
input {
line-height: normal;
}
/*
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Corrects inability to style clickable `input` types in iOS.
* 3. Improves usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/*
* Re-set default cursor for disabled elements.
*/
button[disabled],
input[disabled] {
cursor: default;
}
/*
* 1. Addresses box sizing set to `content-box` in IE 8/9.
* 2. Removes excess padding in IE 8/9.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/*
* 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/*
* Removes inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
* Removes inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/*
* 1. Removes default vertical scrollbar in IE 8/9.
* 2. Improves readability and alignment in all browsers.
*/
textarea {
overflow: auto; /* 1 */
vertical-align: top; /* 2 */
}
/* ==========================================================================
Tables
========================================================================== */
/*
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}

View file

@ -0,0 +1,137 @@
# # Translation Plugin
# Last Revised: April 23, 2015 by Ben Gotow
#
# TranslateButton is a simple React component that allows you to select
# a language from a popup menu and translates draft text into that language.
#
request = require 'request'
{React,
ComponentRegistry,
QuotedHTMLParser,
DraftStore} = require 'nylas-exports'
{Menu,
RetinaImg,
Popover} = require 'nylas-component-kit'
YandexTranslationURL = 'https://translate.yandex.net/api/v1.5/tr.json/translate'
YandexTranslationKey = 'trnsl.1.1.20150415T044616Z.24814c314120d022.0a339e2bc2d2337461a98d5ec9863fc46e42735e'
YandexLanguages =
'English': 'en'
'Spanish': 'es'
'Russian': 'ru'
'Chinese': 'zh'
'French': 'fr'
'German': 'de'
'Italian': 'it'
'Japanese': 'ja'
'Portuguese': 'pt'
'Korean': 'ko'
class TranslateButton extends React.Component
# Adding a `displayName` makes debugging React easier
@displayName: 'TranslateButton'
# Since our button is being injected into the Composer Footer,
# we receive the local id of the current draft as a `prop` (a read-only
# property). Since our code depends on this prop, we mark it as a requirement.
#
@propTypes:
draftClientId: React.PropTypes.string.isRequired
# The `render` method returns a React Virtual DOM element. This code looks
# like HTML, but don't be fooled. The CJSX preprocessor converts
#
# `<a href="http://facebook.github.io/react/">Hello!</a>`
#
# into Javascript objects which describe the HTML you want:
#
# `React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello!')`
#
# We're rendering a `Popover` with a `Menu` inside. These components are part
# of Edgehill's standard `nylas-component-kit` library, and make it easy to build
# interfaces that match the rest of Edgehill's UI.
#
render: =>
<Popover ref="popover"
className="translate-language-picker pull-right"
buttonComponent={@_renderButton()}>
<Menu items={ Object.keys(YandexLanguages) }
itemKey={ (item) -> item }
itemContent={ (item) -> item }
onSelect={@_onTranslate}
/>
</Popover>
# Helper method to render the button that will activate the popover. Using the
# `RetinaImg` component makes it easy to display an image from our package.
# `RetinaImg` will automatically chose the best image format for our display.
#
_renderButton: =>
<button className="btn btn-toolbar">
Translate
<RetinaImg name="toolbar-chevron.png"/>
</button>
_onTranslate: (lang) =>
@refs.popover.close()
# Obtain the session for the current draft. The draft session provides us
# the draft object and also manages saving changes to the local cache and
# Nilas API as multiple parts of the application touch the draft.
#
session = DraftStore.sessionForClientId(@props.draftClientId).then (session) =>
draftHtml = session.draft().body
text = QuotedHTMLParser.removeQuotedHTML(draftHtml)
query =
key: YandexTranslationKey
lang: YandexLanguages[lang]
text: text
format: 'html'
# Use Node's `request` library to perform the translation using the Yandex API.
request {url: YandexTranslationURL, qs: query}, (error, resp, data) =>
return @_onError(error) unless resp.statusCode is 200
json = JSON.parse(data)
# The new text of the draft is our translated response, plus any quoted text
# that we didn't process.
translated = json.text.join('')
translated = QuotedHTMLParser.appendQuotedHTML(translated, draftHtml)
# To update the draft, we add the new body to it's session. The session object
# automatically marshalls changes to the database and ensures that others accessing
# the same draft are notified of changes.
session.changes.add(body: translated)
session.changes.commit()
_onError: (error) =>
@refs.popover.close()
dialog = require('remote').require('dialog')
dialog.showErrorBox('Language Conversion Failed', error.toString())
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)

View file

@ -0,0 +1,21 @@
{
"name": "translate",
"version": "0.2.0",
"main": "./lib/main",
"description": "An example package for Nylas Mail that translates drafts into other languages using the Yandex API.",
"license": "Proprietary",
"engines": {
"atom": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/nylas/translate"
},
"windowTypes": {
"default": true,
"composer": true
},
"dependencies": {
"request": "^2.53"
}
}

View file

@ -0,0 +1,12 @@
describe "AccountSidebarStore", ->
xit "should update it's selected ID when the focusTag action fires", ->
true
xit "should update when the DatabaseStore emits changes to tags", ->
true
xit "should update when the NamespaceStore emits", ->
true
xit "should provide an array of sections to the sidebar view", ->
true

View file

@ -0,0 +1,13 @@
@import "ui-variables";
@import "ui-mixins";
.translate-language-picker .menu {
.footer-container,
.header-container {
display:none;
}
.content-container {
height:185px;
overflow:scroll;
}
}

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,85 @@
_ = require 'underscore-plus'
GithubUserStore = require "./github-user-store"
{React} = require 'nylas-exports'
# Small React component that renders a single Github repository
class GithubRepo extends React.Component
@displayName: 'GithubRepo'
@propTypes:
# This component takes a `repo` object as a prop. Listing props is optional
# but enables nice React warnings when our expectations aren't met
repo: React.PropTypes.object.isRequired
render: =>
<div className="repo">
<div className="stars">{@props.repo.stargazers_count}</div>
<a href={@props.repo.html_url}>{@props.repo.full_name}</a>
</div>
# Small React component that renders the user's Github profile.
class GithubProfile extends React.Component
@displayName: 'GithubProfile'
@propTypes:
# This component takes a `profile` object as a prop. Listing props is optional
# but enables nice React warnings when our expectations aren't met.
profile: React.PropTypes.object.isRequired
render: =>
# Transform the profile's array of repos into an array of React <GithubRepo> elements
repoElements = _.map @props.profile.repos, (repo) ->
<GithubRepo key={repo.id} repo={repo} />
# Remember - this looks like HTML, but it's actually CJSX, which is converted into
# Coffeescript at transpile-time. We're actually creating a nested tree of Javascript
# objects here that *represent* the DOM we want.
<div className="profile">
<img className="logo" src="nylas://sidebar-github-profile/assets/github.png"/>
<a href={@props.profile.html_url}>{@props.profile.login}</a>
<div>{repoElements}</div>
</div>
module.exports =
class GithubSidebar extends React.Component
@displayName: 'GithubSidebar'
# We're registering this component to appear in one of the app's primary
# columns, the MessageListSidebar. Each React Component in a column can
# specify a min and max width which limit the resizing behavior of the column.
@containerStyles:
maxWidth: 300
minWidth: 200
order: 2
flexShrink: 0
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: =>
# When our component mounts, start listening to the GithubUserStore.
# When the store `triggers`, our `_onChange` method will fire and allow
# us to replace our state.
@unsubscribe = GithubUserStore.listen @_onChange
componentWillUnmount: =>
@unsubscribe()
render: =>
<div className="sidebar-github-profile">
<h2>Github</h2>
{@_renderInner()}
</div>
_renderInner: =>
# Handle various loading states by returning early
return <div>Loading...</div> if @state.loading
return <div>No Matching Profile</div> if not @state.profile
<GithubProfile profile={@state.profile} />
# The data vended by the GithubUserStore has changed. Calling `setState:`
# will cause React to re-render our view to reflect the new values.
_onChange: =>
@setState(@_getStateFromStores())
_getStateFromStores: =>
profile: GithubUserStore.profileForFocusedContact()
loading: GithubUserStore.loading()

View file

@ -0,0 +1,82 @@
_ = require 'underscore-plus'
Reflux = require 'reflux'
request = require 'request'
{FocusedContactsStore} = require 'nylas-exports'
module.exports =
# This package uses the Flux pattern - our Store is a small singleton that
# observes other parts of the application and vends data to our React
# component. If the user could interact with the GithubSidebar, this store
# would also listen for `Actions` emitted by our React components.
GithubUserStore = Reflux.createStore
init: ->
@_profile = null
@_cache = {}
@_loading = false
@_error = null
# Register a callback with the FocusedContactsStore. This will tell us
# whenever the selected person has changed so we can refresh our data.
@listenTo FocusedContactsStore, @_onFocusedContactChanged
# Getter Methods
profileForFocusedContact: ->
@_profile
loading: ->
@_loading
error: ->
@_error
# Called when the FocusedContactStore `triggers`, notifying us that the data
# it vends has changed.
_onFocusedContactChanged: ->
# Grab the new focused contact
contact = FocusedContactsStore.focusedContact()
# First, clear the contact that we're currently showing and `trigger`. Since
# our React component observes our store, `trigger` causes our React component
# to re-render.
@_error = null
@_profile = null
if contact
@_profile = @_cache[contact.email]
# Make a Github search request to find the matching user profile
@_githubFetchProfile(contact.email) unless @_profile?
@trigger(@)
_githubFetchProfile: (email) ->
@_loading = true
@_githubRequest "https://api.github.com/search/users?q=#{email}", (err, resp, data) =>
console.warn(data.message) if data.message?
# Sometimes we get rate limit errors, etc., so we need to check and make
# sure we've gotten items before pulling the first one.
profile = data?.items?[0] ? false
# If a profile was found, make a second request for the user's public
# repositories.
if profile
profile.repos = []
@_githubRequest profile.repos_url, (err, resp, repos) =>
# Sort the repositories by their stars (`-` for descending order)
profile.repos = _.sortBy repos, (repo) -> -repo.stargazers_count
# Trigger so that our React components refresh their state and display
# the updated data.
@trigger(@)
@_loading = false
@_profile = @_cache[email] = profile
@trigger(@)
# Wrap the Node `request` library and pass the User-Agent header, which is required
# by Github's API. Also pass `json:true`, which causes responses to be automatically
# parsed.
_githubRequest: (url, callback) ->
request({url: url, headers: {'User-Agent': 'request'}, json: true}, callback)

View file

@ -0,0 +1,30 @@
_ = require 'underscore-plus'
GithubSidebar = require "./github-sidebar"
{ComponentRegistry,
WorkspaceStore} = require "nylas-exports"
module.exports =
# Activate is called when the package is loaded. If your package previously
# saved state using `serialize` it is provided.
#
activate: (@state={}) ->
# Register our sidebar so that it appears in the Message List sidebar.
# This sidebar is to the right of the Message List in both split pane mode
# and list mode.
ComponentRegistry.register GithubSidebar,
location: WorkspaceStore.Location.MessageListSidebar
# 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: ->
# Unregister our component
ComponentRegistry.unregister(GithubSidebar)

View file

@ -0,0 +1,19 @@
{
"name": "sidebar-github-profile",
"version": "0.1.0",
"main": "./lib/main",
"description": "View github user data in the sidebar!",
"license": "MIT",
"engines": {
"atom": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/bengotow/nylas-sidebar-github-profile"
},
"dependencies": {
"reflux": "0.1.13",
"request": "^2.53",
"underscore-plus": "^1.6"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View file

@ -0,0 +1,40 @@
@import "ui-variables";
.sidebar-github-profile {
padding: @spacing-standard;
padding-bottom: 0;
a{ text-decoration: none; }
.logo {
float: left;
width:15px;
height:15px;
opacity:0.4;
margin-right: @spacing-half;
}
.repo {
padding-left: @spacing-standard;
border-left:1px solid @border-color-divider;
margin-left: @spacing-standard/2;
font-size: @font-size-smaller;
a {
overflow:hidden;
text-overflow:ellipsis;
}
.stars {
float:right;
margin-left:@spacing-standard;
}
}
h2 {
font-size: 11px;
font-weight: @font-weight-semi-bold;
text-transform: uppercase;
color: @text-color-very-subtle;
border-bottom: 1px solid @border-color-divider;
margin: 2em 0 1em 0;
}
}