diff --git a/examples/N1-Composer-Translate/.gitignore b/examples/N1-Composer-Translate/.gitignore new file mode 100755 index 000000000..3c3629e64 --- /dev/null +++ b/examples/N1-Composer-Translate/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/examples/N1-Composer-Translate/README.md b/examples/N1-Composer-Translate/README.md new file mode 100644 index 000000000..7a34f50b2 --- /dev/null +++ b/examples/N1-Composer-Translate/README.md @@ -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 +``` \ No newline at end of file diff --git a/examples/N1-Composer-Translate/docs/docco.css b/examples/N1-Composer-Translate/docs/docco.css new file mode 100644 index 000000000..b60f6fa3d --- /dev/null +++ b/examples/N1-Composer-Translate/docs/docco.css @@ -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 + +*/ + +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; +} diff --git a/examples/N1-Composer-Translate/docs/main.coffee b/examples/N1-Composer-Translate/docs/main.coffee new file mode 100644 index 000000000..bba0b3a01 --- /dev/null +++ b/examples/N1-Composer-Translate/docs/main.coffee @@ -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 + # + # `Hello!` + # + # 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) diff --git a/examples/N1-Composer-Translate/docs/main.html b/examples/N1-Composer-Translate/docs/main.html new file mode 100644 index 000000000..eaafe2c62 --- /dev/null +++ b/examples/N1-Composer-Translate/docs/main.html @@ -0,0 +1,312 @@ + + + + + Translation Plugin + + + + + +
+
+ + +
+ + diff --git a/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.eot b/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.eot new file mode 100644 index 000000000..1b32532a8 Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.eot differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.ttf b/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.ttf new file mode 100644 index 000000000..dc4cc9c27 Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.ttf differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.woff b/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.woff new file mode 100644 index 000000000..fa16fd0ab Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/aller-bold.woff differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/aller-light.eot b/examples/N1-Composer-Translate/docs/public/fonts/aller-light.eot new file mode 100644 index 000000000..40bd654b5 Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/aller-light.eot differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/aller-light.ttf b/examples/N1-Composer-Translate/docs/public/fonts/aller-light.ttf new file mode 100644 index 000000000..c2c72902a Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/aller-light.ttf differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/aller-light.woff b/examples/N1-Composer-Translate/docs/public/fonts/aller-light.woff new file mode 100644 index 000000000..81a09d18e Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/aller-light.woff differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/fleurons.eot b/examples/N1-Composer-Translate/docs/public/fonts/fleurons.eot new file mode 100644 index 000000000..26189bb40 Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/fleurons.eot differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/fleurons.ttf b/examples/N1-Composer-Translate/docs/public/fonts/fleurons.ttf new file mode 100644 index 000000000..7b1b01726 Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/fleurons.ttf differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/fleurons.woff b/examples/N1-Composer-Translate/docs/public/fonts/fleurons.woff new file mode 100644 index 000000000..10b7e1a1d Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/fleurons.woff differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.eot b/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.eot new file mode 100755 index 000000000..571ed4912 Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.eot differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.ttf b/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.ttf new file mode 100755 index 000000000..e0300b3ee Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.ttf differ diff --git a/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.woff b/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.woff new file mode 100755 index 000000000..642e5b60f Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/fonts/roboto-black.woff differ diff --git a/examples/N1-Composer-Translate/docs/public/images/gray.png b/examples/N1-Composer-Translate/docs/public/images/gray.png new file mode 100644 index 000000000..6eb666914 Binary files /dev/null and b/examples/N1-Composer-Translate/docs/public/images/gray.png differ diff --git a/examples/N1-Composer-Translate/docs/public/stylesheets/normalize.css b/examples/N1-Composer-Translate/docs/public/stylesheets/normalize.css new file mode 100644 index 000000000..73abb76fa --- /dev/null +++ b/examples/N1-Composer-Translate/docs/public/stylesheets/normalize.css @@ -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; +} \ No newline at end of file diff --git a/examples/N1-Composer-Translate/lib/main.cjsx b/examples/N1-Composer-Translate/lib/main.cjsx new file mode 100644 index 000000000..9c2a963d7 --- /dev/null +++ b/examples/N1-Composer-Translate/lib/main.cjsx @@ -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 + # + # `Hello!` + # + # 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: => + + 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: => + + + _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) diff --git a/examples/N1-Composer-Translate/package.json b/examples/N1-Composer-Translate/package.json new file mode 100755 index 000000000..8e8c4ed9d --- /dev/null +++ b/examples/N1-Composer-Translate/package.json @@ -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" + } +} diff --git a/examples/N1-Composer-Translate/spec/main-spec.coffee b/examples/N1-Composer-Translate/spec/main-spec.coffee new file mode 100644 index 000000000..6636dee73 --- /dev/null +++ b/examples/N1-Composer-Translate/spec/main-spec.coffee @@ -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 diff --git a/examples/N1-Composer-Translate/stylesheets/translate.less b/examples/N1-Composer-Translate/stylesheets/translate.less new file mode 100644 index 000000000..a57e8bc06 --- /dev/null +++ b/examples/N1-Composer-Translate/stylesheets/translate.less @@ -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; + } +} diff --git a/examples/N1-Github/assets/github@2x.png b/examples/N1-Message-View-on-Github/assets/github@2x.png similarity index 100% rename from examples/N1-Github/assets/github@2x.png rename to examples/N1-Message-View-on-Github/assets/github@2x.png diff --git a/examples/N1-Github/keymaps/github.cson b/examples/N1-Message-View-on-Github/keymaps/github.cson similarity index 100% rename from examples/N1-Github/keymaps/github.cson rename to examples/N1-Message-View-on-Github/keymaps/github.cson diff --git a/examples/N1-Github/lib/github-store.coffee b/examples/N1-Message-View-on-Github/lib/github-store.coffee similarity index 100% rename from examples/N1-Github/lib/github-store.coffee rename to examples/N1-Message-View-on-Github/lib/github-store.coffee diff --git a/examples/N1-Github/lib/main.cjsx b/examples/N1-Message-View-on-Github/lib/main.cjsx similarity index 100% rename from examples/N1-Github/lib/main.cjsx rename to examples/N1-Message-View-on-Github/lib/main.cjsx diff --git a/examples/N1-Github/lib/view-on-github-button.cjsx b/examples/N1-Message-View-on-Github/lib/view-on-github-button.cjsx similarity index 100% rename from examples/N1-Github/lib/view-on-github-button.cjsx rename to examples/N1-Message-View-on-Github/lib/view-on-github-button.cjsx diff --git a/examples/N1-Github/package.json b/examples/N1-Message-View-on-Github/package.json similarity index 100% rename from examples/N1-Github/package.json rename to examples/N1-Message-View-on-Github/package.json diff --git a/examples/N1-Github/stylesheets/github.less b/examples/N1-Message-View-on-Github/stylesheets/github.less similarity index 100% rename from examples/N1-Github/stylesheets/github.less rename to examples/N1-Message-View-on-Github/stylesheets/github.less diff --git a/examples/N1-Sidebar-Github/assets/github.png b/examples/N1-Sidebar-Github/assets/github.png new file mode 100644 index 000000000..fd168a467 Binary files /dev/null and b/examples/N1-Sidebar-Github/assets/github.png differ diff --git a/examples/N1-Sidebar-Github/lib/github-sidebar.cjsx b/examples/N1-Sidebar-Github/lib/github-sidebar.cjsx new file mode 100644 index 000000000..7f6d398d2 --- /dev/null +++ b/examples/N1-Sidebar-Github/lib/github-sidebar.cjsx @@ -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: => +
+
{@props.repo.stargazers_count}
+ {@props.repo.full_name} +
+ +# 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 elements + repoElements = _.map @props.profile.repos, (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. +
+ + {@props.profile.login} +
{repoElements}
+
+ +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: => +
+

Github

+ {@_renderInner()} +
+ + _renderInner: => + # Handle various loading states by returning early + return
Loading...
if @state.loading + return
No Matching Profile
if not @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() diff --git a/examples/N1-Sidebar-Github/lib/github-user-store.coffee b/examples/N1-Sidebar-Github/lib/github-user-store.coffee new file mode 100644 index 000000000..50a9d267c --- /dev/null +++ b/examples/N1-Sidebar-Github/lib/github-user-store.coffee @@ -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) diff --git a/examples/N1-Sidebar-Github/lib/main.cjsx b/examples/N1-Sidebar-Github/lib/main.cjsx new file mode 100644 index 000000000..ff3da52ae --- /dev/null +++ b/examples/N1-Sidebar-Github/lib/main.cjsx @@ -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) diff --git a/examples/N1-Sidebar-Github/package.json b/examples/N1-Sidebar-Github/package.json new file mode 100755 index 000000000..f39b61e38 --- /dev/null +++ b/examples/N1-Sidebar-Github/package.json @@ -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" + } +} diff --git a/examples/N1-Sidebar-Github/screenshot.png b/examples/N1-Sidebar-Github/screenshot.png new file mode 100644 index 000000000..a954cb4fd Binary files /dev/null and b/examples/N1-Sidebar-Github/screenshot.png differ diff --git a/examples/N1-Sidebar-Github/stylesheets/sidebar-github-profile.less b/examples/N1-Sidebar-Github/stylesheets/sidebar-github-profile.less new file mode 100644 index 000000000..92dd1f014 --- /dev/null +++ b/examples/N1-Sidebar-Github/stylesheets/sidebar-github-profile.less @@ -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; + } +}